@joebigelow / wix-1 / commits / dea25f58

Add PathCanonicalizeForComparison.

Sean Hall committed May 26, 2022 at 17:33 UTC dea25f58e6119ef1acc5c5cc2a7c98e52cdff519
8 files changed +822 -116
src/burn/engine/variable.cpp
+1 -1
@@ -1969,7 +1969,7 @@ static HRESULT InitializeVariableSystemFolder(
1969 {
1970 HRESULT hr = S_OK;
1971 BOOL f64 = (BOOL)dwpData;
1972 - WCHAR wzSystemFolder[MAX_PATH] = { };
1972 + WCHAR wzSystemFolder[MAX_PATH + 2] = { };
1973
1974 #if !defined(_WIN64)
1975 BOOL fIsWow64 = FALSE;
src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+48 -8
@@ -6,7 +6,17 @@
6 extern "C" {
7 #endif
8
9 -typedef enum PATH_EXPAND
9 +typedef enum _PATH_CANONICALIZE
10 +{
11 + // Always prefix fully qualified paths with the long path prefix (\\?\).
12 + PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX = 0x0001,
13 + // Always terminate the path with \.
14 + PATH_CANONICALIZE_BACKSLASH_TERMINATE = 0x0002,
15 + // Don't collapse . or .. in the \\server\share portion of a UNC path.
16 + PATH_CANONICALIZE_KEEP_UNC_ROOT = 0x0004,
17 +} PATH_CANONICALIZE;
18 +
19 +typedef enum _PATH_EXPAND
20 {
21 PATH_EXPAND_ENVIRONMENT = 0x0001,
22 PATH_EXPAND_FULLPATH = 0x0002,
@@ -29,7 +39,9 @@ DAPI_(LPCWSTR) PathExtension(
39 );
40
41 /*******************************************************************
32 - PathGetDirectory - extracts the directory from a path.
42 + PathGetDirectory - extracts the directory from a path including the directory separator.
43 + This means calling the function again with the previous result returns the same result.
44 + Returns S_FALSE if the path only contains a file name.
45 ********************************************************************/
46 DAPI_(HRESULT) PathGetDirectory(
47 __in_z LPCWSTR wzPath,
@@ -38,6 +50,7 @@ DAPI_(HRESULT) PathGetDirectory(
50
51 /*******************************************************************
52 PathGetParentPath - extracts the parent directory from a full path.
53 + *psczDirectory is NULL if the path only contains a file name.
54 ********************************************************************/
55 DAPI_(HRESULT) PathGetParentPath(
56 __in_z LPCWSTR wzPath,
@@ -62,6 +75,21 @@ DAPI_(HRESULT) PathPrefix(
75 __inout LPWSTR *psczFullPath
76 );
77
78 +/*******************************************************************
79 + PathFixedNormalizeSlashes - replaces all / with \ and
80 + removes redundant consecutive slashes.
81 +********************************************************************/
82 +DAPI_(HRESULT) PathFixedNormalizeSlashes(
83 + __inout_z LPWSTR wzPath
84 + );
85 +
86 +/*******************************************************************
87 + PathFixedReplaceForwardSlashes - replaces all / with \
88 +********************************************************************/
89 +DAPI_(void) PathFixedReplaceForwardSlashes(
90 + __inout_z LPWSTR wzPath
91 + );
92 +
93 /*******************************************************************
94 PathFixedBackslashTerminate - appends a \ if path does not have it
95 already, but fails if the buffer is
@@ -77,7 +105,7 @@ DAPI_(HRESULT) PathFixedBackslashTerminate(
105 already.
106 ********************************************************************/
107 DAPI_(HRESULT) PathBackslashTerminate(
80 - __inout LPWSTR* psczPath
108 + __inout_z LPWSTR* psczPath
109 );
110
111 /*******************************************************************
@@ -168,7 +196,7 @@ DAPI_(HRESULT) PathGetKnownFolder(
196 /*******************************************************************
197 PathIsFullyQualified - returns true if the path is fully qualified; false otherwise.
198 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.
199 + For example, these are all fully qualified: C:\dir, C:/dir, \\server\share, \\?\C:\dir.
200 For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir.
201 *******************************************************************/
202 DAPI_(BOOL) PathIsFullyQualified(
@@ -179,7 +207,7 @@ DAPI_(BOOL) PathIsFullyQualified(
207 /*******************************************************************
208 PathIsRooted - returns true if the path is rooted; false otherwise.
209 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.
210 + For example, these are all rooted: C:\dir, C:/dir, C:dir, C:, \dir, \\server\share, \\?\C:\dir.
211 For example, these are not rooted: dir, dir\subdir.
212 *******************************************************************/
213 DAPI_(BOOL) PathIsRooted(
@@ -240,7 +268,7 @@ DAPI_(HRESULT) PathGetHierarchyArray(
268 );
269
270 /*******************************************************************
243 - PathCanonicalizePath - wrapper around PathCanonicalizeW.
271 + PathCanonicalizePath - wrapper around PathCanonicalizeW.
272 *******************************************************************/
273 DAPI_(HRESULT) PathCanonicalizePath(
274 __in_z LPCWSTR wzPath,
@@ -248,8 +276,20 @@ DAPI_(HRESULT) PathCanonicalizePath(
276 );
277
278 /*******************************************************************
251 -PathDirectoryContainsPath - checks if wzPath is located inside
252 - wzDirectory.
279 + PathCanonicalizeForComparison - canonicalizes the path based on the given flags.
280 + . and .. directories are collapsed.
281 + All / are replaced with \.
282 + All redundant consecutive slashes are replaced with a single \.
283 +*******************************************************************/
284 +DAPI_(HRESULT) PathCanonicalizeForComparison(
285 + __in_z LPCWSTR wzPath,
286 + __in DWORD dwCanonicalizeFlags,
287 + __deref_out_z LPWSTR* psczCanonicalized
288 + );
289 +
290 +/*******************************************************************
291 + PathDirectoryContainsPath - checks if wzPath is located inside wzDirectory.
292 + wzDirectory must be a fully qualified path.
293 *******************************************************************/
294 DAPI_(HRESULT) PathDirectoryContainsPath(
295 __in_z LPCWSTR wzDirectory,
src/libs/dutil/WixToolset.DUtil/path2utl.cpp
+114 -35
@@ -9,6 +9,7 @@
9 #define PathExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
10 #define PathExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
11 #define PathExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
12 +#define PathExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, e, s, __VA_ARGS__)
13 #define PathExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
14 #define PathExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
15 #define PathExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
@@ -42,63 +43,141 @@ LExit:
43 return hr;
44 }
45
45 -DAPI_(HRESULT) PathDirectoryContainsPath(
46 - __in_z LPCWSTR wzDirectory,
47 - __in_z LPCWSTR wzPath
46 +DAPI_(HRESULT) PathCanonicalizeForComparison(
47 + __in_z LPCWSTR wzPath,
48 + __in DWORD dwCanonicalizeFlags,
49 + __deref_out_z LPWSTR* psczCanonicalized
50 )
51 {
52 HRESULT hr = S_OK;
51 - LPWSTR sczPath = NULL;
52 - LPWSTR sczDirectory = NULL;
53 - LPWSTR sczOriginalPath = NULL;
54 - LPWSTR sczOriginalDirectory = NULL;
53 + LPWSTR sczNormalizedPath = NULL;
54 + LPCWSTR wzNormalizedPath = NULL;
55 + SIZE_T cchUncRootLength = 0;
56 + BOOL fHasPrefix = FALSE;
57
56 - hr = PathCanonicalizePath(wzPath, &sczOriginalPath);
57 - PathExitOnFailure(hr, "Failed to canonicalize the path.");
58 + hr = StrAllocString(&sczNormalizedPath, wzPath, 0);
59 + PathExitOnFailure(hr, "Failed to allocate string for the normalized path.");
60
59 - hr = PathCanonicalizePath(wzDirectory, &sczOriginalDirectory);
60 - PathExitOnFailure(hr, "Failed to canonicalize the directory.");
61 + PathFixedNormalizeSlashes(sczNormalizedPath);
62 +
63 + wzNormalizedPath = sczNormalizedPath;
64
62 - if (!sczOriginalPath || !*sczOriginalPath)
65 + if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags)
66 {
64 - ExitFunction1(hr = S_FALSE);
67 + if (L'\\' == sczNormalizedPath[0] && (L'\\' == sczNormalizedPath[1] || L'?' == sczNormalizedPath[1]) && L'?' == sczNormalizedPath[2] && L'\\' == sczNormalizedPath[3])
68 + {
69 + if (L'U' == sczNormalizedPath[4] && L'N' == sczNormalizedPath[5] && L'C' == sczNormalizedPath[6] && L'\\' == sczNormalizedPath[7])
70 + {
71 + cchUncRootLength = 8;
72 + }
73 + }
74 + else if (L'\\' == sczNormalizedPath[0] && L'\\' == sczNormalizedPath[1])
75 + {
76 + cchUncRootLength = 2;
77 + }
78 +
79 + if (cchUncRootLength)
80 + {
81 + DWORD dwRemainingSlashes = 2;
82 +
83 + for (wzNormalizedPath += cchUncRootLength; *wzNormalizedPath && dwRemainingSlashes; ++wzNormalizedPath)
84 + {
85 + ++cchUncRootLength;
86 +
87 + if (L'\\' == *wzNormalizedPath)
88 + {
89 + --dwRemainingSlashes;
90 + }
91 + }
92 + }
93 + }
94 +
95 + if (*wzNormalizedPath)
96 + {
97 + hr = PathCanonicalizePath(wzNormalizedPath, psczCanonicalized);
98 + PathExitOnFailure(hr, "Failed to canonicalize: %ls", wzNormalizedPath);
99 }
66 - if (!sczOriginalDirectory || !*sczOriginalDirectory)
100 + else
101 {
68 - ExitFunction1(hr = S_FALSE);
102 + Assert(cchUncRootLength);
103 + ReleaseStr(*psczCanonicalized);
104 + *psczCanonicalized = sczNormalizedPath;
105 + sczNormalizedPath = NULL;
106 + cchUncRootLength = 0;
107 }
108
71 - sczPath = sczOriginalPath;
72 - sczDirectory = sczOriginalDirectory;
109 + if (cchUncRootLength)
110 + {
111 + hr = StrAllocPrefix(psczCanonicalized, sczNormalizedPath, cchUncRootLength);
112 + PathExitOnFailure(hr, "Failed to prefix the UNC root to the canonicalized path.");
113 + }
114
74 - for (; *sczDirectory;)
115 + if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags)
116 {
76 - if (!*sczPath)
77 - {
78 - ExitFunction1(hr = S_FALSE);
79 - }
117 + hr = PathBackslashTerminate(psczCanonicalized);
118 + PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path");
119 + }
120
81 - if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczDirectory, 1, sczPath, 1))
82 - {
83 - ExitFunction1(hr = S_FALSE);
84 - }
121 + if ((PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX & dwCanonicalizeFlags) &&
122 + PathIsFullyQualified(*psczCanonicalized, &fHasPrefix) && !fHasPrefix)
123 + {
124 + hr = PathPrefix(psczCanonicalized);
125 + PathExitOnFailure(hr, "Failed to ensure the long path prefix on the canonicalized path");
126 + }
127 +
128 +LExit:
129 + ReleaseStr(sczNormalizedPath);
130
86 - ++sczDirectory;
87 - ++sczPath;
131 + return hr;
132 +}
133 +
134 +DAPI_(HRESULT) PathDirectoryContainsPath(
135 + __in_z LPCWSTR wzDirectory,
136 + __in_z LPCWSTR wzPath
137 + )
138 +{
139 + HRESULT hr = S_OK;
140 + LPWSTR sczCanonicalizedDirectory = NULL;
141 + LPWSTR sczCanonicalizedPath = NULL;
142 + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT;
143 + size_t cchDirectory = 0;
144 +
145 + if (!wzDirectory || !*wzDirectory)
146 + {
147 + PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory is required.");
148 }
149 + if (!wzPath || !*wzPath)
150 + {
151 + PathExitWithRootFailure(hr, E_INVALIDARG, "wzPath is required.");
152 + }
153 +
154 + hr = PathCanonicalizeForComparison(wzDirectory, dwDefaultFlags | PATH_CANONICALIZE_BACKSLASH_TERMINATE, &sczCanonicalizedDirectory);
155 + PathExitOnFailure(hr, "Failed to canonicalize the directory.");
156
90 - --sczDirectory;
91 - if (('\\' == *sczDirectory && *sczPath) || '\\' == *sczPath)
157 + hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath);
158 + PathExitOnFailure(hr, "Failed to canonicalize the path.");
159 +
160 + if (!PathIsFullyQualified(sczCanonicalizedDirectory, NULL))
161 {
93 - hr = S_OK;
162 + PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path.");
163 }
95 - else
164 + if (!sczCanonicalizedPath || !*sczCanonicalizedPath)
165 {
97 - hr = S_FALSE;
166 + ExitFunction1(hr = S_FALSE);
167 }
168
169 + hr = ::StringCchLengthW(sczCanonicalizedDirectory, STRSAFE_MAX_CCH, &cchDirectory);
170 + PathExitOnFailure(hr, "Failed to get length of canonicalized directory.");
171 +
172 + if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczCanonicalizedDirectory, (DWORD)cchDirectory, sczCanonicalizedPath, (DWORD)cchDirectory))
173 + {
174 + ExitFunction1(hr = S_FALSE);
175 + }
176 +
177 + hr = sczCanonicalizedPath[cchDirectory] ? S_OK : S_FALSE;
178 +
179 LExit:
101 - ReleaseStr(sczOriginalPath);
102 - ReleaseStr(sczOriginalDirectory);
180 + ReleaseStr(sczCanonicalizedPath);
181 + ReleaseStr(sczCanonicalizedDirectory);
182 return hr;
183 }
src/libs/dutil/WixToolset.DUtil/pathutil.cpp
+145 -26
@@ -20,6 +20,9 @@
20
21 #define PATH_GOOD_ENOUGH 64
22
23 +static BOOL IsPathSeparatorChar(
24 + __in WCHAR wc
25 + );
26 static BOOL IsValidDriveChar(
27 __in WCHAR wc
28 );
@@ -41,7 +44,7 @@ DAPI_(LPWSTR) PathFile(
44 // \ => Windows path
45 // / => unix and URL path
46 // : => relative path from mapped root
44 - if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
47 + if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1))
48 {
49 wzFile = wz + 1;
50 }
@@ -64,7 +67,7 @@ DAPI_(LPCWSTR) PathExtension(
67 LPCWSTR wzExtension = NULL;
68 for (LPCWSTR wz = wzPath; *wz; ++wz)
69 {
67 - if (L'\\' == *wz || L'/' == *wz || L':' == *wz)
70 + if (IsPathSeparatorChar(*wz) || L':' == *wz)
71 {
72 wzExtension = NULL;
73 }
@@ -84,7 +87,8 @@ DAPI_(HRESULT) PathGetDirectory(
87 )
88 {
89 HRESULT hr = S_OK;
87 - size_t cchDirectory = SIZE_T_MAX;
90 + LPCWSTR wzRemaining = NULL;
91 + SIZE_T cchDirectory = 0;
92
93 for (LPCWSTR wz = wzPath; *wz; ++wz)
94 {
@@ -92,18 +96,20 @@ DAPI_(HRESULT) PathGetDirectory(
96 // \ => Windows path
97 // / => unix and URL path
98 // : => relative path from mapped root
95 - if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
99 + if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1))
100 {
97 - cchDirectory = static_cast<size_t>(wz - wzPath) + 1;
101 + wzRemaining = wz;
102 }
103 }
104
101 - if (SIZE_T_MAX == cchDirectory)
105 + if (!wzRemaining)
106 {
107 // we were given just a file name, so there's no directory available
104 - return S_FALSE;
108 + ExitFunction1(hr = S_FALSE);
109 }
110
111 + cchDirectory = static_cast<SIZE_T>(wzRemaining - wzPath) + 1;
112 +
113 hr = StrAllocString(psczDirectory, wzPath, cchDirectory);
114 PathExitOnFailure(hr, "Failed to copy directory.");
115
@@ -122,7 +128,7 @@ DAPI_(HRESULT) PathGetParentPath(
128
129 for (LPCWSTR wz = wzPath; *wz; ++wz)
130 {
125 - if (wz[1] && (L'\\' == *wz || L'/' == *wz))
131 + if (IsPathSeparatorChar(*wz) && wz[1])
132 {
133 wzParent = wz;
134 }
@@ -291,12 +297,13 @@ DAPI_(HRESULT) PathPrefix(
297 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
298 PathExitOnFailure(hr, "Failed to add prefix to file path.");
299 }
294 - else if (fFullyQualified && L'\\' == wzFullPath[1]) // UNC
300 + else if (fFullyQualified && IsPathSeparatorChar(wzFullPath[1])) // UNC
301 {
302 hr = StrSize(*psczFullPath, &cbFullPath);
303 PathExitOnFailure(hr, "Failed to get size of full path.");
304
305 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
306 + wzFullPath[0] = L'\\';
307
308 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
309 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
@@ -312,6 +319,90 @@ LExit:
319 }
320
321
322 +DAPI_(HRESULT) PathFixedNormalizeSlashes(
323 + __inout_z LPWSTR wzPath
324 + )
325 +{
326 + HRESULT hr = S_OK;
327 + size_t cchLength = 0;
328 + BOOL fAllowDoubleSlash = FALSE;
329 + SIZE_T iSource = 0;
330 + SIZE_T jDestination = 0;
331 +
332 + hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_CCH, &cchLength);
333 + PathExitOnFailure(hr, "Failed to get length of path.");
334 +
335 + if (1 < cchLength && IsPathSeparatorChar(wzPath[0]))
336 + {
337 + if (IsPathSeparatorChar(wzPath[1]))
338 + {
339 + // \\?\\a\ is not equivalent to \\?\a\ and \\server\\a\ is not equivalent to \\server\a\.
340 + fAllowDoubleSlash = TRUE;
341 + wzPath[0] = '\\';
342 + wzPath[1] = '\\';
343 + iSource = 2;
344 + jDestination = 2;
345 + }
346 + else if (2 < cchLength && L'?' == wzPath[1] && L'?' == wzPath[2])
347 + {
348 + // \??\\a\ is not equivalent to \??\a\.
349 + fAllowDoubleSlash = TRUE;
350 + wzPath[0] = '\\';
351 + wzPath[1] = '?';
352 + wzPath[2] = '?';
353 + iSource = 3;
354 + jDestination = 3;
355 + }
356 + }
357 +
358 + for (; iSource < cchLength; ++iSource)
359 + {
360 + if (IsPathSeparatorChar(wzPath[iSource]))
361 + {
362 + if (fAllowDoubleSlash)
363 + {
364 + fAllowDoubleSlash = FALSE;
365 + }
366 + else if (IsPathSeparatorChar(wzPath[iSource + 1]))
367 + {
368 + // Skip consecutive slashes.
369 + continue;
370 + }
371 +
372 + wzPath[jDestination] = '\\';
373 + }
374 + else
375 + {
376 + wzPath[jDestination] = wzPath[iSource];
377 + }
378 +
379 + ++jDestination;
380 + }
381 +
382 + for (; jDestination < cchLength; ++jDestination)
383 + {
384 + wzPath[jDestination] = '\0';
385 + }
386 +
387 +LExit:
388 + return hr;
389 +}
390 +
391 +
392 +DAPI_(void) PathFixedReplaceForwardSlashes(
393 + __inout_z LPWSTR wzPath
394 + )
395 +{
396 + for (LPWSTR wz = wzPath; *wz; ++wz)
397 + {
398 + if (L'/' == *wz)
399 + {
400 + *wz = L'\\';
401 + }
402 + }
403 +}
404 +
405 +
406 DAPI_(HRESULT) PathFixedBackslashTerminate(
407 __inout_ecount_z(cchPath) LPWSTR wzPath,
408 __in SIZE_T cchPath
@@ -320,17 +411,28 @@ DAPI_(HRESULT) PathFixedBackslashTerminate(
411 HRESULT hr = S_OK;
412 size_t cchLength = 0;
413
414 + if (!cchPath)
415 + {
416 + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
417 + }
418 +
419 hr = ::StringCchLengthW(wzPath, cchPath, &cchLength);
420 PathExitOnFailure(hr, "Failed to get length of path.");
421
326 - if (cchLength >= cchPath)
422 + LPWSTR wzLast = wzPath + (cchLength - 1);
423 + if (cchLength && L'/' == wzLast[0])
424 {
328 - hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
425 + wzLast[0] = L'\\';
426 }
330 - else if (L'\\' != wzPath[cchLength - 1])
427 + else if (!cchLength || L'\\' != wzLast[0])
428 {
332 - wzPath[cchLength] = L'\\';
333 - wzPath[cchLength + 1] = L'\0';
429 + if (cchLength + 2 > cchPath)
430 + {
431 + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
432 + }
433 +
434 + wzLast[1] = L'\\';
435 + wzLast[2] = L'\0';
436 }
437
438 LExit:
@@ -339,10 +441,10 @@ LExit:
441
442
443 DAPI_(HRESULT) PathBackslashTerminate(
342 - __inout LPWSTR* psczPath
444 + __inout_z LPWSTR* psczPath
445 )
446 {
345 - Assert(psczPath && *psczPath);
447 + Assert(psczPath);
448
449 HRESULT hr = S_OK;
450 SIZE_T cchPath = 0;
@@ -354,7 +456,12 @@ DAPI_(HRESULT) PathBackslashTerminate(
456 hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength);
457 PathExitOnFailure(hr, "Failed to get length of path.");
458
357 - if (L'\\' != (*psczPath)[cchLength - 1])
459 + LPWSTR wzLast = *psczPath + (cchLength - 1);
460 + if (cchLength && L'/' == wzLast[0])
461 + {
462 + wzLast[0] = L'\\';
463 + }
464 + else if (!cchLength || L'\\' != wzLast[0])
465 {
466 hr = StrAllocConcat(psczPath, L"\\", 1);
467 PathExitOnFailure(hr, "Failed to concat backslash onto string.");
@@ -833,13 +940,13 @@ DAPI_(BOOL) PathIsFullyQualified(
940 ExitFunction();
941 }
942
836 - if (L'\\' != wzPath[0])
943 + if (!IsPathSeparatorChar(wzPath[0]))
944 {
945 // The only way to specify a fully qualified path that doesn't begin with a slash
946 // is the drive, colon, slash format (C:\).
947 if (IsValidDriveChar(wzPath[0]) &&
948 L':' == wzPath[1] &&
842 - L'\\' == wzPath[2])
949 + IsPathSeparatorChar(wzPath[2]))
950 {
951 fFullyQualified = TRUE;
952 }
@@ -849,14 +956,14 @@ DAPI_(BOOL) PathIsFullyQualified(
956
957 // Non-drive fully qualified paths must start with \\ or \?.
958 // \??\ is an archaic form of \\?\.
852 - if (L'?' != wzPath[1] && L'\\' != wzPath[1])
959 + if (L'?' != wzPath[1] && !IsPathSeparatorChar(wzPath[1]))
960 {
961 ExitFunction();
962 }
963
964 fFullyQualified = TRUE;
965
859 - if (L'?' == wzPath[2] && L'\\' == wzPath[3])
966 + if (L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3]))
967 {
968 fHasLongPathPrefix = TRUE;
969 }
@@ -877,7 +984,7 @@ DAPI_(BOOL) PathIsRooted(
984 )
985 {
986 return wzPath &&
880 - (wzPath[0] == L'\\' ||
987 + (IsPathSeparatorChar(wzPath[0]) ||
988 IsValidDriveChar(wzPath[0]) && wzPath[1] == L':');
989 }
990
@@ -1008,20 +1115,25 @@ DAPI_(HRESULT) PathGetHierarchyArray(
1115
1116 for (size_t i = 0; i < cchPath; ++i)
1117 {
1011 - if (wzPath[i] == L'\\')
1118 + if (IsPathSeparatorChar(wzPath[i]))
1119 {
1120 ++cArraySpacesNeeded;
1121 }
1122 }
1123
1017 - if (wzPath[cchPath - 1] != L'\\')
1124 + if (!IsPathSeparatorChar(wzPath[cchPath - 1]))
1125 {
1126 ++cArraySpacesNeeded;
1127 }
1128
1129 // If it's a UNC path, cut off the first three paths, 2 because it starts with a double backslash, and another because the first ("\\servername\") isn't a path.
1023 - if (wzPath[0] == L'\\' && wzPath[1] == L'\\')
1130 + if (IsPathSeparatorChar(wzPath[0]) && IsPathSeparatorChar(wzPath[1]))
1131 {
1132 + if (3 > cArraySpacesNeeded)
1133 + {
1134 + ExitFunction1(hr = E_INVALIDARG);
1135 + }
1136 +
1137 cArraySpacesNeeded -= 3;
1138 }
1139
@@ -1042,7 +1154,7 @@ DAPI_(HRESULT) PathGetHierarchyArray(
1154 DWORD cchPathCopy = lstrlenW(sczPathCopy);
1155
1156 // If it ends in a backslash, it's a directory path, so cut off everything the last backslash before we get the directory portion of the path
1045 - if (wzPath[cchPathCopy - 1] == L'\\')
1157 + if (IsPathSeparatorChar(wzPath[cchPathCopy - 1]))
1158 {
1159 sczPathCopy[cchPathCopy - 1] = L'\0';
1160 }
@@ -1063,6 +1175,13 @@ LExit:
1175 return hr;
1176 }
1177
1178 +static BOOL IsPathSeparatorChar(
1179 + __in WCHAR wc
1180 + )
1181 +{
1182 + return L'/' == wc || L'\\' == wc;
1183 +}
1184 +
1185 static BOOL IsValidDriveChar(
1186 __in WCHAR wc
1187 )
src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
+1 -1
@@ -40,7 +40,7 @@
40
41 <PropertyGroup>
42 <ProjectAdditionalIncludeDirectories>..\..\WixToolset.DUtil\inc</ProjectAdditionalIncludeDirectories>
43 - <ProjectAdditionalLinkLibraries>rpcrt4.lib;Mpr.lib;Ws2_32.lib;urlmon.lib;wininet.lib</ProjectAdditionalLinkLibraries>
43 + <ProjectAdditionalLinkLibraries>rpcrt4.lib;Mpr.lib;Ws2_32.lib;shlwapi.lib;urlmon.lib;wininet.lib</ProjectAdditionalLinkLibraries>
44 </PropertyGroup>
45
46 <ItemGroup>
src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+511 -43
@@ -11,6 +11,290 @@ namespace DutilTests
11 public ref class PathUtil
12 {
13 public:
14 + [Fact]
15 + void PathBackslashFixedTerminateTest()
16 + {
17 + HRESULT hr = S_OK;
18 + WCHAR wzEmpty[1] = { L'\0' };
19 + WCHAR wzSingleLetter[1] = { L'a' };
20 + WCHAR wzSingleBackslash[1] = { L'\\' };
21 + WCHAR wzSingleForwardSlash[1] = { L'/' };
22 + WCHAR wzSingleLetterNullTerminated[2] = { L'a', L'\0' };
23 + WCHAR wzSingleBackslashNullTerminated[2] = { L'\\', L'\0' };
24 + WCHAR wzSingleForwardSlashNullTerminated[2] = { L'/', L'\0' };
25 + WCHAR wzExtraSpaceLetterNullTerminated[3] = { L'a', L'\0', L'\0' };
26 + WCHAR wzExtraSpaceBackslashNullTerminated[3] = { L'\\', L'\0', L'\0' };
27 + WCHAR wzExtraSpaceForwardSlashNullTerminated[3] = { L'/', L'\0', L'\0' };
28 +
29 + hr = PathFixedBackslashTerminate(wzEmpty, 0);
30 + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: zero-length, {0}", wzEmpty);
31 +
32 + hr = PathFixedBackslashTerminate(wzEmpty, countof(wzEmpty));
33 + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: '' (length 1), {0}", wzEmpty);
34 +
35 + hr = PathFixedBackslashTerminate(wzSingleLetter, countof(wzSingleLetter));
36 + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: 'a' (length 1)");
37 +
38 + hr = PathFixedBackslashTerminate(wzSingleBackslash, countof(wzSingleBackslash));
39 + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: '\\' (length 1)");
40 +
41 + hr = PathFixedBackslashTerminate(wzSingleForwardSlash, countof(wzSingleForwardSlash));
42 + NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathFixedBackslashTerminate: '/' (length 1)");
43 +
44 + hr = PathFixedBackslashTerminate(wzSingleLetterNullTerminated, countof(wzSingleLetterNullTerminated));
45 + NativeAssert::SpecificReturnCode(E_INSUFFICIENT_BUFFER, hr, "PathFixedBackslashTerminate: 'a' (length 2)");
46 +
47 + hr = PathFixedBackslashTerminate(wzSingleBackslashNullTerminated, countof(wzSingleBackslashNullTerminated));
48 + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '\\' (length 2)");
49 + NativeAssert::StringEqual(L"\\", wzSingleBackslashNullTerminated);
50 +
51 + hr = PathFixedBackslashTerminate(wzSingleForwardSlashNullTerminated, countof(wzSingleForwardSlashNullTerminated));
52 + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '/' (length 2)");
53 + NativeAssert::StringEqual(L"\\", wzSingleForwardSlashNullTerminated);
54 +
55 + hr = PathFixedBackslashTerminate(wzExtraSpaceLetterNullTerminated, countof(wzExtraSpaceLetterNullTerminated));
56 + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: 'a' (length 3)");
57 + NativeAssert::StringEqual(L"a\\", wzExtraSpaceLetterNullTerminated);
58 +
59 + hr = PathFixedBackslashTerminate(wzExtraSpaceBackslashNullTerminated, countof(wzExtraSpaceBackslashNullTerminated));
60 + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '\\' (length 3)");
61 + NativeAssert::StringEqual(L"\\", wzExtraSpaceBackslashNullTerminated);
62 +
63 + hr = PathFixedBackslashTerminate(wzExtraSpaceForwardSlashNullTerminated, countof(wzExtraSpaceForwardSlashNullTerminated));
64 + NativeAssert::Succeeded(hr, "PathFixedBackslashTerminate: '/' (length 3)");
65 + NativeAssert::StringEqual(L"\\", wzExtraSpaceForwardSlashNullTerminated);
66 + }
67 +
68 + [Fact]
69 + void PathBackslashTerminateTest()
70 + {
71 + HRESULT hr = S_OK;
72 + LPWSTR sczPath = NULL;
73 + LPCWSTR rgwzPaths[16] =
74 + {
75 + L"", L"\\",
76 + L"a", L"a\\",
77 + L"\\", L"\\",
78 + L"a\\", L"a\\",
79 + L"/", L"\\",
80 + L"a/", L"a\\",
81 + L"\\\\", L"\\\\",
82 + L"//", L"/\\",
83 + };
84 +
85 + try
86 + {
87 + for (DWORD i = 0; i < countof(rgwzPaths); i += 2)
88 + {
89 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
90 + NativeAssert::Succeeded(hr, "Failed to copy string");
91 +
92 + hr = PathBackslashTerminate(&sczPath);
93 + NativeAssert::Succeeded(hr, "PathBackslashTerminate: {0}", rgwzPaths[i]);
94 + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
95 + }
96 + }
97 + finally
98 + {
99 + ReleaseStr(sczPath);
100 + }
101 + }
102 +
103 + [Fact]
104 + void PathCanonicalizeForComparisonTest()
105 + {
106 + HRESULT hr = S_OK;
107 + LPWSTR sczCanonicalized = NULL;
108 +
109 + try
110 + {
111 + hr = PathCanonicalizeForComparison(L"C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized);
112 + Assert::Equal<HRESULT>(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr);
113 +
114 + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized);
115 + Assert::Equal<HRESULT>(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr);
116 +
117 + hr = PathCanonicalizeForComparison(L"\\\\server", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
118 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
119 + NativeAssert::StringEqual(L"\\\\server", sczCanonicalized);
120 +
121 + hr = PathCanonicalizeForComparison(L"\\\\server", 0, &sczCanonicalized);
122 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
123 + NativeAssert::StringEqual(L"\\\\server", sczCanonicalized);
124 +
125 + hr = PathCanonicalizeForComparison(L"\\\\server\\", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
126 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
127 + NativeAssert::StringEqual(L"\\\\server\\", sczCanonicalized);
128 +
129 + hr = PathCanonicalizeForComparison(L"\\\\server\\share", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
130 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
131 + NativeAssert::StringEqual(L"\\\\server\\share", sczCanonicalized);
132 +
133 + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
134 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
135 + NativeAssert::StringEqual(L"\\\\server\\share\\", sczCanonicalized);
136 +
137 + hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
138 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
139 + NativeAssert::StringEqual(L"\\\\.\\share\\otherdir\\unc.exe", sczCanonicalized);
140 +
141 + hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", 0, &sczCanonicalized);
142 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
143 + NativeAssert::StringEqual(L"\\\\share\\otherdir\\unc.exe", sczCanonicalized);
144 +
145 + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
146 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
147 + NativeAssert::StringEqual(L"\\\\server\\share\\otherdir\\unc.exe", sczCanonicalized);
148 +
149 + hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", 0, &sczCanonicalized);
150 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
151 + NativeAssert::StringEqual(L"\\\\otherdir\\unc.exe", sczCanonicalized);
152 +
153 + hr = PathCanonicalizeForComparison(L"\\\\?\\UNC\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
154 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
155 + NativeAssert::StringEqual(L"\\\\?\\UNC\\server\\share\\otherdir\\unc.exe", sczCanonicalized);
156 +
157 + hr = PathCanonicalizeForComparison(L"\\\\?\\UNC\\server\\share\\..\\..\\otherdir\\unc.exe", 0, &sczCanonicalized);
158 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
159 + NativeAssert::StringEqual(L"\\\\otherdir\\unc.exe", sczCanonicalized);
160 +
161 + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized);
162 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
163 + NativeAssert::StringEqual(L"C:\\otherdir\\pastroot.exe", sczCanonicalized);
164 +
165 + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized);
166 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
167 + NativeAssert::StringEqual(L"C:\\otherdir\\pastroot.exe", sczCanonicalized);
168 +
169 + hr = PathCanonicalizeForComparison(L"\\\\?\\C:dir\\subdir\\..\\..\\..\\otherdir\\pastroot.exe", 0, &sczCanonicalized);
170 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
171 + NativeAssert::StringEqual(L"\\otherdir\\pastroot.exe", sczCanonicalized);
172 +
173 + hr = PathCanonicalizeForComparison(L"C:dir\\subdir\\..\\..\\..\\otherdir\\pastrelativeroot.exe", 0, &sczCanonicalized);
174 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
175 + NativeAssert::StringEqual(L"\\otherdir\\pastrelativeroot.exe", sczCanonicalized);
176 +
177 + hr = PathCanonicalizeForComparison(L"A:dir\\subdir\\..\\..\\otherdir\\relativeroot.exe", 0, &sczCanonicalized);
178 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
179 + NativeAssert::StringEqual(L"\\otherdir\\relativeroot.exe", sczCanonicalized);
180 +
181 + hr = PathCanonicalizeForComparison(L"C:dir\\subdir\\otherdir\\relativeroot.exe", 0, &sczCanonicalized);
182 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
183 + NativeAssert::StringEqual(L"C:dir\\subdir\\otherdir\\relativeroot.exe", sczCanonicalized);
184 +
185 + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\otherdir\\backslashes.exe", 0, &sczCanonicalized);
186 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
187 + NativeAssert::StringEqual(L"C:\\otherdir\\backslashes.exe", sczCanonicalized);
188 +
189 + hr = PathCanonicalizeForComparison(L"C:\\dir\\subdir\\..\\..\\otherdir\\\\consecutivebackslashes.exe", 0, &sczCanonicalized);
190 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
191 + NativeAssert::StringEqual(L"C:\\otherdir\\consecutivebackslashes.exe", sczCanonicalized);
192 +
193 + hr = PathCanonicalizeForComparison(L"C:/dir/subdir/../../otherdir/forwardslashes.exe", 0, &sczCanonicalized);
194 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
195 + NativeAssert::StringEqual(L"C:\\otherdir\\forwardslashes.exe", sczCanonicalized);
196 +
197 + hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\test\\..\\validlongpath.exe", 0, &sczCanonicalized);
198 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
199 + NativeAssert::StringEqual(L"C:\\validlongpath.exe", sczCanonicalized);
200 +
201 + hr = PathCanonicalizeForComparison(L"\\\\?\\test\\..\\invalidlongpath.exe", 0, &sczCanonicalized);
202 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
203 + NativeAssert::StringEqual(L"\\\\?\\invalidlongpath.exe", sczCanonicalized);
204 +
205 + hr = PathCanonicalizeForComparison(L"C:\\.\\invalid:pathchars?.exe", 0, &sczCanonicalized);
206 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
207 + NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized);
208 +
209 + hr = PathCanonicalizeForComparison(L"C:\\addprefix.exe", PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX, &sczCanonicalized);
210 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
211 + NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized);
212 +
213 + hr = PathCanonicalizeForComparison(L"C:\\addbackslash.exe", PATH_CANONICALIZE_BACKSLASH_TERMINATE, &sczCanonicalized);
214 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
215 + NativeAssert::StringEqual(L"C:\\addbackslash.exe\\", sczCanonicalized);
216 + }
217 + finally
218 + {
219 + ReleaseStr(sczCanonicalized);
220 + }
221 + }
222 +
223 + [Fact]
224 + void PathDirectoryContainsPathTest()
225 + {
226 + HRESULT hr = S_OK;
227 +
228 + hr = PathDirectoryContainsPath(L"", L"");
229 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
230 +
231 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"");
232 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
233 +
234 + hr = PathDirectoryContainsPath(L"", L"C:\\Directory");
235 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
236 +
237 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\Directory");
238 + Assert::Equal<HRESULT>(S_FALSE, hr);
239 +
240 + hr = PathDirectoryContainsPath(L"C:\\Dir", L"C:\\Directory");
241 + Assert::Equal<HRESULT>(S_FALSE, hr);
242 +
243 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\");
244 + Assert::Equal<HRESULT>(S_FALSE, hr);
245 +
246 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\DirectoryPlus");
247 + Assert::Equal<HRESULT>(S_FALSE, hr);
248 +
249 + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\DirectoryPlus");
250 + Assert::Equal<HRESULT>(S_FALSE, hr);
251 +
252 + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory\\../Plus");
253 + Assert::Equal<HRESULT>(S_FALSE, hr);
254 +
255 + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory/../Plus");
256 + Assert::Equal<HRESULT>(S_FALSE, hr);
257 +
258 + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\server\\share\\DirectoryPlus");
259 + Assert::Equal<HRESULT>(S_FALSE, hr);
260 +
261 + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\discarded\\..\\server\\share\\Directory\\Plus");
262 + Assert::Equal<HRESULT>(S_FALSE, hr);
263 +
264 + hr = PathDirectoryContainsPath(L"..\\..", L"..\\..\\plus");
265 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
266 +
267 + hr = PathDirectoryContainsPath(L"..\\..", L"\\..\\..\\plus");
268 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
269 +
270 + hr = PathDirectoryContainsPath(L"\\..\\..", L"\\..\\..\\plus");
271 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
272 +
273 + hr = PathDirectoryContainsPath(L"C:..\\..", L"C:..\\..\\plus");
274 + Assert::Equal<HRESULT>(E_INVALIDARG, hr);
275 +
276 + hr = PathDirectoryContainsPath(L"\\\\server\\share\\Directory", L"\\\\server\\share\\Directory\\Plus");
277 + Assert::Equal<HRESULT>(S_OK, hr);
278 +
279 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\directory\\plus");
280 + Assert::Equal<HRESULT>(S_OK, hr);
281 +
282 + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory\\Plus");
283 + Assert::Equal<HRESULT>(S_OK, hr);
284 +
285 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\.\\Directory\\Plus");
286 + Assert::Equal<HRESULT>(S_OK, hr);
287 +
288 + hr = PathDirectoryContainsPath(L"C:\\Directory", L"C:\\Directory/Plus");
289 + Assert::Equal<HRESULT>(S_OK, hr);
290 +
291 + hr = PathDirectoryContainsPath(L"C:\\Directory\\", L"C:\\Directory/Plus");
292 + Assert::Equal<HRESULT>(S_OK, hr);
293 +
294 + hr = PathDirectoryContainsPath(L"\\\\?\\C:\\Directory", L"C:\\Directory\\Plus");
295 + Assert::Equal<HRESULT>(S_OK, hr);
296 + }
297 +
298 [Fact]
299 void PathGetDirectoryTest()
300 {
@@ -103,6 +387,57 @@ namespace DutilTests
387 NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]);
388 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
389 ReleaseNullStrArray(rgsczPaths, cPaths);
390 +
391 + hr = PathGetHierarchyArray(L"c:/foo/bar/bas/a.txt", &rgsczPaths, &cPaths);
392 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path");
393 + Assert::Equal<DWORD>(5, cPaths);
394 + NativeAssert::StringEqual(L"c:/", rgsczPaths[0]);
395 + NativeAssert::StringEqual(L"c:/foo/", rgsczPaths[1]);
396 + NativeAssert::StringEqual(L"c:/foo/bar/", rgsczPaths[2]);
397 + NativeAssert::StringEqual(L"c:/foo/bar/bas/", rgsczPaths[3]);
398 + NativeAssert::StringEqual(L"c:/foo/bar/bas/a.txt", rgsczPaths[4]);
399 + ReleaseNullStrArray(rgsczPaths, cPaths);
400 +
401 + hr = PathGetHierarchyArray(L"c:/foo/bar/bas/", &rgsczPaths, &cPaths);
402 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular directory path");
403 + Assert::Equal<DWORD>(4, cPaths);
404 + NativeAssert::StringEqual(L"c:/", rgsczPaths[0]);
405 + NativeAssert::StringEqual(L"c:/foo/", rgsczPaths[1]);
406 + NativeAssert::StringEqual(L"c:/foo/bar/", rgsczPaths[2]);
407 + NativeAssert::StringEqual(L"c:/foo/bar/bas/", rgsczPaths[3]);
408 + ReleaseNullStrArray(rgsczPaths, cPaths);
409 +
410 + hr = PathGetHierarchyArray(L"//server/share/subdir/file.txt", &rgsczPaths, &cPaths);
411 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC file path");
412 + Assert::Equal<DWORD>(3, cPaths);
413 + NativeAssert::StringEqual(L"//server/share/", rgsczPaths[0]);
414 + NativeAssert::StringEqual(L"//server/share/subdir/", rgsczPaths[1]);
415 + NativeAssert::StringEqual(L"//server/share/subdir/file.txt", rgsczPaths[2]);
416 + ReleaseNullStrArray(rgsczPaths, cPaths);
417 +
418 + hr = PathGetHierarchyArray(L"//server/share/subdir/", &rgsczPaths, &cPaths);
419 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
420 + Assert::Equal<DWORD>(2, cPaths);
421 + NativeAssert::StringEqual(L"//server/share/", rgsczPaths[0]);
422 + NativeAssert::StringEqual(L"//server/share/subdir/", rgsczPaths[1]);
423 + ReleaseNullStrArray(rgsczPaths, cPaths);
424 +
425 + hr = PathGetHierarchyArray(L"Software/Microsoft/Windows/ValueName", &rgsczPaths, &cPaths);
426 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
427 + Assert::Equal<DWORD>(4, cPaths);
428 + NativeAssert::StringEqual(L"Software/", rgsczPaths[0]);
429 + NativeAssert::StringEqual(L"Software/Microsoft/", rgsczPaths[1]);
430 + NativeAssert::StringEqual(L"Software/Microsoft/Windows/", rgsczPaths[2]);
431 + NativeAssert::StringEqual(L"Software/Microsoft/Windows/ValueName", rgsczPaths[3]);
432 + ReleaseNullStrArray(rgsczPaths, cPaths);
433 +
434 + hr = PathGetHierarchyArray(L"Software/Microsoft/Windows/", &rgsczPaths, &cPaths);
435 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
436 + Assert::Equal<DWORD>(3, cPaths);
437 + NativeAssert::StringEqual(L"Software/", rgsczPaths[0]);
438 + NativeAssert::StringEqual(L"Software/Microsoft/", rgsczPaths[1]);
439 + NativeAssert::StringEqual(L"Software/Microsoft/Windows/", rgsczPaths[2]);
440 + ReleaseNullStrArray(rgsczPaths, cPaths);
441 }
442 finally
443 {
@@ -110,12 +445,66 @@ namespace DutilTests
445 }
446 }
447
448 + [Fact]
449 + void PathNormalizeSlashesFixedTest()
450 + {
451 + HRESULT hr = S_OK;
452 + LPWSTR sczPath = NULL;
453 + LPCWSTR rgwzPaths[54] =
454 + {
455 + L"", L"",
456 + L"\\", L"\\",
457 + L"\\\\", L"\\\\",
458 + L"\\\\\\", L"\\\\\\",
459 + L"\\\\?\\UNC\\", L"\\\\?\\UNC\\",
460 + L"C:\\\\foo2", L"C:\\foo2",
461 + L"\\\\?\\C:\\\\foo2", L"\\\\?\\C:\\foo2",
462 + L"\\\\a\\b\\", L"\\\\a\\b\\",
463 + L"\\\\?\\UNC\\a\\b\\\\c\\", L"\\\\?\\UNC\\a\\b\\c\\",
464 + L"\\\\?\\UNC\\a\\b\\\\", L"\\\\?\\UNC\\a\\b\\",
465 + L"\\\\?\\UNC\\test\\unc\\path\\to\\\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
466 + L"\\\\?\\C:\\\\foo\\\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt",
467 + L"\\??\\C:\\\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt",
468 + L"\\??\\\\C:\\\\foo\\bar.txt", L"\\??\\\\C:\\foo\\bar.txt",
469 + L"/", L"\\",
470 + L"//", L"\\\\",
471 + L"///", L"\\\\\\",
472 + L"//?/UNC/", L"\\\\?\\UNC\\",
473 + L"C://foo2", L"C:\\foo2",
474 + L"//?/C://foo2", L"\\\\?\\C:\\foo2",
475 + L"//a/b/", L"\\\\a\\b\\",
476 + L"//?/UNC/a/b//c/", L"\\\\?\\UNC\\a\\b\\c\\",
477 + L"//?/UNC/a/b//", L"\\\\?\\UNC\\a\\b\\",
478 + L"//?/UNC/test/unc/path/to//something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
479 + L"//?/C://foo//bar.txt", L"\\\\?\\C:\\foo\\bar.txt",
480 + L"/??/C://foo/bar.txt", L"\\??\\C:\\foo\\bar.txt",
481 + L"/??//C://foo/bar.txt", L"\\??\\\\C:\\foo\\bar.txt",
482 + };
483 +
484 + try
485 + {
486 + for (DWORD i = 0; i < countof(rgwzPaths); i += 2)
487 + {
488 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
489 + NativeAssert::Succeeded(hr, "Failed to copy string");
490 +
491 + hr = PathFixedNormalizeSlashes(sczPath);
492 + NativeAssert::Succeeded(hr, "PathNormalizeSlashes: {0}", rgwzPaths[i]);
493 + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
494 + }
495 + }
496 + finally
497 + {
498 + ReleaseStr(sczPath);
499 + }
500 + }
501 +
502 [Fact]
503 void PathPrefixTest()
504 {
505 HRESULT hr = S_OK;
506 LPWSTR sczPath = NULL;
118 - LPCWSTR rgwzPaths[12] =
507 + LPCWSTR rgwzPaths[24] =
508 {
509 L"\\\\", L"\\\\?\\UNC\\",
510 L"C:\\\\foo2", L"\\\\?\\C:\\\\foo2",
@@ -123,11 +512,17 @@ namespace DutilTests
512 L"\\\\?\\UNC\\test\\unc\\path\\to\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
513 L"\\\\?\\C:\\foo\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt",
514 L"\\??\\C:\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt",
515 + L"//", L"\\\\?\\UNC\\",
516 + L"C://foo2", L"\\\\?\\C://foo2",
517 + L"//a/b/", L"\\\\?\\UNC\\a/b/",
518 + L"//?/UNC/test/unc/path/to/something", L"//?/UNC/test/unc/path/to/something",
519 + L"//?/C:/foo/bar.txt", L"//?/C:/foo/bar.txt",
520 + L"/??/C:/foo/bar.txt", L"/??/C:/foo/bar.txt",
521 };
522
523 try
524 {
130 - for (DWORD i = 0; i < countof(rgwzPaths) / 2; i += 2)
525 + for (DWORD i = 0; i < countof(rgwzPaths); i += 2)
526 {
527 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
528 NativeAssert::Succeeded(hr, "Failed to copy string");
@@ -148,16 +543,20 @@ namespace DutilTests
543 {
544 HRESULT hr = S_OK;
545 LPWSTR sczPath = NULL;
151 - LPCWSTR rgwzPaths[8] =
546 + LPCWSTR rgwzPaths[12] =
547 {
548 L"\\",
549 + L"/",
550 L"C:",
551 L"C:foo.txt",
552 L"",
553 L"\\?",
554 + L"/?",
555 L"\\dir",
556 + L"/dir",
557 L"dir",
558 L"dir\\subdir",
559 + L"dir/subdir",
560 };
561
562 try
@@ -180,93 +579,162 @@ namespace DutilTests
579 [Fact]
580 void PathIsRootedAndFullyQualifiedTest()
581 {
582 + HRESULT hr = S_OK;
583 + LPWSTR sczPath = NULL;
584 LPCWSTR rgwzPaths[15] =
585 {
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",
586 + L"//",
587 + L"///",
588 + L"C:/",
589 + L"C://",
590 + L"C:/foo1",
591 + L"C://foo2",
592 + L"//test/unc/path/to/something",
593 + L"//a/b/c/d/e",
594 + L"//a/b/",
595 + L"//a/b",
596 + L"//test/unc",
597 + L"//Server",
598 + L"//Server/Foo.txt",
599 + L"//Server/Share/Foo.txt",
600 + L"//Server/Share/Test/Foo.txt",
601 };
602
202 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
603 + try
604 {
204 - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE);
205 - ValidateRootedPath(rgwzPaths[i], TRUE);
605 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
606 + {
607 + ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE);
608 + ValidateRootedPath(rgwzPaths[i], TRUE);
609 +
610 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
611 + NativeAssert::Succeeded(hr, "Failed to copy string");
612 +
613 + PathFixedReplaceForwardSlashes(sczPath);
614 + ValidateFullyQualifiedPath(sczPath, TRUE, FALSE);
615 + ValidateRootedPath(sczPath, TRUE);
616 + }
617 + }
618 + finally
619 + {
620 + ReleaseStr(sczPath);
621 }
622 }
623
624 [Fact]
625 void PathIsRootedAndFullyQualifiedWithPrefixTest()
626 {
627 + HRESULT hr = S_OK;
628 + LPWSTR sczPath = NULL;
629 LPCWSTR rgwzPaths[6] =
630 {
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",
631 + L"//?/UNC/test/unc/path/to/something",
632 + L"//?/UNC/test/unc",
633 + L"//?/UNC/a/b1",
634 + L"//?/UNC/a/b2/",
635 + L"//?/C:/foo/bar.txt",
636 + L"/??/C:/foo/bar.txt",
637 };
638
222 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
639 + try
640 {
224 - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE);
225 - ValidateRootedPath(rgwzPaths[i], TRUE);
641 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
642 + {
643 + ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE);
644 + ValidateRootedPath(rgwzPaths[i], TRUE);
645 +
646 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
647 + NativeAssert::Succeeded(hr, "Failed to copy string");
648 +
649 + PathFixedReplaceForwardSlashes(sczPath);
650 + ValidateFullyQualifiedPath(sczPath, TRUE, TRUE);
651 + ValidateRootedPath(sczPath, TRUE);
652 + }
653 + }
654 + finally
655 + {
656 + ReleaseStr(sczPath);
657 }
658 }
659
660 [Fact]
661 void PathIsRootedButNotFullyQualifiedTest()
662 {
663 + HRESULT hr = S_OK;
664 + LPWSTR sczPath = NULL;
665 LPCWSTR rgwzPaths[7] =
666 {
234 - L"\\",
667 + L"/",
668 L"a:",
669 L"A:",
670 L"z:",
671 L"Z:",
672 L"C:foo.txt",
240 - L"\\dir",
673 + L"/dir",
674 };
675
243 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
676 + try
677 {
245 - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
246 - ValidateRootedPath(rgwzPaths[i], TRUE);
678 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
679 + {
680 + ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
681 + ValidateRootedPath(rgwzPaths[i], TRUE);
682 +
683 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
684 + NativeAssert::Succeeded(hr, "Failed to copy string");
685 +
686 + PathFixedReplaceForwardSlashes(sczPath);
687 + ValidateFullyQualifiedPath(sczPath, FALSE, FALSE);
688 + ValidateRootedPath(sczPath, TRUE);
689 + }
690 + }
691 + finally
692 + {
693 + ReleaseStr(sczPath);
694 }
695 }
696
697 [Fact]
698 void PathIsNotRootedAndNotFullyQualifiedTest()
699 {
700 + HRESULT hr = S_OK;
701 + LPWSTR sczPath = NULL;
702 LPCWSTR rgwzPaths[9] =
703 {
704 NULL,
705 L"",
706 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
707 + L"dir/subdir",
708 + L"@:/foo", // 064 = @ 065 = A
709 + L"[://", // 091 = [ 090 = Z
710 + L"`:/foo ", // 096 = ` 097 = a
711 + L"{://", // 123 = { 122 = z
712 L"[:",
713 };
714
266 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
715 + try
716 {
268 - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
269 - ValidateRootedPath(rgwzPaths[i], FALSE);
717 + for (DWORD i = 0; i < countof(rgwzPaths); ++i)
718 + {
719 + ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
720 + ValidateRootedPath(rgwzPaths[i], FALSE);
721 +
722 + if (!rgwzPaths[i])
723 + {
724 + continue;
725 + }
726 +
727 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
728 + NativeAssert::Succeeded(hr, "Failed to copy string");
729 +
730 + PathFixedReplaceForwardSlashes(sczPath);
731 + ValidateFullyQualifiedPath(sczPath, FALSE, FALSE);
732 + ValidateRootedPath(sczPath, FALSE);
733 + }
734 + }
735 + finally
736 + {
737 + ReleaseStr(sczPath);
738 }
739 }
740
src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs
+1 -1
@@ -84,7 +84,7 @@ namespace WixToolsetTest.CoreIntegration
84 ? field.AsNullableNumber()?.ToString()
85 : field?.AsString())
86 .ToList();
87 - Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]);
87 + Assert.Equal(@"c\d\e\f.exe", fields[(int)WixBundlePayloadSymbolFields.Name]);
88 }
89 }
90
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs
+1 -1
@@ -1,7 +1,7 @@
1 <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PayloadGroup Id="CanonicalizeName">
4 - <Payload SourceFile="dir\file.ext" Name="a\..\c\.\d.exe" />
4 + <Payload SourceFile="dir\file.ext" Name="a\..\c\.\d/e\\f.exe" />
5 </PayloadGroup>
6 </Fragment>
7 </Wix>