1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//
5
6//
7// ===========================================================================
8// File: path.cpp
9//
10// Path APIs ported from shlwapi (especially for Fusion)
11// ===========================================================================
12
13#include "common.h"
14#include "strsafe.h"
15
16
17#define CH_SLASH W('/')
18#define CH_WHACK W('\\')
19
20//
21// Inline function to check for a double-backslash at the
22// beginning of a string
23//
24
25static __inline BOOL DBL_BSLASH(LPCWSTR psz)
26{
27 return (psz[0] == W('\\') && psz[1] == W('\\'));
28}
29
30//
31// Inline function to check for a path separator character.
32//
33
34static __inline BOOL IsPathSeparator(WCHAR ch)
35{
36 return (ch == CH_SLASH || ch == CH_WHACK);
37}
38
39__inline BOOL ChrCmpW_inline(WCHAR w1, WCHAR wMatch)
40{
41 return(!(w1 == wMatch));
42}
43
44STDAPI_(LPWSTR) StrRChrW(LPCWSTR lpStart, LPCWSTR lpEnd, WCHAR wMatch)
45{
46 LPCWSTR lpFound = NULL;
47
48 RIPMSG(lpStart && IS_VALID_STRING_PTRW(lpStart, -1), "StrRChrW: caller passed bad lpStart");
49 RIPMSG(!lpEnd || lpEnd <= lpStart + wcslen(lpStart), "StrRChrW: caller passed bad lpEnd");
50 // don't need to check for NULL lpStart
51
52 if (!lpEnd)
53 lpEnd = lpStart + wcslen(lpStart);
54
55 for ( ; lpStart < lpEnd; lpStart++)
56 {
57 if (!ChrCmpW_inline(*lpStart, wMatch))
58 lpFound = lpStart;
59 }
60 return ((LPWSTR)lpFound);
61}
62
63
64// check if a path is a root
65//
66// returns:
67// TRUE
68// "\" "X:\" "\\" "\\foo" "\\foo\bar"
69//
70// FALSE for others including "\\foo\bar\" (!)
71//
72STDAPI_(BOOL) PathIsRootW(LPCWSTR pPath)
73{
74 RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathIsRoot: caller passed bad pPath");
75
76 if (!pPath || !*pPath)
77 {
78 return FALSE;
79 }
80
81 if (!lstrcmpiW(pPath + 1, W(":\\")))
82 {
83 return TRUE; // "X:\" case
84 }
85
86 if (IsPathSeparator(*pPath) && (*(pPath + 1) == 0))
87 {
88 return TRUE; // "/" or "\" case
89 }
90
91 if (DBL_BSLASH(pPath)) // smells like UNC name
92 {
93 LPCWSTR p;
94 int cBackslashes = 0;
95
96 for (p = pPath + 2; *p; p++)
97 {
98 if (*p == W('\\'))
99 {
100 //
101 // return FALSE for "\\server\share\dir"
102 // so just check if there is more than one slash
103 //
104 // "\\server\" without a share name causes
105 // problems for WNet APIs. we should return
106 // FALSE for this as well
107 //
108 if ((++cBackslashes > 1) || !*(p+1))
109 return FALSE;
110 }
111 }
112 // end of string with only 1 more backslash
113 // must be a bare UNC, which looks like a root dir
114 return TRUE;
115 }
116 return FALSE;
117}
118
119/*
120// rips the last part of the path off including the backslash
121// C:\foo -> C:\
122// C:\foo\bar -> C:\foo
123// C:\foo\ -> C:\foo
124// \\x\y\x -> \\x\y
125// \\x\y -> \\x
126// \\x -> \\ (Just the double slash!)
127// \foo -> \ (Just the slash!)
128//
129// in/out:
130// pFile fully qualified path name
131// returns:
132// TRUE we stripped something
133// FALSE didn't strip anything (root directory case)
134//
135*/
136STDAPI_(BOOL) PathRemoveFileSpecW(LPWSTR pFile)
137{
138 RIPMSG(pFile && IS_VALID_STRING_PTR(pFile, -1), "PathRemoveFileSpec: caller passed bad pFile");
139
140 if (pFile)
141 {
142 LPWSTR pT;
143 LPWSTR pT2 = pFile;
144
145 for (pT = pT2; *pT2; pT2++)
146 {
147 if (IsPathSeparator(*pT2))
148 {
149 pT = pT2; // last "\" found, (we will strip here)
150 }
151 else if (*pT2 == W(':')) // skip ":\" so we don't
152 {
153 if (IsPathSeparator(pT2[1])) // strip the "\" from "C:\"
154 {
155 pT2++;
156 }
157 pT = pT2 + 1;
158 }
159 }
160
161 if (*pT == 0)
162 {
163 // didn't strip anything
164 return FALSE;
165 }
166 else if (((pT == pFile) && IsPathSeparator(*pT)) || // is it the "\foo" case?
167 ((pT == pFile+1) && (*pT == CH_WHACK && *pFile == CH_WHACK))) // or the "\\bar" case?
168 {
169 // Is it just a '\'?
170 if (*(pT+1) != W('\0'))
171 {
172 // Nope.
173 *(pT+1) = W('\0');
174 return TRUE; // stripped something
175 }
176 else
177 {
178 // Yep.
179 return FALSE;
180 }
181 }
182 else
183 {
184 *pT = 0;
185 return TRUE; // stripped something
186 }
187 }
188 return FALSE;
189}
190
191//
192// Return a pointer to the end of the next path component in the string.
193// ie return a pointer to the next backslash or terminating NULL.
194//
195LPCWSTR GetPCEnd(LPCWSTR lpszStart)
196{
197 LPCWSTR lpszEnd;
198 LPCWSTR lpszSlash;
199
200 lpszEnd = StrChr(lpszStart, CH_WHACK);
201 lpszSlash = StrChr(lpszStart, CH_SLASH);
202 if ((lpszSlash && lpszSlash < lpszEnd) ||
203 !lpszEnd)
204 {
205 lpszEnd = lpszSlash;
206 }
207 if (!lpszEnd)
208 {
209 lpszEnd = lpszStart + wcslen(lpszStart);
210 }
211
212 return lpszEnd;
213}
214
215//
216// Given a pointer to the end of a path component, return a pointer to
217// its begining.
218// ie return a pointer to the previous backslash (or start of the string).
219//
220LPCWSTR PCStart(LPCWSTR lpszStart, LPCWSTR lpszEnd)
221{
222 LPCWSTR lpszBegin = StrRChrW(lpszStart, lpszEnd, CH_WHACK);
223 LPCWSTR lpszSlash = StrRChrW(lpszStart, lpszEnd, CH_SLASH);
224 if (lpszSlash > lpszBegin)
225 {
226 lpszBegin = lpszSlash;
227 }
228 if (!lpszBegin)
229 {
230 lpszBegin = lpszStart;
231 }
232 return lpszBegin;
233}
234
235//
236// Fix up a few special cases so that things roughly make sense.
237//
238void NearRootFixups(LPWSTR lpszPath, BOOL fUNC)
239{
240 // Check for empty path.
241 if (lpszPath[0] == W('\0'))
242 {
243 // Fix up.
244#ifndef PLATFORM_UNIX
245 lpszPath[0] = CH_WHACK;
246#else
247 lpszPath[0] = CH_SLASH;
248#endif
249 lpszPath[1] = W('\0');
250 }
251 // Check for missing slash.
252 if (lpszPath[1] == W(':') && lpszPath[2] == W('\0'))
253 {
254 // Fix up.
255 lpszPath[2] = W('\\');
256 lpszPath[3] = W('\0');
257 }
258 // Check for UNC root.
259 if (fUNC && lpszPath[0] == W('\\') && lpszPath[1] == W('\0'))
260 {
261 // Fix up.
262 //lpszPath[0] = W('\\'); // already checked in if guard
263 lpszPath[1] = W('\\');
264 lpszPath[2] = W('\0');
265 }
266}
267
268/*----------------------------------------------------------
269Purpose: Canonicalize a path.
270
271Returns:
272Cond: --
273*/
274STDAPI_(BOOL) PathCanonicalizeW(LPWSTR lpszDst, LPCWSTR lpszSrc)
275{
276 LPCWSTR lpchSrc;
277 LPCWSTR lpchPCEnd; // Pointer to end of path component.
278 LPWSTR lpchDst;
279 BOOL fUNC;
280 int cchPC;
281
282 RIPMSG(lpszDst && IS_VALID_WRITE_BUFFER(lpszDst, WCHAR, MAX_PATH), "PathCanonicalize: caller passed bad lpszDst");
283 RIPMSG(lpszSrc && IS_VALID_STRING_PTR(lpszSrc, -1), "PathCanonicalize: caller passed bad lpszSrc");
284 RIPMSG(lpszDst != lpszSrc, "PathCanonicalize: caller passed the same buffer for lpszDst and lpszSrc");
285
286 if (!lpszDst || !lpszSrc)
287 {
288 SetLastError(ERROR_INVALID_PARAMETER);
289 return FALSE;
290 }
291
292 *lpszDst = W('\0');
293
294 fUNC = PathIsUNCW(lpszSrc); // Check for UNCness.
295
296 // Init.
297 lpchSrc = lpszSrc;
298 lpchDst = lpszDst;
299
300 while (*lpchSrc)
301 {
302 lpchPCEnd = GetPCEnd(lpchSrc);
303 cchPC = (int) (lpchPCEnd - lpchSrc)+1;
304
305 if (cchPC == 1 && IsPathSeparator(*lpchSrc)) // Check for slashes.
306 {
307 // Just copy them.
308#ifndef PLATFORM_UNIX
309 *lpchDst = CH_WHACK;
310#else
311 *lpchDst = CH_SLASH;
312#endif
313 lpchDst++;
314 lpchSrc++;
315 }
316 else if (cchPC == 2 && *lpchSrc == W('.')) // Check for dots.
317 {
318 // Skip it...
319 // Are we at the end?
320 if (*(lpchSrc+1) == W('\0'))
321 {
322 lpchSrc++;
323
324 // remove the last slash we copied (if we've copied one), but don't make a mal-formed root
325 if ((lpchDst > lpszDst) && !PathIsRootW(lpszDst))
326 lpchDst--;
327 }
328 else
329 {
330 lpchSrc += 2;
331 }
332 }
333 else if (cchPC == 3 && *lpchSrc == W('.') && *(lpchSrc + 1) == W('.')) // Check for dot dot.
334 {
335 // make sure we aren't already at the root
336 if (!PathIsRootW(lpszDst))
337 {
338 // Go up... Remove the previous path component.
339 lpchDst = (LPWSTR)PCStart(lpszDst, lpchDst - 1);
340 }
341 else
342 {
343 // When we can't back up, skip the trailing backslash
344 // so we don't copy one again. (C:\..\FOO would otherwise
345 // turn into C:\\FOO).
346 if (IsPathSeparator(*(lpchSrc + 2)))
347 {
348 lpchSrc++;
349 }
350 }
351
352 // skip ".."
353 lpchSrc += 2;
354 }
355 else // Everything else
356 {
357 // Just copy it.
358 int cchRemainingBuffer = MAX_PATH - (lpszDst - lpchDst);
359 StringCchCopyNW(lpchDst, cchRemainingBuffer, lpchSrc, cchPC);
360 lpchDst += cchPC - 1;
361 lpchSrc += cchPC - 1;
362 }
363
364 // Keep everything nice and tidy.
365 *lpchDst = W('\0');
366 }
367
368 // Check for weirdo root directory stuff.
369 NearRootFixups(lpszDst, fUNC);
370
371 return TRUE;
372}
373
374// Modifies:
375// pszRoot
376//
377// Returns:
378// TRUE if a drive root was found
379// FALSE otherwise
380//
381STDAPI_(BOOL) PathStripToRootW(LPWSTR pszRoot)
382{
383 RIPMSG(pszRoot && IS_VALID_STRING_PTR(pszRoot, -1), "PathStripToRoot: caller passed bad pszRoot");
384
385 if (pszRoot)
386 {
387 while (!PathIsRootW(pszRoot))
388 {
389 if (!PathRemoveFileSpecW(pszRoot))
390 {
391 // If we didn't strip anything off,
392 // must be current drive
393 return FALSE;
394 }
395 }
396 return TRUE;
397 }
398 return FALSE;
399}
400
401
402
403/*----------------------------------------------------------
404Purpose: Concatenate lpszDir and lpszFile into a properly formed
405 path and canonicalize any relative path pieces.
406
407 lpszDest and lpszFile can be the same buffer
408 lpszDest and lpszDir can be the same buffer
409
410Returns: pointer to lpszDest
411*/
412STDAPI_(LPWSTR) PathCombineW(LPWSTR lpszDest, LPCWSTR lpszDir, LPCWSTR lpszFile)
413{
414#ifdef DEBUG
415 RIPMSG(lpszDest && IS_VALID_WRITE_BUFFER(lpszDest, TCHAR, MAX_LONGPATH), "PathCombine: caller passed bad lpszDest");
416 RIPMSG(!lpszDir || IS_VALID_STRING_PTR(lpszDir, -1), "PathCombine: caller passed bad lpszDir");
417 RIPMSG(!lpszFile || IS_VALID_STRING_PTR(lpszFile, -1), "PathCombine: caller passed bad lpszFile");
418 RIPMSG(lpszDir || lpszFile, "PathCombine: caller neglected to pass lpszDir or lpszFile");
419#endif // DEBUG
420
421
422 if (lpszDest)
423 {
424 TCHAR szTemp[MAX_LONGPATH];
425 LPWSTR pszT;
426
427 *szTemp = W('\0');
428
429 if (lpszDir && *lpszDir)
430 {
431 if (!lpszFile || *lpszFile==W('\0'))
432 {
433 // lpszFile is empty
434 StringCchCopyNW(szTemp, ARRAYSIZE(szTemp), lpszDir, ARRAYSIZE(szTemp));
435 }
436 else if (PathIsRelativeW(lpszFile))
437 {
438 StringCchCopyNW(szTemp, ARRAYSIZE(szTemp), lpszDir, ARRAYSIZE(szTemp));
439 pszT = PathAddBackslashW(szTemp);
440 if (pszT)
441 {
442 size_t iRemaining = ARRAYSIZE(szTemp) - (pszT - szTemp);
443
444 if (wcslen(lpszFile) < iRemaining)
445 {
446 StringCchCopyNW(pszT, iRemaining, lpszFile, iRemaining);
447 }
448 else
449 {
450 *szTemp = W('\0');
451 }
452 }
453 else
454 {
455 *szTemp = W('\0');
456 }
457 }
458 else if (IsPathSeparator(*lpszFile) && !PathIsUNCW(lpszFile))
459 {
460 StringCchCopyNW(szTemp, ARRAYSIZE(szTemp), lpszDir, ARRAYSIZE(szTemp));
461 // FEATURE: Note that we do not check that an actual root is returned;
462 // it is assumed that we are given valid parameters
463 PathStripToRootW(szTemp);
464
465 pszT = PathAddBackslashW(szTemp);
466 if (pszT)
467 {
468 // Skip the backslash when copying
469 // Note: We don't support strings longer than 4GB, but that's
470 // okay because we already barf at MAX_PATH
471 int iRemaining = (int)(ARRAYSIZE(szTemp) - (pszT - szTemp));
472 StringCchCopyNW(pszT, iRemaining, lpszFile+1, iRemaining);
473 }
474 else
475 {
476 *szTemp = W('\0');
477 }
478 }
479 else
480 {
481 // already fully qualified file part
482 StringCchCopyNW(szTemp, ARRAYSIZE(szTemp), lpszFile, ARRAYSIZE(szTemp));
483 }
484 }
485 else if (lpszFile && *lpszFile)
486 {
487 // no dir just use file.
488 StringCchCopyNW(szTemp, ARRAYSIZE(szTemp), lpszFile, ARRAYSIZE(szTemp));
489 }
490
491 //
492 // if szTemp has something in it we succeeded. Also if szTemp is empty and
493 // the input strings are empty we succeed and PathCanonicalize() will
494 // return "\"
495 //
496 if (*szTemp || ((lpszDir || lpszFile) && !((lpszDir && *lpszDir) || (lpszFile && *lpszFile))))
497 {
498 PathCanonicalizeW(lpszDest, szTemp); // this deals with .. and . stuff
499 // returns "\" on empty szTemp
500 }
501 else
502 {
503 *lpszDest = W('\0'); // set output buffer to empty string.
504 lpszDest = NULL; // return failure.
505 }
506 }
507
508 return lpszDest;
509}
510
511// add a backslash to a qualified path
512//
513// in:
514// lpszPath path (A:, C:\foo, etc)
515//
516// out:
517// lpszPath A:\, C:\foo\ ;
518//
519// returns:
520// pointer to the NULL that terminates the path
521//
522STDAPI_(LPWSTR) PathAddBackslashW(LPWSTR lpszPath)
523{
524 LPWSTR lpszRet = NULL;
525
526 RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathAddBackslash: caller passed bad lpszPath");
527
528 if (lpszPath)
529 {
530 size_t ichPath = wcslen(lpszPath);
531 LPWSTR lpszEnd = lpszPath + ichPath;
532
533 if (ichPath)
534 {
535
536 // Get the end of the source directory
537 switch(*(lpszEnd-1))
538 {
539 case CH_SLASH:
540 case CH_WHACK:
541 break;
542
543 default:
544 // try to keep us from tromping over MAX_PATH in size.
545 // if we find these cases, return NULL. Note: We need to
546 // check those places that call us to handle their GP fault
547 // if they try to use the NULL!
548 if (ichPath >= (MAX_PATH - 2)) // -2 because ichPath doesn't include NULL, and we're adding a CH_WHACK.
549 {
550 return(NULL);
551 }
552
553 *lpszEnd++ = CH_WHACK;
554 *lpszEnd = W('\0');
555 }
556 }
557
558 lpszRet = lpszEnd;
559 }
560
561 return lpszRet;
562}
563
564
565
566
567//---------------------------------------------------------------------------
568// Returns TRUE if the given string is a UNC path.
569//
570// TRUE
571// "\\foo\bar"
572// "\\foo" <- careful
573// "\\"
574// FALSE
575// "\foo"
576// "foo"
577// "c:\foo"
578//
579//
580STDAPI_(BOOL) PathIsUNCW(LPCWSTR pszPath)
581{
582 RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNC: caller passed bad pszPath");
583
584 if (pszPath)
585 {
586 return DBL_BSLASH(pszPath);
587 }
588 return FALSE;
589}
590
591
592
593
594
595//---------------------------------------------------------------------------
596// Return TRUE if the path isn't absoulte.
597//
598// TRUE
599// "foo.exe"
600// ".\foo.exe"
601// "..\boo\foo.exe"
602//
603// FALSE
604// "\foo"
605// "c:bar" <- be careful
606// "c:\bar"
607// "\\foo\bar"
608//
609STDAPI_(BOOL) PathIsRelativeW(LPCWSTR lpszPath)
610{
611 RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsRelative: caller passed bad lpszPath");
612
613 if (!lpszPath || *lpszPath == 0)
614 {
615 // The NULL path is assumed relative
616 return TRUE;
617 }
618
619 if (IsPathSeparator(lpszPath[0]))
620 {
621 // Does it begin with a slash ?
622 return FALSE;
623 }
624 else if (lpszPath[1] == W(':'))
625 {
626 // Does it begin with a drive and a colon ?
627 return FALSE;
628 }
629 else
630 {
631 // Probably relative.
632 return TRUE;
633 }
634}
635
636// find the next slash or null terminator
637LPWSTR StrSlash(LPCWSTR psz)
638{
639 for (; *psz && !IsPathSeparator(*psz); psz++);
640
641 // Cast to a non-const string to mimic the behavior
642 // of wcschr/StrChr and strchr.
643 return (LPWSTR) psz;
644}
645
646
647
648