summaryrefslogtreecommitdiff
path: root/src/libs
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs')
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/pathutil.h19
-rw-r--r--src/libs/dutil/WixToolset.DUtil/pathutil.cpp102
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp199
3 files changed, 300 insertions, 20 deletions
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
index 44d36568..602b4a80 100644
--- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
+++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
@@ -166,10 +166,23 @@ DAPI_(HRESULT) PathGetKnownFolder(
166 ); 166 );
167 167
168/******************************************************************* 168/*******************************************************************
169 PathIsAbsolute - returns true if the path is absolute; false 169 PathIsFullyQualified - returns true if the path is fully qualified; false otherwise.
170 otherwise. 170 Note that some rooted paths like C:dir are not fully qualified.
171 For example, these are all fully qualified: C:\dir, \\server\share, \\?\C:\dir.
172 For example, these are not fully qualified: C:dir, C:, \dir, dir, dir\subdir.
171*******************************************************************/ 173*******************************************************************/
172DAPI_(BOOL) PathIsAbsolute( 174DAPI_(BOOL) PathIsFullyQualified(
175 __in_z LPCWSTR wzPath,
176 __out_opt BOOL* pfHasLongPathPrefix
177 );
178
179/*******************************************************************
180 PathIsRooted - returns true if the path is rooted; false otherwise.
181 Note that some rooted paths like C:dir are not fully qualified.
182 For example, these are all rooted: C:\dir, C:dir, C:, \dir, \\server\share, \\?\C:\dir.
183 For example, these are not rooted: dir, dir\subdir.
184*******************************************************************/
185DAPI_(BOOL) PathIsRooted(
173 __in_z LPCWSTR wzPath 186 __in_z LPCWSTR wzPath
174 ); 187 );
175 188
diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
index 9823779b..314eab85 100644
--- a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
@@ -20,6 +20,10 @@
20 20
21#define PATH_GOOD_ENOUGH 64 21#define PATH_GOOD_ENOUGH 64
22 22
23static BOOL IsValidDriveChar(
24 __in WCHAR wc
25 );
26
23 27
24DAPI_(LPWSTR) PathFile( 28DAPI_(LPWSTR) PathFile(
25 __in_z LPCWSTR wzPath 29 __in_z LPCWSTR wzPath
@@ -272,29 +276,30 @@ DAPI_(HRESULT) PathPrefix(
272 276
273 HRESULT hr = S_OK; 277 HRESULT hr = S_OK;
274 LPWSTR wzFullPath = *psczFullPath; 278 LPWSTR wzFullPath = *psczFullPath;
279 BOOL fFullyQualified = FALSE;
280 BOOL fHasPrefix = FALSE;
275 SIZE_T cbFullPath = 0; 281 SIZE_T cbFullPath = 0;
276 282
277 if (((L'a' <= wzFullPath[0] && L'z' >= wzFullPath[0]) || 283 fFullyQualified = PathIsFullyQualified(wzFullPath, &fHasPrefix);
278 (L'A' <= wzFullPath[0] && L'Z' >= wzFullPath[0])) && 284 if (fHasPrefix)
279 L':' == wzFullPath[1] && 285 {
280 L'\\' == wzFullPath[2]) // normal path 286 ExitFunction();
287 }
288
289 if (fFullyQualified && L':' == wzFullPath[1]) // normal path
281 { 290 {
282 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); 291 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
283 PathExitOnFailure(hr, "Failed to add prefix to file path."); 292 PathExitOnFailure(hr, "Failed to add prefix to file path.");
284 } 293 }
285 else if (L'\\' == wzFullPath[0] && L'\\' == wzFullPath[1]) // UNC 294 else if (fFullyQualified && L'\\' == wzFullPath[1]) // UNC
286 { 295 {
287 // ensure that we're not already prefixed 296 hr = StrSize(*psczFullPath, &cbFullPath);
288 if (!(L'?' == wzFullPath[2] && L'\\' == wzFullPath[3])) 297 PathExitOnFailure(hr, "Failed to get size of full path.");
289 {
290 hr = StrSize(*psczFullPath, &cbFullPath);
291 PathExitOnFailure(hr, "Failed to get size of full path.");
292 298
293 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR)); 299 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
294 300
295 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7); 301 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
296 PathExitOnFailure(hr, "Failed to add prefix to UNC path."); 302 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
297 }
298 } 303 }
299 else 304 else
300 { 305 {
@@ -814,11 +819,66 @@ LExit:
814} 819}
815 820
816 821
817DAPI_(BOOL) PathIsAbsolute( 822DAPI_(BOOL) PathIsFullyQualified(
823 __in_z LPCWSTR wzPath,
824 __out_opt BOOL* pfHasLongPathPrefix
825 )
826{
827 BOOL fFullyQualified = FALSE;
828 BOOL fHasLongPathPrefix = FALSE;
829
830 if (!wzPath || !wzPath[0] || !wzPath[1])
831 {
832 // There is no way to specify a fully qualified path with one character (or less).
833 ExitFunction();
834 }
835
836 if (L'\\' != wzPath[0])
837 {
838 // The only way to specify a fully qualified path that doesn't begin with a slash
839 // is the drive, colon, slash format (C:\).
840 if (IsValidDriveChar(wzPath[0]) &&
841 L':' == wzPath[1] &&
842 L'\\' == wzPath[2])
843 {
844 fFullyQualified = TRUE;
845 }
846
847 ExitFunction();
848 }
849
850 // Non-drive fully qualified paths must start with \\ or \?.
851 // \??\ is an archaic form of \\?\.
852 if (L'?' != wzPath[1] && L'\\' != wzPath[1])
853 {
854 ExitFunction();
855 }
856
857 fFullyQualified = TRUE;
858
859 if (L'?' == wzPath[2] && L'\\' == wzPath[3])
860 {
861 fHasLongPathPrefix = TRUE;
862 }
863
864
865LExit:
866 if (pfHasLongPathPrefix)
867 {
868 *pfHasLongPathPrefix = fHasLongPathPrefix;
869 }
870
871 return fFullyQualified;
872}
873
874
875DAPI_(BOOL) PathIsRooted(
818 __in_z LPCWSTR wzPath 876 __in_z LPCWSTR wzPath
819 ) 877 )
820{ 878{
821 return wzPath && wzPath[0] && wzPath[1] && (wzPath[0] == L'\\') || (wzPath[1] == L':'); 879 return wzPath &&
880 (wzPath[0] == L'\\' ||
881 IsValidDriveChar(wzPath[0]) && wzPath[1] == L':');
822} 882}
823 883
824 884
@@ -847,7 +907,7 @@ DAPI_(HRESULT) PathConcatCch(
847 hr = StrAllocString(psczCombined, wzPath1, cchPath1); 907 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
848 PathExitOnFailure(hr, "Failed to copy just path1 to output."); 908 PathExitOnFailure(hr, "Failed to copy just path1 to output.");
849 } 909 }
850 else if (!wzPath1 || !*wzPath1 || PathIsAbsolute(wzPath2)) 910 else if (!wzPath1 || !*wzPath1 || PathIsRooted(wzPath2))
851 { 911 {
852 hr = StrAllocString(psczCombined, wzPath2, cchPath2); 912 hr = StrAllocString(psczCombined, wzPath2, cchPath2);
853 PathExitOnFailure(hr, "Failed to copy just path2 to output."); 913 PathExitOnFailure(hr, "Failed to copy just path2 to output.");
@@ -1002,3 +1062,11 @@ LExit:
1002 1062
1003 return hr; 1063 return hr;
1004} 1064}
1065
1066static BOOL IsValidDriveChar(
1067 __in WCHAR wc
1068 )
1069{
1070 return L'a' <= wc && L'z' >= wc ||
1071 L'A' <= wc && L'Z' >= wc;
1072}
diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
index 6a05a45c..65856514 100644
--- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
+++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
@@ -109,5 +109,204 @@ namespace DutilTests
109 ReleaseStrArray(rgsczPaths, cPaths); 109 ReleaseStrArray(rgsczPaths, cPaths);
110 } 110 }
111 } 111 }
112
113 [Fact]
114 void PathPrefixTest()
115 {
116 HRESULT hr = S_OK;
117 LPWSTR sczPath = NULL;
118 LPCWSTR rgwzPaths[12] =
119 {
120 L"\\\\", L"\\\\?\\UNC\\",
121 L"C:\\\\foo2", L"\\\\?\\C:\\\\foo2",
122 L"\\\\a\\b\\", L"\\\\?\\UNC\\a\\b\\",
123 L"\\\\?\\UNC\\test\\unc\\path\\to\\something", L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
124 L"\\\\?\\C:\\foo\\bar.txt", L"\\\\?\\C:\\foo\\bar.txt",
125 L"\\??\\C:\\foo\\bar.txt", L"\\??\\C:\\foo\\bar.txt",
126 };
127
128 try
129 {
130 for (DWORD i = 0; i < countof(rgwzPaths) / 2; i += 2)
131 {
132 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
133 NativeAssert::Succeeded(hr, "Failed to copy string");
134
135 hr = PathPrefix(&sczPath);
136 NativeAssert::Succeeded(hr, "PathPrefix: {0}", rgwzPaths[i]);
137 NativeAssert::StringEqual(rgwzPaths[i + 1], sczPath);
138 }
139 }
140 finally
141 {
142 ReleaseStr(sczPath);
143 }
144 }
145
146 [Fact]
147 void PathPrefixFailureTest()
148 {
149 HRESULT hr = S_OK;
150 LPWSTR sczPath = NULL;
151 LPCWSTR rgwzPaths[8] =
152 {
153 L"\\",
154 L"C:",
155 L"C:foo.txt",
156 L"",
157 L"\\?",
158 L"\\dir",
159 L"dir",
160 L"dir\\subdir",
161 };
162
163 try
164 {
165 for (DWORD i = 0; i < countof(rgwzPaths); ++i)
166 {
167 hr = StrAllocString(&sczPath, rgwzPaths[i], 0);
168 NativeAssert::Succeeded(hr, "Failed to copy string");
169
170 hr = PathPrefix(&sczPath);
171 NativeAssert::SpecificReturnCode(E_INVALIDARG, hr, "PathPrefix: {0}, {1}", rgwzPaths[i], sczPath);
172 }
173 }
174 finally
175 {
176 ReleaseStr(sczPath);
177 }
178 }
179
180 [Fact]
181 void PathIsRootedAndFullyQualifiedTest()
182 {
183 LPCWSTR rgwzPaths[15] =
184 {
185 L"\\\\",
186 L"\\\\\\",
187 L"C:\\",
188 L"C:\\\\",
189 L"C:\\foo1",
190 L"C:\\\\foo2",
191 L"\\\\test\\unc\\path\\to\\something",
192 L"\\\\a\\b\\c\\d\\e",
193 L"\\\\a\\b\\",
194 L"\\\\a\\b",
195 L"\\\\test\\unc",
196 L"\\\\Server",
197 L"\\\\Server\\Foo.txt",
198 L"\\\\Server\\Share\\Foo.txt",
199 L"\\\\Server\\Share\\Test\\Foo.txt",
200 };
201
202 for (DWORD i = 0; i < countof(rgwzPaths); ++i)
203 {
204 ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, FALSE);
205 ValidateRootedPath(rgwzPaths[i], TRUE);
206 }
207 }
208
209 [Fact]
210 void PathIsRootedAndFullyQualifiedWithPrefixTest()
211 {
212 LPCWSTR rgwzPaths[6] =
213 {
214 L"\\\\?\\UNC\\test\\unc\\path\\to\\something",
215 L"\\\\?\\UNC\\test\\unc",
216 L"\\\\?\\UNC\\a\\b1",
217 L"\\\\?\\UNC\\a\\b2\\",
218 L"\\\\?\\C:\\foo\\bar.txt",
219 L"\\??\\C:\\foo\\bar.txt",
220 };
221
222 for (DWORD i = 0; i < countof(rgwzPaths); ++i)
223 {
224 ValidateFullyQualifiedPath(rgwzPaths[i], TRUE, TRUE);
225 ValidateRootedPath(rgwzPaths[i], TRUE);
226 }
227 }
228
229 [Fact]
230 void PathIsRootedButNotFullyQualifiedTest()
231 {
232 LPCWSTR rgwzPaths[7] =
233 {
234 L"\\",
235 L"a:",
236 L"A:",
237 L"z:",
238 L"Z:",
239 L"C:foo.txt",
240 L"\\dir",
241 };
242
243 for (DWORD i = 0; i < countof(rgwzPaths); ++i)
244 {
245 ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
246 ValidateRootedPath(rgwzPaths[i], TRUE);
247 }
248 }
249
250 [Fact]
251 void PathIsNotRootedAndNotFullyQualifiedTest()
252 {
253 LPCWSTR rgwzPaths[9] =
254 {
255 NULL,
256 L"",
257 L"dir",
258 L"dir\\subdir",
259 L"@:\\foo", // 064 = @ 065 = A
260 L"[:\\\\", // 091 = [ 090 = Z
261 L"`:\\foo ", // 096 = ` 097 = a
262 L"{:\\\\", // 123 = { 122 = z
263 L"[:",
264 };
265
266 for (DWORD i = 0; i < countof(rgwzPaths); ++i)
267 {
268 ValidateFullyQualifiedPath(rgwzPaths[i], FALSE, FALSE);
269 ValidateRootedPath(rgwzPaths[i], FALSE);
270 }
271 }
272
273 void ValidateFullyQualifiedPath(LPCWSTR wzPath, BOOL fExpected, BOOL fExpectedHasPrefix)
274 {
275 BOOL fHasLongPathPrefix = FALSE;
276 BOOL fRooted = PathIsFullyQualified(wzPath, &fHasLongPathPrefix);
277 String^ message = String::Format("IsFullyQualified: {0}", gcnew String(wzPath));
278 if (fExpected)
279 {
280 Assert::True(fRooted, message);
281 }
282 else
283 {
284 Assert::False(fRooted, message);
285 }
286
287 message = String::Format("HasLongPathPrefix: {0}", gcnew String(wzPath));
288 if (fExpectedHasPrefix)
289 {
290 Assert::True(fHasLongPathPrefix, message);
291 }
292 else
293 {
294 Assert::False(fHasLongPathPrefix, message);
295 }
296 }
297
298 void ValidateRootedPath(LPCWSTR wzPath, BOOL fExpected)
299 {
300 BOOL fRooted = PathIsRooted(wzPath);
301 String^ message = String::Format("IsRooted: {0}", gcnew String(wzPath));
302 if (fExpected)
303 {
304 Assert::True(fRooted, message);
305 }
306 else
307 {
308 Assert::False(fRooted, message);
309 }
310 }
112 }; 311 };
113} 312}