@joebigelow / wix / commits / 6b0f2d97

Add PathSkipPastRoot.

Sean Hall committed Jun 3, 2022 at 17:48 UTC 6b0f2d978504da82070523eb6adb0b59f9812e93
9 files changed +414 -310
src/burn/engine/bundlepackageengine.cpp
+1 -1
@@ -763,7 +763,7 @@ static HRESULT ExecuteBundle(
763
764 if (fPseudoPackage)
765 {
766 - if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL))
766 + if (!PathIsFullyQualified(pPackagePayload->sczFilePath))
767 {
768 ExitWithRootFailure(hr, E_INVALIDSTATE, "Related bundles must have a fully qualified target path.");
769 }
src/burn/engine/exeengine.cpp
+1 -1
@@ -363,7 +363,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
363
364 if (pPackage->Exe.fPseudoPackage && BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE != pPackagePayload->verification)
365 {
366 - if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL))
366 + if (!PathIsFullyQualified(pPackagePayload->sczFilePath))
367 {
368 ExitWithRootFailure(hr, E_INVALIDSTATE, "Pseudo ExePackages must have a fully qualified target path.");
369 }
src/burn/engine/logging.cpp
+4 -2
@@ -134,10 +134,12 @@ extern "C" HRESULT LoggingOpen(
134
135 if (sczPrefixFormatted && *sczPrefixFormatted)
136 {
137 + // Best effort to open default logging.
138 LPCWSTR wzPrefix = sczPrefixFormatted;
139 + LPCWSTR wzPastRoot = PathSkipPastRoot(sczPrefixFormatted, NULL, NULL, NULL);
140
139 - // Best effort to open default logging.
140 - if (PathIsRooted(sczPrefixFormatted))
141 + // If the log path is rooted and has a file component, then use that path as is.
142 + if (wzPastRoot && *wzPastRoot)
143 {
144 hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder);
145 ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted);
src/libs/dutil/WixToolset.DUtil/dirutil.cpp
+15 -5
@@ -364,17 +364,27 @@ extern "C" DWORD DAPI DirDeleteEmptyDirectoriesToRoot(
364 __in DWORD /*dwFlags*/
365 )
366 {
367 + HRESULT hr = S_OK;
368 DWORD cDeletedDirs = 0;
369 LPWSTR sczPath = NULL;
370 + LPCWSTR wzPastRoot = NULL;
371 + SIZE_T cchRoot = 0;
372 +
373 + // Make sure the path is normalized and prefixed.
374 + hr = PathExpand(&sczPath, wzPath, PATH_EXPAND_FULLPATH);
375 + DirExitOnFailure(hr, "Failed to get full path for: %ls", wzPath);
376 +
377 + wzPastRoot = PathSkipPastRoot(sczPath, NULL, NULL, NULL);
378 + DirExitOnNull(wzPastRoot, hr, E_INVALIDARG, "Full path was not rooted: %ls", sczPath);
379
370 - while (wzPath && *wzPath && ::RemoveDirectoryW(wzPath))
380 + cchRoot = wzPastRoot - sczPath;
381 +
382 + while (sczPath && sczPath[cchRoot] && ::RemoveDirectoryW(sczPath))
383 {
384 ++cDeletedDirs;
385
374 - HRESULT hr = PathGetParentPath(wzPath, &sczPath);
375 - DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", wzPath);
376 -
377 - wzPath = sczPath;
386 + hr = PathGetParentPath(sczPath, &sczPath, &cchRoot);
387 + DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", sczPath);
388 }
389
390 LExit:
src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+43 -12
@@ -8,8 +8,8 @@ extern "C" {
8
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,
11 + // Always prefix fully qualified paths with the extended path prefix (\\?\).
12 + PATH_CANONICALIZE_APPEND_EXTENDED_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.
@@ -22,6 +22,14 @@ typedef enum _PATH_EXPAND
22 PATH_EXPAND_FULLPATH = 0x0002,
23 } PATH_EXPAND;
24
25 +typedef enum _PATH_PREFIX
26 +{
27 + // Add prefix even if the path is not longer than MAX_PATH.
28 + PATH_PREFIX_SHORT_PATHS = 0x0001,
29 + // Error with E_INVALIDARG if the path is not fully qualified.
30 + PATH_PREFIX_EXPECT_FULLY_QUALIFIED = 0x0002,
31 +} PATH_PREFIX;
32 +
33
34 /*******************************************************************
35 PathFile - returns a pointer to the file part of the path.
@@ -40,8 +48,9 @@ DAPI_(LPCWSTR) PathExtension(
48
49 /*******************************************************************
50 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.
51 + Calling the function again with the previous result returns the same result.
52 + Returns S_FALSE if the path only contains a file name.
53 + For example, C:\a\b -> C:\a\ -> C:\a\
54 ********************************************************************/
55 DAPI_(HRESULT) PathGetDirectory(
56 __in_z LPCWSTR wzPath,
@@ -49,12 +58,18 @@ DAPI_(HRESULT) PathGetDirectory(
58 );
59
60 /*******************************************************************
52 -PathGetParentPath - extracts the parent directory from a full path.
53 - *psczDirectory is NULL if the path only contains a file name.
61 +PathGetParentPath - extracts the parent directory from a path
62 + ignoring a trailing slash so that when called repeatedly,
63 + it eventually returns the root portion of the path.
64 + *psczDirectory is NULL if the path only contains a file name or
65 + the path only contains the root.
66 + *pcchRoot is the length of the root part of the path.
67 + For example, C:\a\b -> C:\a\ -> C:\ -> NULL
68 ********************************************************************/
69 DAPI_(HRESULT) PathGetParentPath(
70 __in_z LPCWSTR wzPath,
57 - __out_z LPWSTR *psczDirectory
71 + __out_z LPWSTR *psczDirectory,
72 + __out_opt SIZE_T* pcchRoot
73 );
74
75 /*******************************************************************
@@ -78,11 +93,14 @@ DAPI_(HRESULT) PathGetFullPathName(
93 );
94
95 /*******************************************************************
81 - PathPrefix - prefixes a full path with \\?\ or \\?\UNC as
82 - appropriate.
96 + PathPrefix - prefixes a path with \\?\ or \\?\UNC if it doesn't
97 + already have an extended prefix, is longer than MAX_PATH,
98 + and is fully qualified.
99 ********************************************************************/
100 DAPI_(HRESULT) PathPrefix(
85 - __inout LPWSTR *psczFullPath
101 + __inout_z LPWSTR *psczFullPath,
102 + __in SIZE_T cchFullPath,
103 + __in DWORD dwPrefixFlags
104 );
105
106 /*******************************************************************
@@ -203,6 +221,20 @@ DAPI_(HRESULT) PathGetKnownFolder(
221 __out LPWSTR* psczKnownFolder
222 );
223
224 +/*******************************************************************
225 + PathSkipPastRoot - returns a pointer to the first character after
226 + the root portion of the path or NULL if the path has no root.
227 + For example, the pointer will point to the "a" in "after":
228 + C:\after, C:after, \after, \\server\share\after,
229 + \\?\C:\afterroot, \\?\UNC\server\share\after
230 +*******************************************************************/
231 +DAPI_(LPCWSTR) PathSkipPastRoot(
232 + __in_z LPCWSTR wzPath,
233 + __out_opt BOOL* pfHasExtendedPrefix,
234 + __out_opt BOOL* pfFullyQualified,
235 + __out_opt BOOL* pfUNC
236 + );
237 +
238 /*******************************************************************
239 PathIsFullyQualified - returns true if the path is fully qualified; false otherwise.
240 Note that some rooted paths like C:dir are not fully qualified.
@@ -210,8 +242,7 @@ DAPI_(HRESULT) PathGetKnownFolder(
242 For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir.
243 *******************************************************************/
244 DAPI_(BOOL) PathIsFullyQualified(
213 - __in_z LPCWSTR wzPath,
214 - __out_opt BOOL* pfHasLongPathPrefix
245 + __in_z LPCWSTR wzPath
246 );
247
248 /*******************************************************************
src/libs/dutil/WixToolset.DUtil/logutil.cpp
+1 -1
@@ -133,7 +133,7 @@ extern "C" HRESULT DAPI LogOpen(
133 hr = PathConcat(wzDirectory, wzLog, &sczCombined);
134 LoguExitOnFailure(hr, "Failed to combine the log path.");
135
136 - if (!PathIsFullyQualified(sczCombined, NULL))
136 + if (!PathIsFullyQualified(sczCombined))
137 {
138 hr = PathExpand(&LogUtil_sczLogPath, sczCombined, PATH_EXPAND_FULLPATH);
139 LoguExitOnFailure(hr, "Failed to expand the log path.");
src/libs/dutil/WixToolset.DUtil/path2utl.cpp
+18 -35
@@ -64,31 +64,12 @@ DAPI_(HRESULT) PathCanonicalizeForComparison(
64
65 if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags)
66 {
67 - if (L'\\' == sczNormalizedPath[0] && (L'\\' == sczNormalizedPath[1] || L'?' == sczNormalizedPath[1]) && L'?' == sczNormalizedPath[2] && L'\\' == sczNormalizedPath[3])
67 + BOOL fUNC = FALSE;
68 + LPCWSTR wzPastRoot = PathSkipPastRoot(sczNormalizedPath, NULL, NULL, &fUNC);
69 + if (fUNC)
70 {
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 - }
71 + wzNormalizedPath = wzPastRoot;
72 + cchUncRootLength = wzPastRoot - sczNormalizedPath;
73 }
74 }
75
@@ -115,22 +96,24 @@ DAPI_(HRESULT) PathCanonicalizeForComparison(
96 if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags)
97 {
98 hr = PathBackslashTerminate(psczCanonicalized);
118 - PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path");
99 + PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path.");
100 }
101
121 - if (PathIsFullyQualified(*psczCanonicalized, &fHasPrefix) && !fHasPrefix &&
122 - (PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX & dwCanonicalizeFlags))
102 + if (PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX & dwCanonicalizeFlags)
103 {
124 - hr = PathPrefix(psczCanonicalized);
125 - PathExitOnFailure(hr, "Failed to ensure the long path prefix on the canonicalized path");
126 -
127 - fHasPrefix = TRUE;
104 + hr = PathPrefix(psczCanonicalized, 0, PATH_PREFIX_SHORT_PATHS);
105 + PathExitOnFailure(hr, "Failed to ensure the extended path prefix on the canonicalized path.");
106 }
107
108 + PathSkipPastRoot(*psczCanonicalized, &fHasPrefix, NULL, NULL);
109 +
110 if (fHasPrefix)
111 {
132 - // Canonicalize \??\ into \\?\.
112 + // Canonicalize prefix into \\?\.
113 + (*psczCanonicalized)[0] = L'\\';
114 (*psczCanonicalized)[1] = L'\\';
115 + (*psczCanonicalized)[2] = L'?';
116 + (*psczCanonicalized)[3] = L'\\';
117 }
118
119 LExit:
@@ -188,7 +171,7 @@ DAPI_(HRESULT) PathCompareCanonicalized(
171 HRESULT hr = S_OK;
172 LPWSTR sczCanonicalized1 = NULL;
173 LPWSTR sczCanonicalized2 = NULL;
191 - DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT;
174 + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT;
175 int nResult = 0;
176
177 if (!wzPath1 || !wzPath2)
@@ -221,7 +204,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath(
204 HRESULT hr = S_OK;
205 LPWSTR sczCanonicalizedDirectory = NULL;
206 LPWSTR sczCanonicalizedPath = NULL;
224 - DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT;
207 + DWORD dwDefaultFlags = PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX | PATH_CANONICALIZE_KEEP_UNC_ROOT;
208 size_t cchDirectory = 0;
209
210 if (!wzDirectory || !*wzDirectory)
@@ -239,7 +222,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath(
222 hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath);
223 PathExitOnFailure(hr, "Failed to canonicalize the path.");
224
242 - if (!PathIsFullyQualified(sczCanonicalizedDirectory, NULL))
225 + if (!PathIsFullyQualified(sczCanonicalizedDirectory))
226 {
227 PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path.");
228 }
src/libs/dutil/WixToolset.DUtil/pathutil.cpp
+178 -118
@@ -120,13 +120,34 @@ LExit:
120
121 DAPI_(HRESULT) PathGetParentPath(
122 __in_z LPCWSTR wzPath,
123 - __out_z LPWSTR *psczParent
123 + __out_z LPWSTR* psczParent,
124 + __out_opt SIZE_T* pcchRoot
125 )
126 {
127 HRESULT hr = S_OK;
128 + LPCWSTR wzPastRoot = NULL;
129 LPCWSTR wzParent = NULL;
130 + LPCWSTR wz = NULL;
131
129 - for (LPCWSTR wz = wzPath; *wz; ++wz)
132 + wzPastRoot = PathSkipPastRoot(wzPath, NULL, NULL, NULL);
133 +
134 + if (pcchRoot)
135 + {
136 + *pcchRoot = !wzPastRoot ? 0 : wzPastRoot - wzPath;
137 + }
138 +
139 + if (wzPastRoot && *wzPastRoot)
140 + {
141 + Assert(wzPastRoot > wzPath);
142 + wz = wzPastRoot;
143 + wzParent = wzPastRoot - 1;
144 + }
145 + else
146 + {
147 + wz = wzPath;
148 + }
149 +
150 + for (; *wz; ++wz)
151 {
152 if (IsPathSeparatorChar(*wz) && wz[1])
153 {
@@ -143,7 +164,7 @@ DAPI_(HRESULT) PathGetParentPath(
164 }
165 else
166 {
146 - ReleaseNullStr(psczParent);
167 + ReleaseNullStr(*psczParent);
168 }
169
170 LExit:
@@ -164,9 +185,8 @@ DAPI_(HRESULT) PathExpand(
185 LPWSTR sczExpandedPath = NULL;
186 SIZE_T cchWritten = 0;
187 DWORD cchExpandedPath = 0;
167 - SIZE_T cbSize = 0;
168 -
188 LPWSTR sczFullPath = NULL;
189 + DWORD dwPrefixFlags = 0;
190
191 //
192 // First, expand any environment variables.
@@ -201,20 +221,7 @@ DAPI_(HRESULT) PathExpand(
221 }
222 }
223
204 - if (MAX_PATH < cch)
205 - {
206 - hr = PathPrefix(&sczExpandedPath); // ignore invald arg from path prefix because this may not be a complete path yet
207 - if (E_INVALIDARG == hr)
208 - {
209 - hr = S_OK;
210 - }
211 - PathExitOnFailure(hr, "Failed to prefix long path after expanding environment variables.");
212 -
213 - hr = StrMaxLength(sczExpandedPath, &cbSize);
214 - PathExitOnFailure(hr, "Failed to get max length of expanded path.");
215 -
216 - cchExpandedPath = (DWORD)min(DWORD_MAX, cbSize);
217 - }
224 + cchWritten = cch;
225 }
226
227 //
@@ -227,11 +234,7 @@ DAPI_(HRESULT) PathExpand(
234 hr = PathGetFullPathName(wzPath, &sczFullPath, NULL, &cchWritten);
235 PathExitOnFailure(hr, "Failed to get full path for string: %ls", wzPath);
236
230 - if (MAX_PATH < cchWritten)
231 - {
232 - hr = PathPrefix(&sczFullPath);
233 - PathExitOnFailure(hr, "Failed to prefix long path after expanding.");
234 - }
237 + dwPrefixFlags |= PATH_PREFIX_EXPECT_FULLY_QUALIFIED;
238 }
239 else
240 {
@@ -239,6 +242,12 @@ DAPI_(HRESULT) PathExpand(
242 sczExpandedPath = NULL;
243 }
244
245 + if (dwResolveFlags)
246 + {
247 + hr = PathPrefix(&sczFullPath, cchWritten, dwPrefixFlags);
248 + PathExitOnFailure(hr, "Failed to prefix path after expanding.");
249 + }
250 +
251 hr = StrAllocString(psczFullPath, sczFullPath ? sczFullPath : wzRelativePath, 0);
252 PathExitOnFailure(hr, "Failed to copy relative path into full path.");
253
@@ -319,29 +328,54 @@ LExit:
328
329
330 DAPI_(HRESULT) PathPrefix(
322 - __inout LPWSTR *psczFullPath
331 + __inout_z LPWSTR* psczFullPath,
332 + __in SIZE_T cchFullPath,
333 + __in DWORD dwPrefixFlags
334 )
335 {
325 - Assert(psczFullPath && *psczFullPath);
336 + Assert(psczFullPath);
337
338 HRESULT hr = S_OK;
339 LPWSTR wzFullPath = *psczFullPath;
340 BOOL fFullyQualified = FALSE;
341 BOOL fHasPrefix = FALSE;
342 + BOOL fUNC = FALSE;
343 SIZE_T cbFullPath = 0;
344
333 - fFullyQualified = PathIsFullyQualified(wzFullPath, &fHasPrefix);
345 + PathSkipPastRoot(wzFullPath, &fHasPrefix, &fFullyQualified, &fUNC);
346 +
347 if (fHasPrefix)
348 {
349 ExitFunction();
350 }
351
339 - if (fFullyQualified && L':' == wzFullPath[1]) // normal path
352 + // The prefix is only allowed on fully qualified paths.
353 + if (!fFullyQualified)
354 {
341 - hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
342 - PathExitOnFailure(hr, "Failed to add prefix to file path.");
355 + if (dwPrefixFlags & PATH_PREFIX_EXPECT_FULLY_QUALIFIED)
356 + {
357 + PathExitWithRootFailure(hr, E_INVALIDARG, "Expected fully qualified path provided to prefix: %ls.", wzFullPath);
358 + }
359 +
360 + ExitFunction();
361 + }
362 +
363 + if (!(dwPrefixFlags & PATH_PREFIX_SHORT_PATHS))
364 + {
365 + // The prefix is not necessary unless the path is longer than MAX_PATH.
366 + if (!cchFullPath)
367 + {
368 + hr = ::StringCchLengthW(wzFullPath, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchFullPath));
369 + PathExitOnFailure(hr, "Failed to get length of path to prefix.");
370 + }
371 +
372 + if (MAX_PATH >= cchFullPath)
373 + {
374 + ExitFunction();
375 + }
376 }
344 - else if (fFullyQualified && IsPathSeparatorChar(wzFullPath[1])) // UNC
377 +
378 + if (fUNC)
379 {
380 hr = StrSize(*psczFullPath, &cbFullPath);
381 PathExitOnFailure(hr, "Failed to get size of full path.");
@@ -352,10 +386,10 @@ DAPI_(HRESULT) PathPrefix(
386 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
387 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
388 }
355 - else
389 + else // must be a normal path
390 {
357 - hr = E_INVALIDARG;
358 - PathExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath);
391 + hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
392 + PathExitOnFailure(hr, "Failed to add prefix to file path.");
393 }
394
395 LExit:
@@ -970,55 +1004,114 @@ LExit:
1004 }
1005
1006
973 -DAPI_(BOOL) PathIsFullyQualified(
974 - __in_z LPCWSTR wzPath,
975 - __out_opt BOOL* pfHasLongPathPrefix
1007 +DAPI_(LPCWSTR) PathSkipPastRoot(
1008 + __in_z_opt LPCWSTR wzPath,
1009 + __out_opt BOOL* pfHasExtendedPrefix,
1010 + __out_opt BOOL* pfFullyQualified,
1011 + __out_opt BOOL* pfUNC
1012 )
1013 {
1014 + LPCWSTR wzPastRoot = NULL;
1015 + BOOL fHasPrefix = FALSE;
1016 BOOL fFullyQualified = FALSE;
979 - BOOL fHasLongPathPrefix = FALSE;
1017 + BOOL fUNC = FALSE;
1018 + DWORD dwRootMissingSlashes = 0;
1019
981 - if (!wzPath || !wzPath[0] || !wzPath[1])
1020 + if (!wzPath || !*wzPath)
1021 {
983 - // There is no way to specify a fully qualified path with one character (or less).
1022 ExitFunction();
1023 }
1024
987 - if (!IsPathSeparatorChar(wzPath[0]))
1025 + if (IsPathSeparatorChar(wzPath[0]))
1026 {
989 - // The only way to specify a fully qualified path that doesn't begin with a slash
990 - // is the drive, colon, slash format (C:\).
991 - if (IsValidDriveChar(wzPath[0]) &&
992 - L':' == wzPath[1] &&
993 - IsPathSeparatorChar(wzPath[2]))
1027 + if (IsPathSeparatorChar(wzPath[1]) && (L'?' == wzPath[2] || L'.' == wzPath[2]) && IsPathSeparatorChar(wzPath[3]) ||
1028 + L'?' == wzPath[1] && L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3]))
1029 {
995 - fFullyQualified = TRUE;
996 - }
1030 + fHasPrefix = TRUE;
1031
998 - ExitFunction();
1032 + if (L'U' == wzPath[4] && L'N' == wzPath[5] && L'C' == wzPath[6] && IsPathSeparatorChar(wzPath[7]))
1033 + {
1034 + fUNC = TRUE;
1035 + wzPastRoot = wzPath + 8;
1036 + dwRootMissingSlashes = 2;
1037 + }
1038 + else
1039 + {
1040 + wzPastRoot = wzPath + 4;
1041 + dwRootMissingSlashes = 1;
1042 + }
1043 + }
1044 + else if (IsPathSeparatorChar(wzPath[1]))
1045 + {
1046 + fUNC = TRUE;
1047 + wzPastRoot = wzPath + 2;
1048 + dwRootMissingSlashes = 2;
1049 + }
1050 }
1051
1001 - // Non-drive fully qualified paths must start with \\ or \?.
1002 - // \??\ is an archaic form of \\?\.
1003 - if (L'?' != wzPath[1] && !IsPathSeparatorChar(wzPath[1]))
1052 + if (dwRootMissingSlashes)
1053 {
1005 - ExitFunction();
1054 + Assert(wzPastRoot);
1055 + fFullyQualified = TRUE;
1056 +
1057 + for (; *wzPastRoot && dwRootMissingSlashes; ++wzPastRoot)
1058 + {
1059 + if (IsPathSeparatorChar(*wzPastRoot))
1060 + {
1061 + --dwRootMissingSlashes;
1062 + }
1063 + }
1064 }
1065 + else
1066 + {
1067 + Assert(!wzPastRoot);
1068
1008 - fFullyQualified = TRUE;
1069 + if (IsPathSeparatorChar(wzPath[0]))
1070 + {
1071 + wzPastRoot = wzPath + 1;
1072 + }
1073 + else if (IsValidDriveChar(wzPath[0]) && wzPath[1] == L':')
1074 + {
1075 + if (IsPathSeparatorChar(wzPath[2]))
1076 + {
1077 + fFullyQualified = TRUE;
1078 + wzPastRoot = wzPath + 3;
1079 + }
1080 + else
1081 + {
1082 + wzPastRoot = wzPath + 2;
1083 + }
1084 + }
1085 + }
1086
1010 - if (L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3]))
1087 +LExit:
1088 + if (pfHasExtendedPrefix)
1089 {
1012 - fHasLongPathPrefix = TRUE;
1090 + *pfHasExtendedPrefix = fHasPrefix;
1091 }
1092
1093 + if (pfFullyQualified)
1094 + {
1095 + *pfFullyQualified = fFullyQualified;
1096 + }
1097
1016 -LExit:
1017 - if (pfHasLongPathPrefix)
1098 + if (pfUNC)
1099 {
1019 - *pfHasLongPathPrefix = fHasLongPathPrefix;
1100 + *pfUNC = fUNC;
1101 }
1102
1103 + return wzPastRoot;
1104 +}
1105 +
1106 +
1107 +DAPI_(BOOL) PathIsFullyQualified(
1108 + __in_z LPCWSTR wzPath
1109 + )
1110 +{
1111 + BOOL fFullyQualified = FALSE;
1112 +
1113 + PathSkipPastRoot(wzPath, NULL, &fFullyQualified, NULL);
1114 +
1115 return fFullyQualified;
1116 }
1117
@@ -1027,9 +1120,7 @@ DAPI_(BOOL) PathIsRooted(
1120 __in_z LPCWSTR wzPath
1121 )
1122 {
1030 - return wzPath &&
1031 - (IsPathSeparatorChar(wzPath[0]) ||
1032 - IsValidDriveChar(wzPath[0]) && wzPath[1] == L':');
1123 + return NULL != PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1124 }
1125
1126
@@ -1118,78 +1209,47 @@ DAPI_(HRESULT) PathGetHierarchyArray(
1209 )
1210 {
1211 HRESULT hr = S_OK;
1121 - LPWSTR sczPathCopy = NULL;
1122 - LPWSTR sczNewPathCopy = NULL;
1123 - DWORD cArraySpacesNeeded = 0;
1124 - size_t cchPath = 0;
1212 + LPCWSTR wz = NULL;
1213 + SIZE_T cch = 0;
1214 + *pcPathArray = 0;
1215
1126 - hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_LENGTH, &cchPath);
1127 - PathExitOnRootFailure(hr, "Failed to get string length of path: %ls", wzPath);
1216 + PathExitOnNull(wzPath, hr, E_INVALIDARG, "wzPath is required.");
1217
1129 - if (!cchPath)
1218 + wz = PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1219 + if (wz)
1220 {
1131 - ExitFunction1(hr = E_INVALIDARG);
1132 - }
1221 + cch = wz - wzPath;
1222
1134 - for (size_t i = 0; i < cchPath; ++i)
1135 - {
1136 - if (IsPathSeparatorChar(wzPath[i]))
1137 - {
1138 - ++cArraySpacesNeeded;
1139 - }
1140 - }
1223 + hr = MemEnsureArraySize(reinterpret_cast<void**>(prgsczPathArray), 1, sizeof(LPWSTR), 5);
1224 + PathExitOnFailure(hr, "Failed to allocate array.");
1225
1142 - if (!IsPathSeparatorChar(wzPath[cchPath - 1]))
1143 - {
1144 - ++cArraySpacesNeeded;
1145 - }
1226 + hr = StrAllocString(*prgsczPathArray, wzPath, cch);
1227 + PathExitOnFailure(hr, "Failed to copy root into array.");
1228
1147 - // 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.
1148 - if (IsPathSeparatorChar(wzPath[0]) && IsPathSeparatorChar(wzPath[1]))
1229 + *pcPathArray += 1;
1230 + }
1231 + else
1232 {
1150 - if (3 > cArraySpacesNeeded)
1151 - {
1152 - ExitFunction1(hr = E_INVALIDARG);
1153 - }
1154 -
1155 - cArraySpacesNeeded -= 3;
1233 + wz = wzPath;
1234 }
1235
1158 - Assert(cArraySpacesNeeded >= 1);
1159 -
1160 - hr = MemEnsureArraySize(reinterpret_cast<void **>(prgsczPathArray), cArraySpacesNeeded, sizeof(LPWSTR), 0);
1161 - PathExitOnFailure(hr, "Failed to allocate array of size %u for parent directories", cArraySpacesNeeded);
1162 - *pcPathArray = cArraySpacesNeeded;
1163 -
1164 - hr = StrAllocString(&sczPathCopy, wzPath, 0);
1165 - PathExitOnFailure(hr, "Failed to allocate copy of original path");
1166 -
1167 - for (DWORD i = 0; i < cArraySpacesNeeded; ++i)
1236 + for (; *wz; ++wz)
1237 {
1169 - hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0);
1170 - PathExitOnFailure(hr, "Failed to copy path");
1238 + ++cch;
1239
1172 - DWORD cchPathCopy = lstrlenW(sczPathCopy);
1173 -
1174 - // 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
1175 - if (IsPathSeparatorChar(wzPath[cchPathCopy - 1]))
1240 + if (IsPathSeparatorChar(*wz) || !wz[1])
1241 {
1177 - sczPathCopy[cchPathCopy - 1] = L'\0';
1178 - }
1179 -
1180 - hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy);
1181 - PathExitOnFailure(hr, "Failed to get directory portion of path");
1242 + hr = MemEnsureArraySizeForNewItems(reinterpret_cast<void**>(prgsczPathArray), *pcPathArray, 1, sizeof(LPWSTR), 5);
1243 + PathExitOnFailure(hr, "Failed to allocate array.");
1244
1183 - ReleaseStr(sczPathCopy);
1184 - sczPathCopy = sczNewPathCopy;
1185 - sczNewPathCopy = NULL;
1186 - }
1245 + hr = StrAllocString(*prgsczPathArray + *pcPathArray, wzPath, cch);
1246 + PathExitOnFailure(hr, "Failed to copy path into array.");
1247
1188 - hr = S_OK;
1248 + *pcPathArray += 1;
1249 + }
1250 + }
1251
1252 LExit:
1191 - ReleaseStr(sczPathCopy);
1192 -
1253 return hr;
1254 }
1255
src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+153 -135
@@ -135,14 +135,22 @@ namespace DutilTests
135 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
136 NativeAssert::StringEqual(L"\\\\server\\share\\", sczCanonicalized);
137
138 - hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
138 + hr = PathCanonicalizeForComparison(L"\\\\.\\UNC\\server\\share\\..\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
139 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
140 - NativeAssert::StringEqual(L"\\\\.\\share\\otherdir\\unc.exe", sczCanonicalized);
140 + NativeAssert::StringEqual(L"\\\\?\\UNC\\server\\share\\unc.exe", sczCanonicalized);
141
142 - hr = PathCanonicalizeForComparison(L"\\\\.\\share\\otherdir\\unc.exe", 0, &sczCanonicalized);
142 + hr = PathCanonicalizeForComparison(L"\\\\..\\share\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
143 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
144 + NativeAssert::StringEqual(L"\\\\..\\share\\otherdir\\unc.exe", sczCanonicalized);
145 +
146 + hr = PathCanonicalizeForComparison(L"\\\\..\\share\\otherdir\\unc.exe", 0, &sczCanonicalized);
147 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
148 NativeAssert::StringEqual(L"\\\\share\\otherdir\\unc.exe", sczCanonicalized);
149
150 + hr = PathCanonicalizeForComparison(L"\\\\.\\UNC\\share\\otherdir\\unc.exe", 0, &sczCanonicalized);
151 + NativeAssert::Succeeded(hr, "Failed to canonicalize path");
152 + NativeAssert::StringEqual(L"\\\\UNC\\share\\otherdir\\unc.exe", sczCanonicalized);
153 +
154 hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
155 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
156 NativeAssert::StringEqual(L"\\\\server\\share\\otherdir\\unc.exe", sczCanonicalized);
@@ -215,7 +223,7 @@ namespace DutilTests
223 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
224 NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized);
225
218 - hr = PathCanonicalizeForComparison(L"C:\\addprefix.exe", PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX, &sczCanonicalized);
226 + hr = PathCanonicalizeForComparison(L"C:\\addprefix.exe", PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX, &sczCanonicalized);
227 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
228 NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized);
229
@@ -312,7 +320,7 @@ namespace DutilTests
320 LPCWSTR rgwzPaths[8] =
321 {
322 L"C:\\simplepath", L"D:\\simplepath",
315 - L"\\\\.\\share\\otherdir\\unc.exe", L"\\\\share\\otherdir\\unc.exe",
323 + L"\\\\..\\share\\otherdir\\unc.exe", L"\\\\share\\otherdir\\unc.exe",
324 L"\\\\server\\.\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe",
325 L"\\\\server\\\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe",
326 };
@@ -541,9 +549,10 @@ namespace DutilTests
549 {
550 HRESULT hr = S_OK;
551 LPWSTR sczPath = NULL;
544 - LPCWSTR rgwzPaths[18] =
552 + LPCWSTR rgwzPaths[20] =
553 {
554 L"C:\\a\\b", L"C:\\a\\",
555 + L"C:\\a\\b\\", L"C:\\a\\b\\",
556 L"C:\\a", L"C:\\",
557 L"C:\\", L"C:\\",
558 L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\",
@@ -569,6 +578,40 @@ namespace DutilTests
578 }
579 }
580
581 + [Fact]
582 + void PathGetParentPathTest()
583 + {
584 + HRESULT hr = S_OK;
585 + LPWSTR sczPath = NULL;
586 + LPCWSTR rgwzPaths[20] =
587 + {
588 + L"C:\\a\\b", L"C:\\a\\",
589 + L"C:\\a\\b\\", L"C:\\a\\",
590 + L"C:\\a", L"C:\\",
591 + L"C:\\", NULL,
592 + L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\",
593 + L"\"C:\\a\\b\\\"c", L"\"C:\\a\\b\\",
594 + L"\"C:\\a\\b\"\\c", L"\"C:\\a\\b\"\\",
595 + L"\"C:\\a\\\"b\\c", L"\"C:\\a\\\"b\\",
596 + L"C:\\a\"\\\"b\\c", L"C:\\a\"\\\"b\\",
597 + L"C:\\a\"\\b\\c\"", L"C:\\a\"\\b\\",
598 + };
599 +
600 + try
601 + {
602 + for (DWORD i = 0; i < countof(rgwzPaths); i += 2)
603 + {
604 + hr = PathGetParentPath(rgwzPaths[i], &sczPath, NULL);
605 + NativeAssert::Succeeded(hr, "PathGetParentPath: {0}", rgwzPaths[i]);
606 + NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
607 + }
608 + }
609 + finally
610 + {
611 + ReleaseStr(sczPath);
612 + }
613 + }
614 +
615 [Fact]
616 void PathGetFullPathNameTest()
617 {
@@ -693,6 +736,12 @@ namespace DutilTests
736 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
737 ReleaseNullStrArray(rgsczPaths, cPaths);
738
739 + hr = PathGetHierarchyArray(L"Software", &rgsczPaths, &cPaths);
740 + NativeAssert::Succeeded(hr, "Failed to get parent directories array for relative path");
741 + Assert::Equal<DWORD>(1, cPaths);
742 + NativeAssert::StringEqual(L"Software", rgsczPaths[0]);
743 + ReleaseNullStrArray(rgsczPaths, cPaths);
744 +
745 hr = PathGetHierarchyArray(L"c:/foo/bar/bas/a.txt", &rgsczPaths, &cPaths);
746 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path");
747 Assert::Equal<DWORD>(5, cPaths);
@@ -832,8 +881,12 @@ namespace DutilTests
881 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
882 NativeAssert::Succeeded(hr, "Failed to copy string");
883
835 - hr = PathPrefix(&sczPath);
884 + hr = PathPrefix(&sczPath, 0, 0);
885 NativeAssert::Succeeded(hr, "PathPrefix: {0}", rgwzPaths[i]);
886 + NativeAssert::StringEqual(rgwzPaths[i], sczPath);
887 +
888 + hr = PathPrefix(&sczPath, 0, PATH_PREFIX_SHORT_PATHS);
889 + NativeAssert::Succeeded(hr, "PathPrefix (SHORT_PATHS): {0}", rgwzPaths[i]);
890 NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
891 }
892 }
@@ -871,7 +924,7 @@ namespace DutilTests
924 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
925 NativeAssert::Succeeded(hr, "Failed to copy string");
926
874 - hr = PathPrefix(&sczPath);
927 + hr = PathPrefix(&sczPath, 0, PATH_PREFIX_EXPECT_FULLY_QUALIFIED);
928 NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath);
929 }
930 }
@@ -884,187 +937,152 @@ namespace DutilTests
937 [Fact]
938 void PathIsRootedAndFullyQualifiedTest()
939 {
887 - HRESULT hr = S_OK;
888 - LPWSTR sczPath = NULL;
889 - LPCWSTR rgwzPaths[15] =
890 - {
891 - L"//",
892 - L"///",
893 - L"C:/",
894 - L"C://",
895 - L"C:/foo1",
896 - L"C://foo2",
897 - L"//test/unc/path/to/something",
898 - L"//a/b/c/d/e",
899 - L"//a/b/",
900 - L"//a/b",
901 - L"//test/unc",
902 - L"//Server",
903 - L"//Server/Foo.txt",
904 - L"//Server/Share/Foo.txt",
905 - L"//Server/Share/Test/Foo.txt",
940 + LPCWSTR rgwzPaths[30] =
941 + {
942 + L"//", L"",
943 + L"///", L"",
944 + L"C:/", L"",
945 + L"C://", L"/",
946 + L"C:/foo1", L"foo1",
947 + L"C://foo2", L"/foo2",
948 + L"//test/unc/path/to/something", L"path/to/something",
949 + L"//a/b/c/d/e", L"c/d/e",
950 + L"//a/b/", L"",
951 + L"//a/b", L"",
952 + L"//test/unc", L"",
953 + L"//Server", L"",
954 + L"//Server/Foo.txt", L"",
955 + L"//Server/Share/Foo.txt", L"Foo.txt",
956 + L"//Server/Share/Test/Foo.txt", L"Test/Foo.txt",
957 };
958
908 - try
909 - {
910 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
911 - {
912 - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE);
913 - ValidateRootedPath(rgwzPaths[i], TRUE);
914 -
915 - hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
916 - NativeAssert::Succeeded(hr, "Failed to copy string");
917 -
918 - PathFixedReplaceForwardSlashes(sczPath);
919 - ValidateFullyQualifiedPath(sczPath, TRUE, FALSE);
920 - ValidateRootedPath(sczPath, TRUE);
921 - }
922 - }
923 - finally
924 - {
925 - ReleaseStr(sczPath);
926 - }
959 + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, TRUE, TRUE);
960 }
961
962 [Fact]
963 void PathIsRootedAndFullyQualifiedWithPrefixTest()
964 {
932 - HRESULT hr = S_OK;
933 - LPWSTR sczPath = NULL;
934 - LPCWSTR rgwzPaths[6] =
965 + LPCWSTR rgwzPaths[12] =
966 {
936 - L"//?/UNC/test/unc/path/to/something",
937 - L"//?/UNC/test/unc",
938 - L"//?/UNC/a/b1",
939 - L"//?/UNC/a/b2/",
940 - L"//?/C:/foo/bar.txt",
941 - L"/??/C:/foo/bar.txt",
967 + L"//?/UNC/test/unc/path/to/something", L"path/to/something",
968 + L"//?/UNC/test/unc", L"",
969 + L"//?/UNC/a/b1", L"",
970 + L"//?/UNC/a/b2/", L"",
971 + L"//?/C:/foo/bar.txt", L"foo/bar.txt",
972 + L"/??/C:/foo/bar.txt", L"foo/bar.txt",
973 };
974
944 - try
945 - {
946 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
947 - {
948 - ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE);
949 - ValidateRootedPath(rgwzPaths[i], TRUE);
950 -
951 - hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
952 - NativeAssert::Succeeded(hr, "Failed to copy string");
953 -
954 - PathFixedReplaceForwardSlashes(sczPath);
955 - ValidateFullyQualifiedPath(sczPath, TRUE, TRUE);
956 - ValidateRootedPath(sczPath, TRUE);
957 - }
958 - }
959 - finally
960 - {
961 - ReleaseStr(sczPath);
962 - }
975 + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), TRUE, TRUE, TRUE);
976 }
977
978 [Fact]
979 void PathIsRootedButNotFullyQualifiedTest()
980 {
968 - HRESULT hr = S_OK;
969 - LPWSTR sczPath = NULL;
970 - LPCWSTR rgwzPaths[7] =
981 + LPCWSTR rgwzPaths[14] =
982 {
972 - L"/",
973 - L"a:",
974 - L"A:",
975 - L"z:",
976 - L"Z:",
977 - L"C:foo.txt",
978 - L"/dir",
983 + L"/", L"",
984 + L"a:", L"",
985 + L"A:", L"",
986 + L"z:", L"",
987 + L"Z:", L"",
988 + L"C:foo.txt", L"foo.txt",
989 + L"/dir", L"dir",
990 };
991
981 - try
982 - {
983 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
984 - {
985 - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
986 - ValidateRootedPath(rgwzPaths[i], TRUE);
987 -
988 - hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
989 - NativeAssert::Succeeded(hr, "Failed to copy string");
990 -
991 - PathFixedReplaceForwardSlashes(sczPath);
992 - ValidateFullyQualifiedPath(sczPath, FALSE, FALSE);
993 - ValidateRootedPath(sczPath, TRUE);
994 - }
995 - }
996 - finally
997 - {
998 - ReleaseStr(sczPath);
999 - }
992 + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, FALSE, TRUE);
993 }
994
995 [Fact]
996 void PathIsNotRootedAndNotFullyQualifiedTest()
997 {
1005 - HRESULT hr = S_OK;
1006 - LPWSTR sczPath = NULL;
1007 - LPCWSTR rgwzPaths[9] =
998 + LPCWSTR rgwzPaths[18] =
999 {
1009 - NULL,
1010 - L"",
1011 - L"dir",
1012 - L"dir/subdir",
1013 - L"@:/foo", // 064 = @ 065 = A
1014 - L"[://", // 091 = [ 090 = Z
1015 - L"`:/foo ", // 096 = ` 097 = a
1016 - L"{://", // 123 = { 122 = z
1017 - L"[:",
1000 + NULL, NULL,
1001 + L"", NULL,
1002 + L"dir", NULL,
1003 + L"dir/subdir", NULL,
1004 + L"@:/foo", NULL, // 064 = @ 065 = A
1005 + L"[://", NULL, // 091 = [ 090 = Z
1006 + L"`:/foo ", NULL, // 096 = ` 097 = a
1007 + L"{://", NULL, // 123 = { 122 = z
1008 + L"[:", NULL,
1009 };
1010
1011 + ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, FALSE, FALSE);
1012 + }
1013 +
1014 + void ValidateSkipPastRoot(LPCWSTR* rgwzPaths, DWORD cPaths, BOOL fExpectedPrefix, BOOL fExpectedFullyQualified, BOOL fExpectedRooted)
1015 + {
1016 + HRESULT hr = S_OK;
1017 + LPWSTR sczPath = NULL;
1018 + LPWSTR sczSkipRootPath = NULL;
1019 + LPCWSTR wzSkipRootPath = NULL;
1020 + BOOL fHasPrefix = FALSE;
1021 +
1022 try
1023 {
1022 - for (DWORD i = 0; i < countof(rgwzPaths); ++i)
1024 + for (DWORD i = 0; i < cPaths; i += 2)
1025 {
1024 - ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
1025 - ValidateRootedPath(rgwzPaths[i], FALSE);
1026 + wzSkipRootPath = PathSkipPastRoot(rgwzPaths[i], &fHasPrefix, NULL, NULL);
1027 + NativeAssert::StringEqual(rgwzPaths[i + 1], wzSkipRootPath);
1028 + ValidateExtendedPrefixPath(rgwzPaths[i], fExpectedPrefix, fHasPrefix);
1029 + ValidateFullyQualifiedPath(rgwzPaths[i], fExpectedFullyQualified);
1030 + ValidateRootedPath(rgwzPaths[i], fExpectedRooted);
1031
1027 - if (!rgwzPaths[i])
1032 + if (rgwzPaths[i])
1033 {
1029 - continue;
1034 + hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
1035 + NativeAssert::Succeeded(hr, "Failed to copy string");
1036 +
1037 + PathFixedReplaceForwardSlashes(sczPath);
1038 }
1039
1032 - hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
1033 - NativeAssert::Succeeded(hr, "Failed to copy string");
1040 + if (rgwzPaths[i + 1])
1041 + {
1042 + hr = StrAllocString(&sczSkipRootPath, rgwzPaths[i + 1], 0);
1043 + NativeAssert::Succeeded(hr, "Failed to copy string");
1044
1035 - PathFixedReplaceForwardSlashes(sczPath);
1036 - ValidateFullyQualifiedPath(sczPath, FALSE, FALSE);
1037 - ValidateRootedPath(sczPath, FALSE);
1045 + PathFixedReplaceForwardSlashes(sczSkipRootPath);
1046 + }
1047 +
1048 + wzSkipRootPath = PathSkipPastRoot(sczPath, &fHasPrefix, NULL, NULL);
1049 + NativeAssert::StringEqual(sczSkipRootPath, wzSkipRootPath);
1050 + ValidateExtendedPrefixPath(sczPath, fExpectedPrefix, fHasPrefix);
1051 + ValidateFullyQualifiedPath(sczPath, fExpectedFullyQualified);
1052 + ValidateRootedPath(sczPath, fExpectedRooted);
1053 }
1054 }
1055 finally
1056 {
1057 ReleaseStr(sczPath);
1058 + ReleaseStr(sczSkipRootPath);
1059 }
1060 }
1061
1046 - void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected, BOOL fExpectedHasPrefix)
1062 + void ValidateExtendedPrefixPath(LPCWSTR wzPath, BOOL fExpected, BOOL fHasExtendedPrefix)
1063 {
1048 - BOOL fHasLongPathPrefix = FALSE;
1049 - BOOL fRooted = PathIsFullyQualified(wzPath, &fHasLongPathPrefix);
1050 - String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
1064 + String^ message = String::Format("HasExtendedPrefix: {0}", gcnew String(wzPath));
1065 if (fExpected)
1066 {
1053 - Assert::True(fRooted, message);
1067 + Assert::True(fHasExtendedPrefix, message);
1068 }
1069 else
1070 {
1057 - Assert::False(fRooted, message);
1071 + Assert::False(fHasExtendedPrefix, message);
1072 }
1073 + }
1074
1060 - message = String::Format("HasLongPathPrefix: {0}", gcnew String(wzPath));
1061 - if (fExpectedHasPrefix)
1075 + void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected)
1076 + {
1077 + BOOL fRooted = PathIsFullyQualified(wzPath);
1078 + String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
1079 + if (fExpected)
1080 {
1063 - Assert::True(fHasLongPathPrefix, message);
1081 + Assert::True(fRooted, message);
1082 }
1083 else
1084 {
1067 - Assert::False(fHasLongPathPrefix, message);
1085 + Assert::False(fRooted, message);
1086 }
1087 }
1088