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