aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-06-03 17:48:39 -0500
committerSean Hall <r.sean.hall@gmail.com>2022-06-07 19:44:36 -0500
commit6b0f2d978504da82070523eb6adb0b59f9812e93 (patch)
tree6f0b258519a0f51bf589e4313206b3ffeaa32a41
parentb652e93a460b4b822a01382e5992f96f1d805ffe (diff)
downloadwix-6b0f2d978504da82070523eb6adb0b59f9812e93.tar.gz
wix-6b0f2d978504da82070523eb6adb0b59f9812e93.tar.bz2
wix-6b0f2d978504da82070523eb6adb0b59f9812e93.zip
Add PathSkipPastRoot.
-rw-r--r--src/burn/engine/bundlepackageengine.cpp2
-rw-r--r--src/burn/engine/exeengine.cpp2
-rw-r--r--src/burn/engine/logging.cpp6
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dirutil.cpp20
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/pathutil.h55
-rw-r--r--src/libs/dutil/WixToolset.DUtil/logutil.cpp2
-rw-r--r--src/libs/dutil/WixToolset.DUtil/path2utl.cpp53
-rw-r--r--src/libs/dutil/WixToolset.DUtil/pathutil.cpp296
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp288
9 files changed, 414 insertions, 310 deletions
diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp
index 97861436..8ba8e0d2 100644
--- a/src/burn/engine/bundlepackageengine.cpp
+++ b/src/burn/engine/bundlepackageengine.cpp
@@ -763,7 +763,7 @@ static HRESULT ExecuteBundle(
763 763
764 if (fPseudoPackage) 764 if (fPseudoPackage)
765 { 765 {
766 if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL)) 766 if (!PathIsFullyQualified(pPackagePayload->sczFilePath))
767 { 767 {
768 ExitWithRootFailure(hr, E_INVALIDSTATE, "Related bundles must have a fully qualified target path."); 768 ExitWithRootFailure(hr, E_INVALIDSTATE, "Related bundles must have a fully qualified target path.");
769 } 769 }
diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp
index c3757e92..fb852c78 100644
--- a/src/burn/engine/exeengine.cpp
+++ b/src/burn/engine/exeengine.cpp
@@ -363,7 +363,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
363 363
364 if (pPackage->Exe.fPseudoPackage && BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE != pPackagePayload->verification) 364 if (pPackage->Exe.fPseudoPackage && BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE != pPackagePayload->verification)
365 { 365 {
366 if (!PathIsFullyQualified(pPackagePayload->sczFilePath, NULL)) 366 if (!PathIsFullyQualified(pPackagePayload->sczFilePath))
367 { 367 {
368 ExitWithRootFailure(hr, E_INVALIDSTATE, "Pseudo ExePackages must have a fully qualified target path."); 368 ExitWithRootFailure(hr, E_INVALIDSTATE, "Pseudo ExePackages must have a fully qualified target path.");
369 } 369 }
diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp
index 3a403025..77f5079c 100644
--- a/src/burn/engine/logging.cpp
+++ b/src/burn/engine/logging.cpp
@@ -134,10 +134,12 @@ extern "C" HRESULT LoggingOpen(
134 134
135 if (sczPrefixFormatted && *sczPrefixFormatted) 135 if (sczPrefixFormatted && *sczPrefixFormatted)
136 { 136 {
137 // Best effort to open default logging.
137 LPCWSTR wzPrefix = sczPrefixFormatted; 138 LPCWSTR wzPrefix = sczPrefixFormatted;
139 LPCWSTR wzPastRoot = PathSkipPastRoot(sczPrefixFormatted, NULL, NULL, NULL);
138 140
139 // Best effort to open default logging. 141 // If the log path is rooted and has a file component, then use that path as is.
140 if (PathIsRooted(sczPrefixFormatted)) 142 if (wzPastRoot && *wzPastRoot)
141 { 143 {
142 hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder); 144 hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder);
143 ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted); 145 ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted);
diff --git a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp
index c106a467..94eab9e7 100644
--- a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp
@@ -364,17 +364,27 @@ extern "C" DWORD DAPI DirDeleteEmptyDirectoriesToRoot(
364 __in DWORD /*dwFlags*/ 364 __in DWORD /*dwFlags*/
365 ) 365 )
366{ 366{
367 HRESULT hr = S_OK;
367 DWORD cDeletedDirs = 0; 368 DWORD cDeletedDirs = 0;
368 LPWSTR sczPath = NULL; 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);
369 379
370 while (wzPath && *wzPath && ::RemoveDirectoryW(wzPath)) 380 cchRoot = wzPastRoot - sczPath;
381
382 while (sczPath && sczPath[cchRoot] && ::RemoveDirectoryW(sczPath))
371 { 383 {
372 ++cDeletedDirs; 384 ++cDeletedDirs;
373 385
374 HRESULT hr = PathGetParentPath(wzPath, &sczPath); 386 hr = PathGetParentPath(sczPath, &sczPath, &cchRoot);
375 DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", wzPath); 387 DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", sczPath);
376
377 wzPath = sczPath;
378 } 388 }
379 389
380LExit: 390LExit:
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
index e64c8ef3..727318f2 100644
--- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
@@ -8,8 +8,8 @@ extern "C" {
8 8
9typedef enum _PATH_CANONICALIZE 9typedef enum _PATH_CANONICALIZE
10{ 10{
11 // Always prefix fully qualified paths with the long path prefix (\\?\). 11 // Always prefix fully qualified paths with the extended path prefix (\\?\).
12 PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX = 0x0001, 12 PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX = 0x0001,
13 // Always terminate the path with \. 13 // Always terminate the path with \.
14 PATH_CANONICALIZE_BACKSLASH_TERMINATE = 0x0002, 14 PATH_CANONICALIZE_BACKSLASH_TERMINATE = 0x0002,
15 // Don't collapse . or .. in the \\server\share portion of a UNC path. 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, 22 PATH_EXPAND_FULLPATH = 0x0002,
23} PATH_EXPAND; 23} PATH_EXPAND;
24 24
25typedef 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
25 33
26/******************************************************************* 34/*******************************************************************
27 PathFile - returns a pointer to the file part of the path. 35 PathFile - returns a pointer to the file part of the path.
@@ -40,8 +48,9 @@ DAPI_(LPCWSTR) PathExtension(
40 48
41/******************************************************************* 49/*******************************************************************
42 PathGetDirectory - extracts the directory from a path including the directory separator. 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. 51 Calling the function again with the previous result returns the same result.
44 Returns S_FALSE if the path only contains a file name. 52 Returns S_FALSE if the path only contains a file name.
53 For example, C:\a\b -> C:\a\ -> C:\a\
45********************************************************************/ 54********************************************************************/
46DAPI_(HRESULT) PathGetDirectory( 55DAPI_(HRESULT) PathGetDirectory(
47 __in_z LPCWSTR wzPath, 56 __in_z LPCWSTR wzPath,
@@ -49,12 +58,18 @@ DAPI_(HRESULT) PathGetDirectory(
49 ); 58 );
50 59
51/******************************************************************* 60/*******************************************************************
52PathGetParentPath - extracts the parent directory from a full path. 61PathGetParentPath - extracts the parent directory from a path
53 *psczDirectory is NULL if the path only contains a file name. 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
54********************************************************************/ 68********************************************************************/
55DAPI_(HRESULT) PathGetParentPath( 69DAPI_(HRESULT) PathGetParentPath(
56 __in_z LPCWSTR wzPath, 70 __in_z LPCWSTR wzPath,
57 __out_z LPWSTR *psczDirectory 71 __out_z LPWSTR *psczDirectory,
72 __out_opt SIZE_T* pcchRoot
58 ); 73 );
59 74
60/******************************************************************* 75/*******************************************************************
@@ -78,11 +93,14 @@ DAPI_(HRESULT) PathGetFullPathName(
78 ); 93 );
79 94
80/******************************************************************* 95/*******************************************************************
81 PathPrefix - prefixes a full path with \\?\ or \\?\UNC as 96 PathPrefix - prefixes a path with \\?\ or \\?\UNC if it doesn't
82 appropriate. 97 already have an extended prefix, is longer than MAX_PATH,
98 and is fully qualified.
83********************************************************************/ 99********************************************************************/
84DAPI_(HRESULT) PathPrefix( 100DAPI_(HRESULT) PathPrefix(
85 __inout LPWSTR *psczFullPath 101 __inout_z LPWSTR *psczFullPath,
102 __in SIZE_T cchFullPath,
103 __in DWORD dwPrefixFlags
86 ); 104 );
87 105
88/******************************************************************* 106/*******************************************************************
@@ -204,14 +222,27 @@ DAPI_(HRESULT) PathGetKnownFolder(
204 ); 222 );
205 223
206/******************************************************************* 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*******************************************************************/
231DAPI_(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/*******************************************************************
207 PathIsFullyQualified - returns true if the path is fully qualified; false otherwise. 239 PathIsFullyQualified - returns true if the path is fully qualified; false otherwise.
208 Note that some rooted paths like C:dir are not fully qualified. 240 Note that some rooted paths like C:dir are not fully qualified.
209 For example, these are all fully qualified: C:\dir, C:/dir, \\server\share, \\?\C:\dir. 241 For example, these are all fully qualified: C:\dir, C:/dir, \\server\share, \\?\C:\dir.
210 For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir. 242 For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir.
211*******************************************************************/ 243*******************************************************************/
212DAPI_(BOOL) PathIsFullyQualified( 244DAPI_(BOOL) PathIsFullyQualified(
213 __in_z LPCWSTR wzPath, 245 __in_z LPCWSTR wzPath
214 __out_opt BOOL* pfHasLongPathPrefix
215 ); 246 );
216 247
217/******************************************************************* 248/*******************************************************************
diff --git a/src/libs/dutil/WixToolset.DUtil/logutil.cpp b/src/libs/dutil/WixToolset.DUtil/logutil.cpp
index 94c21374..88a90d8c 100644
--- a/src/libs/dutil/WixToolset.DUtil/logutil.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/logutil.cpp
@@ -133,7 +133,7 @@ extern "C" HRESULT DAPI LogOpen(
133 hr = PathConcat(wzDirectory, wzLog, &sczCombined); 133 hr = PathConcat(wzDirectory, wzLog, &sczCombined);
134 LoguExitOnFailure(hr, "Failed to combine the log path."); 134 LoguExitOnFailure(hr, "Failed to combine the log path.");
135 135
136 if (!PathIsFullyQualified(sczCombined, NULL)) 136 if (!PathIsFullyQualified(sczCombined))
137 { 137 {
138 hr = PathExpand(&LogUtil_sczLogPath, sczCombined, PATH_EXPAND_FULLPATH); 138 hr = PathExpand(&LogUtil_sczLogPath, sczCombined, PATH_EXPAND_FULLPATH);
139 LoguExitOnFailure(hr, "Failed to expand the log path."); 139 LoguExitOnFailure(hr, "Failed to expand the log path.");
diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
index 1957a8c5..9e48f9d5 100644
--- a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
@@ -64,31 +64,12 @@ DAPI_(HRESULT) PathCanonicalizeForComparison(
64 64
65 if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags) 65 if (PATH_CANONICALIZE_KEEP_UNC_ROOT & dwCanonicalizeFlags)
66 { 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)
68 { 70 {
69 if (L'U' == sczNormalizedPath[4] && L'N' == sczNormalizedPath[5] && L'C' == sczNormalizedPath[6] && L'\\' == sczNormalizedPath[7]) 71 wzNormalizedPath = wzPastRoot;
70 { 72 cchUncRootLength = wzPastRoot - sczNormalizedPath;
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 } 73 }
93 } 74 }
94 75
@@ -115,22 +96,24 @@ DAPI_(HRESULT) PathCanonicalizeForComparison(
115 if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags) 96 if (PATH_CANONICALIZE_BACKSLASH_TERMINATE & dwCanonicalizeFlags)
116 { 97 {
117 hr = PathBackslashTerminate(psczCanonicalized); 98 hr = PathBackslashTerminate(psczCanonicalized);
118 PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path"); 99 PathExitOnFailure(hr, "Failed to backslash terminate the canonicalized path.");
119 } 100 }
120 101
121 if (PathIsFullyQualified(*psczCanonicalized, &fHasPrefix) && !fHasPrefix && 102 if (PATH_CANONICALIZE_APPEND_EXTENDED_PATH_PREFIX & dwCanonicalizeFlags)
122 (PATH_CANONICALIZE_APPEND_LONG_PATH_PREFIX & dwCanonicalizeFlags))
123 { 103 {
124 hr = PathPrefix(psczCanonicalized); 104 hr = PathPrefix(psczCanonicalized, 0, PATH_PREFIX_SHORT_PATHS);
125 PathExitOnFailure(hr, "Failed to ensure the long path prefix on the canonicalized path"); 105 PathExitOnFailure(hr, "Failed to ensure the extended path prefix on the canonicalized path.");
126
127 fHasPrefix = TRUE;
128 } 106 }
129 107
108 PathSkipPastRoot(*psczCanonicalized, &fHasPrefix, NULL, NULL);
109
130 if (fHasPrefix) 110 if (fHasPrefix)
131 { 111 {
132 // Canonicalize \??\ into \\?\. 112 // Canonicalize prefix into \\?\.
113 (*psczCanonicalized)[0] = L'\\';
133 (*psczCanonicalized)[1] = L'\\'; 114 (*psczCanonicalized)[1] = L'\\';
115 (*psczCanonicalized)[2] = L'?';
116 (*psczCanonicalized)[3] = L'\\';
134 } 117 }
135 118
136LExit: 119LExit:
@@ -188,7 +171,7 @@ DAPI_(HRESULT) PathCompareCanonicalized(
188 HRESULT hr = S_OK; 171 HRESULT hr = S_OK;
189 LPWSTR sczCanonicalized1 = NULL; 172 LPWSTR sczCanonicalized1 = NULL;
190 LPWSTR sczCanonicalized2 = 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;
192 int nResult = 0; 175 int nResult = 0;
193 176
194 if (!wzPath1 || !wzPath2) 177 if (!wzPath1 || !wzPath2)
@@ -221,7 +204,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath(
221 HRESULT hr = S_OK; 204 HRESULT hr = S_OK;
222 LPWSTR sczCanonicalizedDirectory = NULL; 205 LPWSTR sczCanonicalizedDirectory = NULL;
223 LPWSTR sczCanonicalizedPath = 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;
225 size_t cchDirectory = 0; 208 size_t cchDirectory = 0;
226 209
227 if (!wzDirectory || !*wzDirectory) 210 if (!wzDirectory || !*wzDirectory)
@@ -239,7 +222,7 @@ DAPI_(HRESULT) PathDirectoryContainsPath(
239 hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath); 222 hr = PathCanonicalizeForComparison(wzPath, dwDefaultFlags, &sczCanonicalizedPath);
240 PathExitOnFailure(hr, "Failed to canonicalize the path."); 223 PathExitOnFailure(hr, "Failed to canonicalize the path.");
241 224
242 if (!PathIsFullyQualified(sczCanonicalizedDirectory, NULL)) 225 if (!PathIsFullyQualified(sczCanonicalizedDirectory))
243 { 226 {
244 PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path."); 227 PathExitWithRootFailure(hr, E_INVALIDARG, "wzDirectory must be a fully qualified path.");
245 } 228 }
diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
index abbf4d4b..1ac76626 100644
--- a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
@@ -120,13 +120,34 @@ LExit:
120 120
121DAPI_(HRESULT) PathGetParentPath( 121DAPI_(HRESULT) PathGetParentPath(
122 __in_z LPCWSTR wzPath, 122 __in_z LPCWSTR wzPath,
123 __out_z LPWSTR *psczParent 123 __out_z LPWSTR* psczParent,
124 __out_opt SIZE_T* pcchRoot
124 ) 125 )
125{ 126{
126 HRESULT hr = S_OK; 127 HRESULT hr = S_OK;
128 LPCWSTR wzPastRoot = NULL;
127 LPCWSTR wzParent = NULL; 129 LPCWSTR wzParent = NULL;
130 LPCWSTR wz = NULL;
128 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)
130 { 151 {
131 if (IsPathSeparatorChar(*wz) && wz[1]) 152 if (IsPathSeparatorChar(*wz) && wz[1])
132 { 153 {
@@ -143,7 +164,7 @@ DAPI_(HRESULT) PathGetParentPath(
143 } 164 }
144 else 165 else
145 { 166 {
146 ReleaseNullStr(psczParent); 167 ReleaseNullStr(*psczParent);
147 } 168 }
148 169
149LExit: 170LExit:
@@ -164,9 +185,8 @@ DAPI_(HRESULT) PathExpand(
164 LPWSTR sczExpandedPath = NULL; 185 LPWSTR sczExpandedPath = NULL;
165 SIZE_T cchWritten = 0; 186 SIZE_T cchWritten = 0;
166 DWORD cchExpandedPath = 0; 187 DWORD cchExpandedPath = 0;
167 SIZE_T cbSize = 0;
168
169 LPWSTR sczFullPath = NULL; 188 LPWSTR sczFullPath = NULL;
189 DWORD dwPrefixFlags = 0;
170 190
171 // 191 //
172 // First, expand any environment variables. 192 // First, expand any environment variables.
@@ -201,20 +221,7 @@ DAPI_(HRESULT) PathExpand(
201 } 221 }
202 } 222 }
203 223
204 if (MAX_PATH < cch) 224 cchWritten = 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 }
218 } 225 }
219 226
220 // 227 //
@@ -227,11 +234,7 @@ DAPI_(HRESULT) PathExpand(
227 hr = PathGetFullPathName(wzPath, &sczFullPath, NULL, &cchWritten); 234 hr = PathGetFullPathName(wzPath, &sczFullPath, NULL, &cchWritten);
228 PathExitOnFailure(hr, "Failed to get full path for string: %ls", wzPath); 235 PathExitOnFailure(hr, "Failed to get full path for string: %ls", wzPath);
229 236
230 if (MAX_PATH < cchWritten) 237 dwPrefixFlags |= PATH_PREFIX_EXPECT_FULLY_QUALIFIED;
231 {
232 hr = PathPrefix(&sczFullPath);
233 PathExitOnFailure(hr, "Failed to prefix long path after expanding.");
234 }
235 } 238 }
236 else 239 else
237 { 240 {
@@ -239,6 +242,12 @@ DAPI_(HRESULT) PathExpand(
239 sczExpandedPath = NULL; 242 sczExpandedPath = NULL;
240 } 243 }
241 244
245 if (dwResolveFlags)
246 {
247 hr = PathPrefix(&sczFullPath, cchWritten, dwPrefixFlags);
248 PathExitOnFailure(hr, "Failed to prefix path after expanding.");
249 }
250
242 hr = StrAllocString(psczFullPath, sczFullPath ? sczFullPath : wzRelativePath, 0); 251 hr = StrAllocString(psczFullPath, sczFullPath ? sczFullPath : wzRelativePath, 0);
243 PathExitOnFailure(hr, "Failed to copy relative path into full path."); 252 PathExitOnFailure(hr, "Failed to copy relative path into full path.");
244 253
@@ -319,29 +328,54 @@ LExit:
319 328
320 329
321DAPI_(HRESULT) PathPrefix( 330DAPI_(HRESULT) PathPrefix(
322 __inout LPWSTR *psczFullPath 331 __inout_z LPWSTR* psczFullPath,
332 __in SIZE_T cchFullPath,
333 __in DWORD dwPrefixFlags
323 ) 334 )
324{ 335{
325 Assert(psczFullPath && *psczFullPath); 336 Assert(psczFullPath);
326 337
327 HRESULT hr = S_OK; 338 HRESULT hr = S_OK;
328 LPWSTR wzFullPath = *psczFullPath; 339 LPWSTR wzFullPath = *psczFullPath;
329 BOOL fFullyQualified = FALSE; 340 BOOL fFullyQualified = FALSE;
330 BOOL fHasPrefix = FALSE; 341 BOOL fHasPrefix = FALSE;
342 BOOL fUNC = FALSE;
331 SIZE_T cbFullPath = 0; 343 SIZE_T cbFullPath = 0;
332 344
333 fFullyQualified = PathIsFullyQualified(wzFullPath, &fHasPrefix); 345 PathSkipPastRoot(wzFullPath, &fHasPrefix, &fFullyQualified, &fUNC);
346
334 if (fHasPrefix) 347 if (fHasPrefix)
335 { 348 {
336 ExitFunction(); 349 ExitFunction();
337 } 350 }
338 351
339 if (fFullyQualified && L':' == wzFullPath[1]) // normal path 352 // The prefix is only allowed on fully qualified paths.
353 if (!fFullyQualified)
340 { 354 {
341 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); 355 if (dwPrefixFlags & PATH_PREFIX_EXPECT_FULLY_QUALIFIED)
342 PathExitOnFailure(hr, "Failed to add prefix to file path."); 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 }
343 } 376 }
344 else if (fFullyQualified && IsPathSeparatorChar(wzFullPath[1])) // UNC 377
378 if (fUNC)
345 { 379 {
346 hr = StrSize(*psczFullPath, &cbFullPath); 380 hr = StrSize(*psczFullPath, &cbFullPath);
347 PathExitOnFailure(hr, "Failed to get size of full path."); 381 PathExitOnFailure(hr, "Failed to get size of full path.");
@@ -352,10 +386,10 @@ DAPI_(HRESULT) PathPrefix(
352 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7); 386 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
353 PathExitOnFailure(hr, "Failed to add prefix to UNC path."); 387 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
354 } 388 }
355 else 389 else // must be a normal path
356 { 390 {
357 hr = E_INVALIDARG; 391 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
358 PathExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath); 392 PathExitOnFailure(hr, "Failed to add prefix to file path.");
359 } 393 }
360 394
361LExit: 395LExit:
@@ -970,55 +1004,114 @@ LExit:
970} 1004}
971 1005
972 1006
973DAPI_(BOOL) PathIsFullyQualified( 1007DAPI_(LPCWSTR) PathSkipPastRoot(
974 __in_z LPCWSTR wzPath, 1008 __in_z_opt LPCWSTR wzPath,
975 __out_opt BOOL* pfHasLongPathPrefix 1009 __out_opt BOOL* pfHasExtendedPrefix,
1010 __out_opt BOOL* pfFullyQualified,
1011 __out_opt BOOL* pfUNC
976 ) 1012 )
977{ 1013{
1014 LPCWSTR wzPastRoot = NULL;
1015 BOOL fHasPrefix = FALSE;
978 BOOL fFullyQualified = FALSE; 1016 BOOL fFullyQualified = FALSE;
979 BOOL fHasLongPathPrefix = FALSE; 1017 BOOL fUNC = FALSE;
1018 DWORD dwRootMissingSlashes = 0;
980 1019
981 if (!wzPath || !wzPath[0] || !wzPath[1]) 1020 if (!wzPath || !*wzPath)
982 { 1021 {
983 // There is no way to specify a fully qualified path with one character (or less).
984 ExitFunction(); 1022 ExitFunction();
985 } 1023 }
986 1024
987 if (!IsPathSeparatorChar(wzPath[0])) 1025 if (IsPathSeparatorChar(wzPath[0]))
988 { 1026 {
989 // The only way to specify a fully qualified path that doesn't begin with a slash 1027 if (IsPathSeparatorChar(wzPath[1]) && (L'?' == wzPath[2] || L'.' == wzPath[2]) && IsPathSeparatorChar(wzPath[3]) ||
990 // is the drive, colon, slash format (C:\). 1028 L'?' == wzPath[1] && L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3]))
991 if (IsValidDriveChar(wzPath[0]) &&
992 L':' == wzPath[1] &&
993 IsPathSeparatorChar(wzPath[2]))
994 { 1029 {
995 fFullyQualified = TRUE; 1030 fHasPrefix = TRUE;
996 }
997 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 }
999 } 1050 }
1000 1051
1001 // Non-drive fully qualified paths must start with \\ or \?. 1052 if (dwRootMissingSlashes)
1002 // \??\ is an archaic form of \\?\.
1003 if (L'?' != wzPath[1] && !IsPathSeparatorChar(wzPath[1]))
1004 { 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 }
1006 } 1064 }
1065 else
1066 {
1067 Assert(!wzPastRoot);
1007 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 }
1009 1086
1010 if (L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3])) 1087LExit:
1088 if (pfHasExtendedPrefix)
1011 { 1089 {
1012 fHasLongPathPrefix = TRUE; 1090 *pfHasExtendedPrefix = fHasPrefix;
1013 } 1091 }
1014 1092
1093 if (pfFullyQualified)
1094 {
1095 *pfFullyQualified = fFullyQualified;
1096 }
1015 1097
1016LExit: 1098 if (pfUNC)
1017 if (pfHasLongPathPrefix)
1018 { 1099 {
1019 *pfHasLongPathPrefix = fHasLongPathPrefix; 1100 *pfUNC = fUNC;
1020 } 1101 }
1021 1102
1103 return wzPastRoot;
1104}
1105
1106
1107DAPI_(BOOL) PathIsFullyQualified(
1108 __in_z LPCWSTR wzPath
1109 )
1110{
1111 BOOL fFullyQualified = FALSE;
1112
1113 PathSkipPastRoot(wzPath, NULL, &fFullyQualified, NULL);
1114
1022 return fFullyQualified; 1115 return fFullyQualified;
1023} 1116}
1024 1117
@@ -1027,9 +1120,7 @@ DAPI_(BOOL) PathIsRooted(
1027 __in_z LPCWSTR wzPath 1120 __in_z LPCWSTR wzPath
1028 ) 1121 )
1029{ 1122{
1030 return wzPath && 1123 return NULL != PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1031 (IsPathSeparatorChar(wzPath[0]) ||
1032 IsValidDriveChar(wzPath[0]) && wzPath[1] == L':');
1033} 1124}
1034 1125
1035 1126
@@ -1118,78 +1209,47 @@ DAPI_(HRESULT) PathGetHierarchyArray(
1118 ) 1209 )
1119{ 1210{
1120 HRESULT hr = S_OK; 1211 HRESULT hr = S_OK;
1121 LPWSTR sczPathCopy = NULL; 1212 LPCWSTR wz = NULL;
1122 LPWSTR sczNewPathCopy = NULL; 1213 SIZE_T cch = 0;
1123 DWORD cArraySpacesNeeded = 0; 1214 *pcPathArray = 0;
1124 size_t cchPath = 0;
1125 1215
1126 hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_LENGTH, &cchPath); 1216 PathExitOnNull(wzPath, hr, E_INVALIDARG, "wzPath is required.");
1127 PathExitOnRootFailure(hr, "Failed to get string length of path: %ls", wzPath);
1128 1217
1129 if (!cchPath) 1218 wz = PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1219 if (wz)
1130 { 1220 {
1131 ExitFunction1(hr = E_INVALIDARG); 1221 cch = wz - wzPath;
1132 }
1133 1222
1134 for (size_t i = 0; i < cchPath; ++i) 1223 hr = MemEnsureArraySize(reinterpret_cast<void**>(prgsczPathArray), 1, sizeof(LPWSTR), 5);
1135 { 1224 PathExitOnFailure(hr, "Failed to allocate array.");
1136 if (IsPathSeparatorChar(wzPath[i]))
1137 {
1138 ++cArraySpacesNeeded;
1139 }
1140 }
1141 1225
1142 if (!IsPathSeparatorChar(wzPath[cchPath - 1])) 1226 hr = StrAllocString(*prgsczPathArray, wzPath, cch);
1143 { 1227 PathExitOnFailure(hr, "Failed to copy root into array.");
1144 ++cArraySpacesNeeded;
1145 }
1146 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. 1229 *pcPathArray += 1;
1148 if (IsPathSeparatorChar(wzPath[0]) && IsPathSeparatorChar(wzPath[1])) 1230 }
1231 else
1149 { 1232 {
1150 if (3 > cArraySpacesNeeded) 1233 wz = wzPath;
1151 {
1152 ExitFunction1(hr = E_INVALIDARG);
1153 }
1154
1155 cArraySpacesNeeded -= 3;
1156 } 1234 }
1157 1235
1158 Assert(cArraySpacesNeeded >= 1); 1236 for (; *wz; ++wz)
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)
1168 { 1237 {
1169 hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0); 1238 ++cch;
1170 PathExitOnFailure(hr, "Failed to copy path");
1171 1239
1172 DWORD cchPathCopy = lstrlenW(sczPathCopy); 1240 if (IsPathSeparatorChar(*wz) || !wz[1])
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]))
1176 { 1241 {
1177 sczPathCopy[cchPathCopy - 1] = L'\0'; 1242 hr = MemEnsureArraySizeForNewItems(reinterpret_cast<void**>(prgsczPathArray), *pcPathArray, 1, sizeof(LPWSTR), 5);
1178 } 1243 PathExitOnFailure(hr, "Failed to allocate array.");
1179
1180 hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy);
1181 PathExitOnFailure(hr, "Failed to get directory portion of path");
1182 1244
1183 ReleaseStr(sczPathCopy); 1245 hr = StrAllocString(*prgsczPathArray + *pcPathArray, wzPath, cch);
1184 sczPathCopy = sczNewPathCopy; 1246 PathExitOnFailure(hr, "Failed to copy path into array.");
1185 sczNewPathCopy = NULL;
1186 }
1187 1247
1188 hr = S_OK; 1248 *pcPathArray += 1;
1249 }
1250 }
1189 1251
1190LExit: 1252LExit:
1191 ReleaseStr(sczPathCopy);
1192
1193 return hr; 1253 return hr;
1194} 1254}
1195 1255
diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
index 2505c6bf..d1d304d3 100644
--- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
@@ -135,14 +135,22 @@ namespace DutilTests
135 NativeAssert::Succeeded(hr, "Failed to canonicalize path"); 135 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
136 NativeAssert::StringEqual(L"\\\\server\\share\\", sczCanonicalized); 136 NativeAssert::StringEqual(L"\\\\server\\share\\", sczCanonicalized);
137 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"); 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 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);
143 NativeAssert::Succeeded(hr, "Failed to canonicalize path"); 147 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
144 NativeAssert::StringEqual(L"\\\\share\\otherdir\\unc.exe", sczCanonicalized); 148 NativeAssert::StringEqual(L"\\\\share\\otherdir\\unc.exe", sczCanonicalized);
145 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
146 hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); 154 hr = PathCanonicalizeForComparison(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized);
147 NativeAssert::Succeeded(hr, "Failed to canonicalize path"); 155 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
148 NativeAssert::StringEqual(L"\\\\server\\share\\otherdir\\unc.exe", sczCanonicalized); 156 NativeAssert::StringEqual(L"\\\\server\\share\\otherdir\\unc.exe", sczCanonicalized);
@@ -215,7 +223,7 @@ namespace DutilTests
215 NativeAssert::Succeeded(hr, "Failed to canonicalize path"); 223 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
216 NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized); 224 NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized);
217 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);
219 NativeAssert::Succeeded(hr, "Failed to canonicalize path"); 227 NativeAssert::Succeeded(hr, "Failed to canonicalize path");
220 NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized); 228 NativeAssert::StringEqual(L"\\\\?\\C:\\addprefix.exe", sczCanonicalized);
221 229
@@ -312,7 +320,7 @@ namespace DutilTests
312 LPCWSTR rgwzPaths[8] = 320 LPCWSTR rgwzPaths[8] =
313 { 321 {
314 L"C:\\simplepath", L"D:\\simplepath", 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",
316 L"\\\\server\\.\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe", 324 L"\\\\server\\.\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe",
317 L"\\\\server\\\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe", 325 L"\\\\server\\\\otherdir\\unc.exe", L"\\\\server\\otherdir\\unc.exe",
318 }; 326 };
@@ -541,9 +549,10 @@ namespace DutilTests
541 { 549 {
542 HRESULT hr = S_OK; 550 HRESULT hr = S_OK;
543 LPWSTR sczPath = NULL; 551 LPWSTR sczPath = NULL;
544 LPCWSTR rgwzPaths[18] = 552 LPCWSTR rgwzPaths[20] =
545 { 553 {
546 L"C:\\a\\b", L"C:\\a\\", 554 L"C:\\a\\b", L"C:\\a\\",
555 L"C:\\a\\b\\", L"C:\\a\\b\\",
547 L"C:\\a", L"C:\\", 556 L"C:\\a", L"C:\\",
548 L"C:\\", L"C:\\", 557 L"C:\\", L"C:\\",
549 L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\", 558 L"\"C:\\a\\b\\c\"", L"\"C:\\a\\b\\",
@@ -570,6 +579,40 @@ namespace DutilTests
570 } 579 }
571 580
572 [Fact] 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]
573 void PathGetFullPathNameTest() 616 void PathGetFullPathNameTest()
574 { 617 {
575 HRESULT hr = S_OK; 618 HRESULT hr = S_OK;
@@ -693,6 +736,12 @@ namespace DutilTests
693 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]); 736 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
694 ReleaseNullStrArray(rgsczPaths, cPaths); 737 ReleaseNullStrArray(rgsczPaths, cPaths);
695 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
696 hr = PathGetHierarchyArray(L"c:/foo/bar/bas/a.txt", &rgsczPaths, &cPaths); 745 hr = PathGetHierarchyArray(L"c:/foo/bar/bas/a.txt", &rgsczPaths, &cPaths);
697 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path"); 746 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path");
698 Assert::Equal<DWORD>(5, cPaths); 747 Assert::Equal<DWORD>(5, cPaths);
@@ -832,8 +881,12 @@ namespace DutilTests
832 hr = StrAllocString(&sczPath, rgwzPaths[i], 0); 881 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
833 NativeAssert::Succeeded(hr, "Failed to copy string"); 882 NativeAssert::Succeeded(hr, "Failed to copy string");
834 883
835 hr = PathPrefix(&sczPath); 884 hr = PathPrefix(&sczPath, 0, 0);
836 NativeAssert::Succeeded(hr, "PathPrefix: {0}", rgwzPaths[i]); 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]);
837 NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath); 890 NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
838 } 891 }
839 } 892 }
@@ -871,7 +924,7 @@ namespace DutilTests
871 hr = StrAllocString(&sczPath, rgwzPaths[i], 0); 924 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
872 NativeAssert::Succeeded(hr, "Failed to copy string"); 925 NativeAssert::Succeeded(hr, "Failed to copy string");
873 926
874 hr = PathPrefix(&sczPath); 927 hr = PathPrefix(&sczPath, 0, PATH_PREFIX_EXPECT_FULLY_QUALIFIED);
875 NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath); 928 NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath);
876 } 929 }
877 } 930 }
@@ -884,187 +937,152 @@ namespace DutilTests
884 [Fact] 937 [Fact]
885 void PathIsRootedAndFullyQualifiedTest() 938 void PathIsRootedAndFullyQualifiedTest()
886 { 939 {
887 HRESULT hr = S_OK; 940 LPCWSTR rgwzPaths[30] =
888 LPWSTR sczPath = NULL; 941 {
889 LPCWSTR rgwzPaths[15] = 942 L"//", L"",
890 { 943 L"///", L"",
891 L"//", 944 L"C:/", L"",
892 L"///", 945 L"C://", L"/",
893 L"C:/", 946 L"C:/foo1", L"foo1",
894 L"C://", 947 L"C://foo2", L"/foo2",
895 L"C:/foo1", 948 L"//test/unc/path/to/something", L"path/to/something",
896 L"C://foo2", 949 L"//a/b/c/d/e", L"c/d/e",
897 L"//test/unc/path/to/something", 950 L"//a/b/", L"",
898 L"//a/b/c/d/e", 951 L"//a/b", L"",
899 L"//a/b/", 952 L"//test/unc", L"",
900 L"//a/b", 953 L"//Server", L"",
901 L"//test/unc", 954 L"//Server/Foo.txt", L"",
902 L"//Server", 955 L"//Server/Share/Foo.txt", L"Foo.txt",
903 L"//Server/Foo.txt", 956 L"//Server/Share/Test/Foo.txt", L"Test/Foo.txt",
904 L"//Server/Share/Foo.txt",
905 L"//Server/Share/Test/Foo.txt",
906 }; 957 };
907 958
908 try 959 ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, TRUE, TRUE);
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 }
927 } 960 }
928 961
929 [Fact] 962 [Fact]
930 void PathIsRootedAndFullyQualifiedWithPrefixTest() 963 void PathIsRootedAndFullyQualifiedWithPrefixTest()
931 { 964 {
932 HRESULT hr = S_OK; 965 LPCWSTR rgwzPaths[12] =
933 LPWSTR sczPath = NULL;
934 LPCWSTR rgwzPaths[6] =
935 { 966 {
936 L"//?/UNC/test/unc/path/to/something", 967 L"//?/UNC/test/unc/path/to/something", L"path/to/something",
937 L"//?/UNC/test/unc", 968 L"//?/UNC/test/unc", L"",
938 L"//?/UNC/a/b1", 969 L"//?/UNC/a/b1", L"",
939 L"//?/UNC/a/b2/", 970 L"//?/UNC/a/b2/", L"",
940 L"//?/C:/foo/bar.txt", 971 L"//?/C:/foo/bar.txt", L"foo/bar.txt",
941 L"/??/C:/foo/bar.txt", 972 L"/??/C:/foo/bar.txt", L"foo/bar.txt",
942 }; 973 };
943 974
944 try 975 ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), TRUE, TRUE, TRUE);
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 }
963 } 976 }
964 977
965 [Fact] 978 [Fact]
966 void PathIsRootedButNotFullyQualifiedTest() 979 void PathIsRootedButNotFullyQualifiedTest()
967 { 980 {
968 HRESULT hr = S_OK; 981 LPCWSTR rgwzPaths[14] =
969 LPWSTR sczPath = NULL;
970 LPCWSTR rgwzPaths[7] =
971 { 982 {
972 L"/", 983 L"/", L"",
973 L"a:", 984 L"a:", L"",
974 L"A:", 985 L"A:", L"",
975 L"z:", 986 L"z:", L"",
976 L"Z:", 987 L"Z:", L"",
977 L"C:foo.txt", 988 L"C:foo.txt", L"foo.txt",
978 L"/dir", 989 L"/dir", L"dir",
979 }; 990 };
980 991
981 try 992 ValidateSkipPastRoot(rgwzPaths, countof(rgwzPaths), FALSE, FALSE, TRUE);
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 }
1000 } 993 }
1001 994
1002 [Fact] 995 [Fact]
1003 void PathIsNotRootedAndNotFullyQualifiedTest() 996 void PathIsNotRootedAndNotFullyQualifiedTest()
1004 { 997 {
1005 HRESULT hr = S_OK; 998 LPCWSTR rgwzPaths[18] =
1006 LPWSTR sczPath = NULL;
1007 LPCWSTR rgwzPaths[9] =
1008 { 999 {
1009 NULL, 1000 NULL, NULL,
1010 L"", 1001 L"", NULL,
1011 L"dir", 1002 L"dir", NULL,
1012 L"dir/subdir", 1003 L"dir/subdir", NULL,
1013 L"@:/foo", // 064 = @ 065 = A 1004 L"@:/foo", NULL, // 064 = @ 065 = A
1014 L"[://", // 091 = [ 090 = Z 1005 L"[://", NULL, // 091 = [ 090 = Z
1015 L"`:/foo ", // 096 = ` 097 = a 1006 L"`:/foo ", NULL, // 096 = ` 097 = a
1016 L"{://", // 123 = { 122 = z 1007 L"{://", NULL, // 123 = { 122 = z
1017 L"[:", 1008 L"[:", NULL,
1018 }; 1009 };
1019 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
1020 try 1022 try
1021 { 1023 {
1022 for (DWORD i = 0; i < countof(rgwzPaths); ++i) 1024 for (DWORD i = 0; i < cPaths; i += 2)
1023 { 1025 {
1024 ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE); 1026 wzSkipRootPath = PathSkipPastRoot(rgwzPaths[i], &fHasPrefix, NULL, NULL);
1025 ValidateRootedPath(rgwzPaths[i], FALSE); 1027 NativeAssert::StringEqual(rgwzPaths[i + 1], wzSkipRootPath);
1028 ValidateExtendedPrefixPath(rgwzPaths[i], fExpectedPrefix, fHasPrefix);
1029 ValidateFullyQualifiedPath(rgwzPaths[i], fExpectedFullyQualified);
1030 ValidateRootedPath(rgwzPaths[i], fExpectedRooted);
1026 1031
1027 if (!rgwzPaths[i]) 1032 if (rgwzPaths[i])
1028 { 1033 {
1029 continue; 1034 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
1035 NativeAssert::Succeeded(hr, "Failed to copy string");
1036
1037 PathFixedReplaceForwardSlashes(sczPath);
1030 } 1038 }
1031 1039
1032 hr = StrAllocString(&sczPath, rgwzPaths[i], 0); 1040 if (rgwzPaths[i + 1])
1033 NativeAssert::Succeeded(hr, "Failed to copy string"); 1041 {
1042 hr = StrAllocString(&sczSkipRootPath, rgwzPaths[i + 1], 0);
1043 NativeAssert::Succeeded(hr, "Failed to copy string");
1034 1044
1035 PathFixedReplaceForwardSlashes(sczPath); 1045 PathFixedReplaceForwardSlashes(sczSkipRootPath);
1036 ValidateFullyQualifiedPath(sczPath, FALSE, FALSE); 1046 }
1037 ValidateRootedPath(sczPath, FALSE); 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);
1038 } 1053 }
1039 } 1054 }
1040 finally 1055 finally
1041 { 1056 {
1042 ReleaseStr(sczPath); 1057 ReleaseStr(sczPath);
1058 ReleaseStr(sczSkipRootPath);
1043 } 1059 }
1044 } 1060 }
1045 1061
1046 void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected, BOOL fExpectedHasPrefix) 1062 void ValidateExtendedPrefixPath(LPCWSTR wzPath, BOOL fExpected, BOOL fHasExtendedPrefix)
1047 { 1063 {
1048 BOOL fHasLongPathPrefix = FALSE; 1064 String^ message = String::Format("HasExtendedPrefix: {0}", gcnew String(wzPath));
1049 BOOL fRooted = PathIsFullyQualified(wzPath, &fHasLongPathPrefix);
1050 String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
1051 if (fExpected) 1065 if (fExpected)
1052 { 1066 {
1053 Assert::True(fRooted, message); 1067 Assert::True(fHasExtendedPrefix, message);
1054 } 1068 }
1055 else 1069 else
1056 { 1070 {
1057 Assert::False(fRooted, message); 1071 Assert::False(fHasExtendedPrefix, message);
1058 } 1072 }
1073 }
1059 1074
1060 message = String::Format("HasLongPathPrefix: {0}", gcnew String(wzPath)); 1075 void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected)
1061 if (fExpectedHasPrefix) 1076 {
1077 BOOL fRooted = PathIsFullyQualified(wzPath);
1078 String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
1079 if (fExpected)
1062 { 1080 {
1063 Assert::True(fHasLongPathPrefix, message); 1081 Assert::True(fRooted, message);
1064 } 1082 }
1065 else 1083 else
1066 { 1084 {
1067 Assert::False(fHasLongPathPrefix, message); 1085 Assert::False(fRooted, message);
1068 } 1086 }
1069 } 1087 }
1070 1088