@joebigelow / wix / commits / fb4f8c71

Replace PathIsAbsolute with PathIsRooted and add PathIsFullyQualified.

Sean Hall committed May 26, 2022 at 17:33 UTC fb4f8c7108f43d2341ba299424646c4963b21188
8 files changed +350 -22
src/burn/engine/cache.cpp
+1 -1
@@ -563,7 +563,7 @@ extern "C" HRESULT CacheGetLocalSourcePaths(
563 fPreferSourcePathLocation = !pCache->fRunningFromCache || FAILED(hr);
564 fTryLastFolder = SUCCEEDED(hr) && sczLastSourceFolder && *sczLastSourceFolder && CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pCache->sczSourceProcessFolder, -1, sczLastSourceFolder, -1);
565 fTryRelativePath = CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzSourcePath, -1, wzRelativePath, -1);
566 - fSourceIsAbsolute = PathIsAbsolute(wzSourcePath);
566 + fSourceIsAbsolute = PathIsRooted(wzSourcePath);
567
568 // If the source path provided is a full path, try that first.
569 if (fSourceIsAbsolute)
src/burn/engine/logging.cpp
+1 -1
@@ -140,7 +140,7 @@ extern "C" HRESULT LoggingOpen(
140 LPCWSTR wzPrefix = sczPrefixFormatted;
141
142 // Best effort to open default logging.
143 - if (PathIsAbsolute(sczPrefixFormatted))
143 + if (PathIsRooted(sczPrefixFormatted))
144 {
145 hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder);
146 ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted);
src/internal/WixBuildTools.TestSupport.Native/NativeAssert.h
+21
@@ -64,6 +64,27 @@ namespace TestSupport {
64 WixAssert::Succeeded(hr, gcnew String(zFormat), formatArgs);
65 }
66
67 + static void SpecificReturnCode(HRESULT hrExpected, HRESULT hr, LPCSTR zFormat, LPCSTR zArg, ... array<LPCSTR>^ zArgs)
68 + {
69 + array<Object^>^ formatArgs = gcnew array<Object^, 1>(zArgs->Length + 1);
70 + formatArgs[0] = NativeAssert::LPSTRToString(zArg);
71 + for (int i = 0; i < zArgs->Length; ++i)
72 + {
73 + formatArgs[i + 1] = NativeAssert::LPSTRToString(zArgs[i]);
74 + }
75 + WixAssert::SpecificReturnCode(hrExpected, hr, gcnew String(zFormat), formatArgs);
76 + }
77 +
78 + static void SpecificReturnCode(HRESULT hrExpected, HRESULT hr, LPCSTR zFormat, ... array<LPCWSTR>^ wzArgs)
79 + {
80 + array<Object^>^ formatArgs = gcnew array<Object^, 1>(wzArgs->Length);
81 + for (int i = 0; i < wzArgs->Length; ++i)
82 + {
83 + formatArgs[i] = NativeAssert::LPWSTRToString(wzArgs[i]);
84 + }
85 + WixAssert::SpecificReturnCode(hrExpected, hr, gcnew String(zFormat), formatArgs);
86 + }
87 +
88 static void ValidReturnCode(HRESULT hr, ... array<HRESULT>^ validReturnCodes)
89 {
90 Assert::Contains(hr, (IEnumerable<HRESULT>^)validReturnCodes);
src/internal/WixBuildTools.TestSupport/XunitExtensions/SpecificReturnCodeException.cs new
+19
@@ -0,0 +1,19 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixBuildTools.TestSupport
4 +{
5 + using System;
6 + using Xunit.Sdk;
7 +
8 + public class SpecificReturnCodeException : XunitException
9 + {
10 + public SpecificReturnCodeException(int hr, string userMessage)
11 + : base(String.Format("WixAssert.SpecificReturnCode() Failure\r\n" +
12 + "HRESULT: 0x{0:X8}\r\n" +
13 + "Message: {1}",
14 + hr, userMessage))
15 + {
16 + this.HResult = hr;
17 + }
18 + }
19 +}
src/internal/WixBuildTools.TestSupport/XunitExtensions/WixAssert.cs
+8
@@ -52,6 +52,14 @@ namespace WixBuildTools.TestSupport
52 throw new SkipTestException(message);
53 }
54
55 + public static void SpecificReturnCode(int hrExpected, int hr, string format, params object[] formatArgs)
56 + {
57 + if (hrExpected != hr)
58 + {
59 + throw new SpecificReturnCodeException(hr, String.Format(format, formatArgs));
60 + }
61 + }
62 +
63 public static void Succeeded(int hr, string format, params object[] formatArgs)
64 {
65 if (0 > hr)
src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+16 -3
@@ -166,10 +166,23 @@ DAPI_(HRESULT) PathGetKnownFolder(
166 );
167
168 /*******************************************************************
169 - PathIsAbsolute - returns true if the path is absolute; false
170 - otherwise.
169 + PathIsFullyQualified - returns true if the path is fully qualified; false otherwise.
170 + Note that some rooted paths like C:dir are not fully qualified.
171 + For example, these are all fully qualified: C:\dir, \\server\share, \\?\C:\dir.
172 + For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir.
173 *******************************************************************/
172 -DAPI_(BOOL) PathIsAbsolute(
174 +DAPI_(BOOL) PathIsFullyQualified(
175 + __in_z LPCWSTR wzPath,
176 + __out_opt BOOL* pfHasLongPathPrefix
177 + );
178 +
179 +/*******************************************************************
180 + PathIsRooted - returns true if the path is rooted; false otherwise.
181 + Note that some rooted paths like C:dir are not fully qualified.
182 + For example, these are all rooted: C:\dir, C:dir, C:, \dir, \\server\share, \\?\C:\dir.
183 + For example, these are not rooted: dir, dir\subdir.
184 +*******************************************************************/
185 +DAPI_(BOOL) PathIsRooted(
186 __in_z LPCWSTR wzPath
187 );
188
src/libs/dutil/WixToolset.DUtil/pathutil.cpp
+85 -17
@@ -20,6 +20,10 @@
20
21 #define PATH_GOOD_ENOUGH 64
22
23 +static BOOL IsValidDriveChar(
24 + __in WCHAR wc
25 + );
26 +
27
28 DAPI_(LPWSTR) PathFile(
29 __in_z LPCWSTR wzPath
@@ -272,29 +276,30 @@ DAPI_(HRESULT) PathPrefix(
276
277 HRESULT hr = S_OK;
278 LPWSTR wzFullPath = *psczFullPath;
279 + BOOL fFullyQualified = FALSE;
280 + BOOL fHasPrefix = FALSE;
281 SIZE_T cbFullPath = 0;
282
277 - if (((L'a' <= wzFullPath[0] && L'z' >= wzFullPath[0]) ||
278 - (L'A' <= wzFullPath[0] && L'Z' >= wzFullPath[0])) &&
279 - L':' == wzFullPath[1] &&
280 - L'\\' == wzFullPath[2]) // normal path
283 + fFullyQualified = PathIsFullyQualified(wzFullPath, &fHasPrefix);
284 + if (fHasPrefix)
285 + {
286 + ExitFunction();
287 + }
288 +
289 + if (fFullyQualified && L':' == wzFullPath[1]) // normal path
290 {
291 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
292 PathExitOnFailure(hr, "Failed to add prefix to file path.");
293 }
285 - else if (L'\\' == wzFullPath[0] && L'\\' == wzFullPath[1]) // UNC
294 + else if (fFullyQualified && L'\\' == wzFullPath[1]) // UNC
295 {
287 - // ensure that we're not already prefixed
288 - if (!(L'?' == wzFullPath[2] && L'\\' == wzFullPath[3]))
289 - {
290 - hr = StrSize(*psczFullPath, &cbFullPath);
291 - PathExitOnFailure(hr, "Failed to get size of full path.");
296 + hr = StrSize(*psczFullPath, &cbFullPath);
297 + PathExitOnFailure(hr, "Failed to get size of full path.");
298
293 - memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
299 + memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
300
295 - hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
296 - PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
297 - }
301 + hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
302 + PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
303 }
304 else
305 {
@@ -814,11 +819,66 @@ LExit:
819 }
820
821
817 -DAPI_(BOOL) PathIsAbsolute(
822 +DAPI_(BOOL) PathIsFullyQualified(
823 + __in_z LPCWSTR wzPath,
824 + __out_opt BOOL* pfHasLongPathPrefix
825 + )
826 +{
827 + BOOL fFullyQualified = FALSE;
828 + BOOL fHasLongPathPrefix = FALSE;
829 +
830 + if (!wzPath || !wzPath[0] || !wzPath[1])
831 + {
832 + // There is no way to specify a fully qualified path with one character (or less).
833 + ExitFunction();
834 + }
835 +
836 + if (L'\\' != wzPath[0])
837 + {
838 + // The only way to specify a fully qualified path that doesn't begin with a slash
839 + // is the drive, colon, slash format (C:\).
840 + if (IsValidDriveChar(wzPath[0]) &&
841 + L':' == wzPath[1] &&
842 + L'\\' == wzPath[2])
843 + {
844 + fFullyQualified = TRUE;
845 + }
846 +
847 + ExitFunction();
848 + }
849 +
850 + // Non-drive fully qualified paths must start with \\ or \?.
851 + // \??\ is an archaic form of \\?\.
852 + if (L'?' != wzPath[1] && L'\\' != wzPath[1])
853 + {
854 + ExitFunction();
855 + }
856 +
857 + fFullyQualified = TRUE;
858 +
859 + if (L'?' == wzPath[2] && L'\\' == wzPath[3])
860 + {
861 + fHasLongPathPrefix = TRUE;
862 + }
863 +
864 +
865 +LExit:
866 + if (pfHasLongPathPrefix)
867 + {
868 + *pfHasLongPathPrefix = fHasLongPathPrefix;
869 + }
870 +
871 + return fFullyQualified;
872 +}
873 +
874 +
875 +DAPI_(BOOL) PathIsRooted(
876 __in_z LPCWSTR wzPath
877 )
878 {
821 - return wzPath && wzPath[0] && wzPath[1] && (wzPath[0] == L'\\') || (wzPath[1] == L':');
879 + return wzPath &&
880 + (wzPath[0] == L'\\' ||
881 + IsValidDriveChar(wzPath[0]) && wzPath[1] == L':');
882 }
883
884
@@ -847,7 +907,7 @@ DAPI_(HRESULT) PathConcatCch(
907 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
908 PathExitOnFailure(hr, "Failed to copy just path1 to output.");
909 }
850 - else if (!wzPath1 || !*wzPath1 || PathIsAbsolute(wzPath2))
910 + else if (!wzPath1 || !*wzPath1 || PathIsRooted(wzPath2))
911 {
912 hr = StrAllocString(psczCombined, wzPath2, cchPath2);
913 PathExitOnFailure(hr, "Failed to copy just path2 to output.");
@@ -1002,3 +1062,11 @@ LExit:
1062
1063 return hr;
1064 }
1065 +
1066 +static BOOL IsValidDriveChar(
1067 + __in WCHAR wc
1068 + )
1069 +{
1070 + return L'a' <= wc && L'z' >= wc ||
1071 + L'A' <= wc && L'Z' >= wc;
1072 +}
src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+199
@@ -109,5 +109,204 @@ namespace DutilTests
109 ReleaseStrArray(rgsczPaths, cPaths);
110 }
111 }
112 +
113 + [Fact]
114 + void PathPrefixTest()
115 + {
116 + HRESULT hr = S_OK;
117 + LPWSTR sczPath = NULL;
118 + LPCWSTR rgwzPaths[12] =
119 + {
120 + L"\\\\", L"\\\\?\\UNC\\",
121 + L"C:\\\\foo2", L"\\\\?\\C:\\\\foo2",
122 + L"\\\\a\\b\\", L"\\\\?\\UNC\\a\\b\\",
123 + L"\\\\?\\UNC\\test\\unc\\path\\to\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
124 + L"\\\\?\\C:\\foo\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt",
125 + L"\\??\\C:\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt",
126 + };
127 +
128 + try
129 + {
130 + for (DWORD i = 0; i < countof(rgwzPaths) / 2; i += 2)
131 + {
132 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
133 + NativeAssert::Succeeded(hr, "Failed to copy string");
134 +
135 + hr = PathPrefix(&sczPath);
136 + NativeAssert::Succeeded(hr, "PathPrefix: {0}", rgwzPaths[i]);
137 + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
138 + }
139 + }
140 + finally
141 + {
142 + ReleaseStr(sczPath);
143 + }
144 + }
145 +
146 + [Fact]
147 + void PathPrefixFailureTest()
148 + {
149 + HRESULT hr = S_OK;
150 + LPWSTR sczPath = NULL;
151 + LPCWSTR rgwzPaths[8] =
152 + {
153 + L"\\",
154 + L"C:",
155 + L"C:foo.txt",
156 + L"",
157 + L"\\?",
158 + L"\\dir",
159 + L"dir",
160 + L"dir\\subdir",
161 + };
162 +
163 + try
164 + {
165 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
166 + {
167 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
168 + NativeAssert::Succeeded(hr, "Failed to copy string");
169 +
170 + hr = PathPrefix(&sczPath);
171 + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath);
172 + }
173 + }
174 + finally
175 + {
176 + ReleaseStr(sczPath);
177 + }
178 + }
179 +
180 + [Fact]
181 + void PathIsRootedAndFullyQualifiedTest()
182 + {
183 + LPCWSTR rgwzPaths[15] =
184 + {
185 + L"\\\\",
186 + L"\\\\\\",
187 + L"C:\\",
188 + L"C:\\\\",
189 + L"C:\\foo1",
190 + L"C:\\\\foo2",
191 + L"\\\\test\\unc\\path\\to\\something",
192 + L"\\\\a\\b\\c\\d\\e",
193 + L"\\\\a\\b\\",
194 + L"\\\\a\\b",
195 + L"\\\\test\\unc",
196 + L"\\\\Server",
197 + L"\\\\Server\\Foo.txt",
198 + L"\\\\Server\\Share\\Foo.txt",
199 + L"\\\\Server\\Share\\Test\\Foo.txt",
200 + };
201 +
202 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
203 + {
204 + ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE);
205 + ValidateRootedPath(rgwzPaths[i], TRUE);
206 + }
207 + }
208 +
209 + [Fact]
210 + void PathIsRootedAndFullyQualifiedWithPrefixTest()
211 + {
212 + LPCWSTR rgwzPaths[6] =
213 + {
214 + L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
215 + L"\\\\?\\UNC\\test\\unc",
216 + L"\\\\?\\UNC\\a\\b1",
217 + L"\\\\?\\UNC\\a\\b2\\",
218 + L"\\\\?\\C:\\foo\\bar.txt",
219 + L"\\??\\C:\\foo\\bar.txt",
220 + };
221 +
222 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
223 + {
224 + ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE);
225 + ValidateRootedPath(rgwzPaths[i], TRUE);
226 + }
227 + }
228 +
229 + [Fact]
230 + void PathIsRootedButNotFullyQualifiedTest()
231 + {
232 + LPCWSTR rgwzPaths[7] =
233 + {
234 + L"\\",
235 + L"a:",
236 + L"A:",
237 + L"z:",
238 + L"Z:",
239 + L"C:foo.txt",
240 + L"\\dir",
241 + };
242 +
243 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
244 + {
245 + ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
246 + ValidateRootedPath(rgwzPaths[i], TRUE);
247 + }
248 + }
249 +
250 + [Fact]
251 + void PathIsNotRootedAndNotFullyQualifiedTest()
252 + {
253 + LPCWSTR rgwzPaths[9] =
254 + {
255 + NULL,
256 + L"",
257 + L"dir",
258 + L"dir\\subdir",
259 + L"@:\\foo", // 064 = @ 065 = A
260 + L"[:\\\\", // 091 = [ 090 = Z
261 + L"`:\\foo ", // 096 = ` 097 = a
262 + L"{:\\\\", // 123 = { 122 = z
263 + L"[:",
264 + };
265 +
266 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
267 + {
268 + ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
269 + ValidateRootedPath(rgwzPaths[i], FALSE);
270 + }
271 + }
272 +
273 + void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected, BOOL fExpectedHasPrefix)
274 + {
275 + BOOL fHasLongPathPrefix = FALSE;
276 + BOOL fRooted = PathIsFullyQualified(wzPath, &fHasLongPathPrefix);
277 + String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
278 + if (fExpected)
279 + {
280 + Assert::True(fRooted, message);
281 + }
282 + else
283 + {
284 + Assert::False(fRooted, message);
285 + }
286 +
287 + message = String::Format("HasLongPathPrefix: {0}", gcnew String(wzPath));
288 + if (fExpectedHasPrefix)
289 + {
290 + Assert::True(fHasLongPathPrefix, message);
291 + }
292 + else
293 + {
294 + Assert::False(fHasLongPathPrefix, message);
295 + }
296 + }
297 +
298 + void ValidateRootedPath(LPCWSTR wzPath, BOOL fExpected)
299 + {
300 + BOOL fRooted = PathIsRooted(wzPath);
301 + String^ message = String::Format("IsRooted: {0}", gcnew String(wzPath));
302 + if (fExpected)
303 + {
304 + Assert::True(fRooted, message);
305 + }
306 + else
307 + {
308 + Assert::False(fRooted, message);
309 + }
310 + }
311 };
312 }