xref: /reactos/dll/win32/shlwapi/path.c (revision c2c66aff)
1 /*
2  * Path Functions
3  *
4  * Copyright 1999, 2000 Juergen Schmied
5  * Copyright 2001, 2002 Jon Griffiths
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include "precomp.h"
23 
24 #ifdef __REACTOS__
25 int WINAPI IsNetDrive(int drive);
26 #else
27 
28 /* Get a function pointer from a DLL handle */
29 #define GET_FUNC(func, module, name, fail) \
30   do { \
31     if (!func) { \
32       if (!SHLWAPI_h##module && !(SHLWAPI_h##module = LoadLibraryA(#module ".dll"))) return fail; \
33       func = (fn##func)GetProcAddress(SHLWAPI_h##module, name); \
34       if (!func) return fail; \
35     } \
36   } while (0)
37 
38 /* DLL handles for late bound calls */
39 static HMODULE SHLWAPI_hshell32;
40 
41 /* Function pointers for GET_FUNC macro; these need to be global because of gcc bug */
42 typedef BOOL (WINAPI *fnpIsNetDrive)(int);
43 static  fnpIsNetDrive pIsNetDrive;
44 
45 #endif /* __REACTOS__ */
46 
47 
48 HRESULT WINAPI SHGetWebFolderFilePathW(LPCWSTR,LPWSTR,DWORD);
49 
50 static inline WCHAR* heap_strdupAtoW(LPCSTR str)
51 {
52     WCHAR *ret = NULL;
53 
54     if (str)
55     {
56         DWORD len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
57         ret = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
58         if (ret)
59             MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
60     }
61 
62     return ret;
63 }
64 
65 /*************************************************************************
66  * PathAppendA    [SHLWAPI.@]
67  *
68  * Append one path to another.
69  *
70  * PARAMS
71  *  lpszPath   [I/O] Initial part of path, and destination for output
72  *  lpszAppend [I]   Path to append
73  *
74  * RETURNS
75  *  Success: TRUE. lpszPath contains the newly created path.
76  *  Failure: FALSE, if either path is NULL, or PathCombineA() fails.
77  *
78  * NOTES
79  *  lpszAppend must contain at least one backslash ('\') if not NULL.
80  *  Because PathCombineA() is used to join the paths, the resulting
81  *  path is also canonicalized.
82  */
83 BOOL WINAPI PathAppendA (LPSTR lpszPath, LPCSTR lpszAppend)
84 {
85   TRACE("(%s,%s)\n",debugstr_a(lpszPath), debugstr_a(lpszAppend));
86 
87   if (lpszPath && lpszAppend)
88   {
89     if (!PathIsUNCA(lpszAppend))
90       while (*lpszAppend == '\\')
91         lpszAppend++;
92     if (PathCombineA(lpszPath, lpszPath, lpszAppend))
93       return TRUE;
94   }
95   return FALSE;
96 }
97 
98 /*************************************************************************
99  * PathAppendW    [SHLWAPI.@]
100  *
101  * See PathAppendA.
102  */
103 BOOL WINAPI PathAppendW(LPWSTR lpszPath, LPCWSTR lpszAppend)
104 {
105   TRACE("(%s,%s)\n",debugstr_w(lpszPath), debugstr_w(lpszAppend));
106 
107   if (lpszPath && lpszAppend)
108   {
109     if (!PathIsUNCW(lpszAppend))
110       while (*lpszAppend == '\\')
111         lpszAppend++;
112     if (PathCombineW(lpszPath, lpszPath, lpszAppend))
113       return TRUE;
114   }
115   return FALSE;
116 }
117 
118 /*************************************************************************
119  * PathCombineA		[SHLWAPI.@]
120  *
121  * Combine two paths together.
122  *
123  * PARAMS
124  *  lpszDest [O] Destination for combined path
125  *  lpszDir  [I] Directory path
126  *  lpszFile [I] File path
127  *
128  * RETURNS
129  *  Success: The output path
130  *  Failure: NULL, if inputs are invalid.
131  *
132  * NOTES
133  *  lpszDest should be at least MAX_PATH in size, and may point to the same
134  *  memory location as lpszDir. The combined path is canonicalised.
135  */
136 LPSTR WINAPI PathCombineA(LPSTR lpszDest, LPCSTR lpszDir, LPCSTR lpszFile)
137 {
138   WCHAR szDest[MAX_PATH];
139   WCHAR szDir[MAX_PATH];
140   WCHAR szFile[MAX_PATH];
141   TRACE("(%p,%s,%s)\n", lpszDest, debugstr_a(lpszDir), debugstr_a(lpszFile));
142 
143   /* Invalid parameters */
144   if (!lpszDest)
145     return NULL;
146   if (!lpszDir && !lpszFile)
147     goto fail;
148 
149   if (lpszDir)
150     if (!MultiByteToWideChar(CP_ACP,0,lpszDir,-1,szDir,MAX_PATH))
151       goto fail;
152 
153   if (lpszFile)
154     if (!MultiByteToWideChar(CP_ACP,0,lpszFile,-1,szFile,MAX_PATH))
155       goto fail;
156 
157   if (PathCombineW(szDest, lpszDir ? szDir : NULL, lpszFile ? szFile : NULL))
158     if (WideCharToMultiByte(CP_ACP,0,szDest,-1,lpszDest,MAX_PATH,0,0))
159       return lpszDest;
160 
161 fail:
162   lpszDest[0] = 0;
163   return NULL;
164 }
165 
166 /*************************************************************************
167  * PathCombineW		 [SHLWAPI.@]
168  *
169  * See PathCombineA.
170  */
171 LPWSTR WINAPI PathCombineW(LPWSTR lpszDest, LPCWSTR lpszDir, LPCWSTR lpszFile)
172 {
173   WCHAR szTemp[MAX_PATH];
174   BOOL bUseBoth = FALSE, bStrip = FALSE;
175 
176   TRACE("(%p,%s,%s)\n", lpszDest, debugstr_w(lpszDir), debugstr_w(lpszFile));
177 
178   /* Invalid parameters */
179   if (!lpszDest)
180     return NULL;
181   if (!lpszDir && !lpszFile)
182   {
183     lpszDest[0] = 0;
184     return NULL;
185   }
186 
187   if ((!lpszFile || !*lpszFile) && lpszDir)
188   {
189     /* Use dir only */
190     lstrcpynW(szTemp, lpszDir, MAX_PATH);
191   }
192   else if (!lpszDir || !*lpszDir || !PathIsRelativeW(lpszFile))
193   {
194     if (!lpszDir || !*lpszDir || *lpszFile != '\\' || PathIsUNCW(lpszFile))
195     {
196       /* Use file only */
197       lstrcpynW(szTemp, lpszFile, MAX_PATH);
198     }
199     else
200     {
201       bUseBoth = TRUE;
202       bStrip = TRUE;
203     }
204   }
205   else
206     bUseBoth = TRUE;
207 
208   if (bUseBoth)
209   {
210     lstrcpynW(szTemp, lpszDir, MAX_PATH);
211     if (bStrip)
212     {
213       PathStripToRootW(szTemp);
214       lpszFile++; /* Skip '\' */
215     }
216     if (!PathAddBackslashW(szTemp) || strlenW(szTemp) + strlenW(lpszFile) >= MAX_PATH)
217     {
218       lpszDest[0] = 0;
219       return NULL;
220     }
221     strcatW(szTemp, lpszFile);
222   }
223 
224   PathCanonicalizeW(lpszDest, szTemp);
225   return lpszDest;
226 }
227 
228 /*************************************************************************
229  * PathAddBackslashA	[SHLWAPI.@]
230  *
231  * Append a backslash ('\') to a path if one doesn't exist.
232  *
233  * PARAMS
234  *  lpszPath [I/O] The path to append a backslash to.
235  *
236  * RETURNS
237  *  Success: The position of the last backslash in the path.
238  *  Failure: NULL, if lpszPath is NULL or the path is too large.
239  */
240 LPSTR WINAPI PathAddBackslashA(LPSTR lpszPath)
241 {
242   size_t iLen;
243   LPSTR prev = lpszPath;
244 
245   TRACE("(%s)\n",debugstr_a(lpszPath));
246 
247   if (!lpszPath || (iLen = strlen(lpszPath)) >= MAX_PATH)
248     return NULL;
249 
250   if (iLen)
251   {
252     do {
253       lpszPath = CharNextA(prev);
254       if (*lpszPath)
255         prev = lpszPath;
256     } while (*lpszPath);
257     if (*prev != '\\')
258     {
259       *lpszPath++ = '\\';
260       *lpszPath = '\0';
261     }
262   }
263   return lpszPath;
264 }
265 
266 /*************************************************************************
267  * PathAddBackslashW  [SHLWAPI.@]
268  *
269  * See PathAddBackslashA.
270  */
271 LPWSTR WINAPI PathAddBackslashW( LPWSTR lpszPath )
272 {
273   size_t iLen;
274 
275   TRACE("(%s)\n",debugstr_w(lpszPath));
276 
277   if (!lpszPath || (iLen = strlenW(lpszPath)) >= MAX_PATH)
278     return NULL;
279 
280   if (iLen)
281   {
282     lpszPath += iLen;
283     if (lpszPath[-1] != '\\')
284     {
285       *lpszPath++ = '\\';
286       *lpszPath = '\0';
287     }
288   }
289   return lpszPath;
290 }
291 
292 /*************************************************************************
293  * PathBuildRootA    [SHLWAPI.@]
294  *
295  * Create a root drive string (e.g. "A:\") from a drive number.
296  *
297  * PARAMS
298  *  lpszPath [O] Destination for the drive string
299  *
300  * RETURNS
301  *  lpszPath
302  *
303  * NOTES
304  *  If lpszPath is NULL or drive is invalid, nothing is written to lpszPath.
305  */
306 LPSTR WINAPI PathBuildRootA(LPSTR lpszPath, int drive)
307 {
308   TRACE("(%p,%d)\n", lpszPath, drive);
309 
310   if (lpszPath && drive >= 0 && drive < 26)
311   {
312     lpszPath[0] = 'A' + drive;
313     lpszPath[1] = ':';
314     lpszPath[2] = '\\';
315     lpszPath[3] = '\0';
316   }
317   return lpszPath;
318 }
319 
320 /*************************************************************************
321  * PathBuildRootW    [SHLWAPI.@]
322  *
323  * See PathBuildRootA.
324  */
325 LPWSTR WINAPI PathBuildRootW(LPWSTR lpszPath, int drive)
326 {
327   TRACE("(%p,%d)\n", lpszPath, drive);
328 
329   if (lpszPath && drive >= 0 && drive < 26)
330   {
331     lpszPath[0] = 'A' + drive;
332     lpszPath[1] = ':';
333     lpszPath[2] = '\\';
334     lpszPath[3] = '\0';
335   }
336   return lpszPath;
337 }
338 
339 /*************************************************************************
340  * PathFindFileNameA  [SHLWAPI.@]
341  *
342  * Locate the start of the file name in a path
343  *
344  * PARAMS
345  *  lpszPath [I] Path to search
346  *
347  * RETURNS
348  *  A pointer to the first character of the file name
349  */
350 LPSTR WINAPI PathFindFileNameA(LPCSTR lpszPath)
351 {
352   LPCSTR lastSlash = lpszPath;
353 
354   TRACE("(%s)\n",debugstr_a(lpszPath));
355 
356   while (lpszPath && *lpszPath)
357   {
358     if ((*lpszPath == '\\' || *lpszPath == '/' || *lpszPath == ':') &&
359         lpszPath[1] && lpszPath[1] != '\\' && lpszPath[1] != '/')
360       lastSlash = lpszPath + 1;
361     lpszPath = CharNextA(lpszPath);
362   }
363   return (LPSTR)lastSlash;
364 }
365 
366 /*************************************************************************
367  * PathFindFileNameW  [SHLWAPI.@]
368  *
369  * See PathFindFileNameA.
370  */
371 LPWSTR WINAPI PathFindFileNameW(LPCWSTR lpszPath)
372 {
373   LPCWSTR lastSlash = lpszPath;
374 
375   TRACE("(%s)\n",debugstr_w(lpszPath));
376 
377   while (lpszPath && *lpszPath)
378   {
379     if ((*lpszPath == '\\' || *lpszPath == '/' || *lpszPath == ':') &&
380         lpszPath[1] && lpszPath[1] != '\\' && lpszPath[1] != '/')
381       lastSlash = lpszPath + 1;
382     lpszPath++;
383   }
384   return (LPWSTR)lastSlash;
385 }
386 
387 /*************************************************************************
388  * PathFindExtensionA  [SHLWAPI.@]
389  *
390  * Locate the start of the file extension in a path
391  *
392  * PARAMS
393  *  lpszPath [I] The path to search
394  *
395  * RETURNS
396  *  A pointer to the first character of the extension, the end of
397  *  the string if the path has no extension, or NULL If lpszPath is NULL
398  */
399 LPSTR WINAPI PathFindExtensionA( LPCSTR lpszPath )
400 {
401   LPCSTR lastpoint = NULL;
402 
403   TRACE("(%s)\n", debugstr_a(lpszPath));
404 
405   if (lpszPath)
406   {
407     while (*lpszPath)
408     {
409       if (*lpszPath == '\\' || *lpszPath==' ')
410         lastpoint = NULL;
411       else if (*lpszPath == '.')
412         lastpoint = lpszPath;
413       lpszPath = CharNextA(lpszPath);
414     }
415   }
416   return (LPSTR)(lastpoint ? lastpoint : lpszPath);
417 }
418 
419 /*************************************************************************
420  * PathFindExtensionW  [SHLWAPI.@]
421  *
422  * See PathFindExtensionA.
423  */
424 LPWSTR WINAPI PathFindExtensionW( LPCWSTR lpszPath )
425 {
426   LPCWSTR lastpoint = NULL;
427 
428   TRACE("(%s)\n", debugstr_w(lpszPath));
429 
430   if (lpszPath)
431   {
432     while (*lpszPath)
433     {
434       if (*lpszPath == '\\' || *lpszPath==' ')
435         lastpoint = NULL;
436       else if (*lpszPath == '.')
437         lastpoint = lpszPath;
438       lpszPath++;
439     }
440   }
441   return (LPWSTR)(lastpoint ? lastpoint : lpszPath);
442 }
443 
444 /*************************************************************************
445  * PathGetArgsA    [SHLWAPI.@]
446  *
447  * Find the next argument in a string delimited by spaces.
448  *
449  * PARAMS
450  *  lpszPath [I] The string to search for arguments in
451  *
452  * RETURNS
453  *  The start of the next argument in lpszPath, or NULL if lpszPath is NULL
454  *
455  * NOTES
456  *  Spaces in quoted strings are ignored as delimiters.
457  */
458 LPSTR WINAPI PathGetArgsA(LPCSTR lpszPath)
459 {
460   BOOL bSeenQuote = FALSE;
461 
462   TRACE("(%s)\n",debugstr_a(lpszPath));
463 
464   if (lpszPath)
465   {
466     while (*lpszPath)
467     {
468       if ((*lpszPath==' ') && !bSeenQuote)
469         return (LPSTR)lpszPath + 1;
470       if (*lpszPath == '"')
471         bSeenQuote = !bSeenQuote;
472       lpszPath = CharNextA(lpszPath);
473     }
474   }
475   return (LPSTR)lpszPath;
476 }
477 
478 /*************************************************************************
479  * PathGetArgsW    [SHLWAPI.@]
480  *
481  * See PathGetArgsA.
482  */
483 LPWSTR WINAPI PathGetArgsW(LPCWSTR lpszPath)
484 {
485   BOOL bSeenQuote = FALSE;
486 
487   TRACE("(%s)\n",debugstr_w(lpszPath));
488 
489   if (lpszPath)
490   {
491     while (*lpszPath)
492     {
493       if ((*lpszPath==' ') && !bSeenQuote)
494         return (LPWSTR)lpszPath + 1;
495       if (*lpszPath == '"')
496         bSeenQuote = !bSeenQuote;
497       lpszPath++;
498     }
499   }
500   return (LPWSTR)lpszPath;
501 }
502 
503 /*************************************************************************
504  * PathGetDriveNumberA	[SHLWAPI.@]
505  *
506  * Return the drive number from a path
507  *
508  * PARAMS
509  *  lpszPath [I] Path to get the drive number from
510  *
511  * RETURNS
512  *  Success: The drive number corresponding to the drive in the path
513  *  Failure: -1, if lpszPath contains no valid drive
514  */
515 int WINAPI PathGetDriveNumberA(LPCSTR lpszPath)
516 {
517   TRACE ("(%s)\n",debugstr_a(lpszPath));
518 
519   if (lpszPath && !IsDBCSLeadByte(*lpszPath) && lpszPath[1] == ':' &&
520       tolower(*lpszPath) >= 'a' && tolower(*lpszPath) <= 'z')
521     return tolower(*lpszPath) - 'a';
522   return -1;
523 }
524 
525 /*************************************************************************
526  * PathGetDriveNumberW	[SHLWAPI.@]
527  *
528  * See PathGetDriveNumberA.
529  */
530 int WINAPI PathGetDriveNumberW(LPCWSTR lpszPath)
531 {
532   TRACE ("(%s)\n",debugstr_w(lpszPath));
533 
534   if (lpszPath)
535   {
536       WCHAR tl = tolowerW(lpszPath[0]);
537       if (tl >= 'a' && tl <= 'z' && lpszPath[1] == ':')
538           return tl - 'a';
539   }
540   return -1;
541 }
542 
543 /*************************************************************************
544  * PathRemoveFileSpecA	[SHLWAPI.@]
545  *
546  * Remove the file specification from a path.
547  *
548  * PARAMS
549  *  lpszPath [I/O] Path to remove the file spec from
550  *
551  * RETURNS
552  *  TRUE  If the path was valid and modified
553  *  FALSE Otherwise
554  */
555 BOOL WINAPI PathRemoveFileSpecA(LPSTR lpszPath)
556 {
557   LPSTR lpszFileSpec = lpszPath;
558   BOOL bModified = FALSE;
559 
560   TRACE("(%s)\n",debugstr_a(lpszPath));
561 
562   if(lpszPath)
563   {
564     /* Skip directory or UNC path */
565     if (*lpszPath == '\\')
566       lpszFileSpec = ++lpszPath;
567     if (*lpszPath == '\\')
568       lpszFileSpec = ++lpszPath;
569 
570     while (*lpszPath)
571     {
572       if(*lpszPath == '\\')
573         lpszFileSpec = lpszPath; /* Skip dir */
574       else if(*lpszPath == ':')
575       {
576         lpszFileSpec = ++lpszPath; /* Skip drive */
577         if (*lpszPath == '\\')
578           lpszFileSpec++;
579       }
580       if (!(lpszPath = CharNextA(lpszPath)))
581         break;
582     }
583 
584     if (*lpszFileSpec)
585     {
586       *lpszFileSpec = '\0';
587       bModified = TRUE;
588     }
589   }
590   return bModified;
591 }
592 
593 /*************************************************************************
594  * PathRemoveFileSpecW	[SHLWAPI.@]
595  *
596  * See PathRemoveFileSpecA.
597  */
598 BOOL WINAPI PathRemoveFileSpecW(LPWSTR lpszPath)
599 {
600   LPWSTR lpszFileSpec = lpszPath;
601   BOOL bModified = FALSE;
602 
603   TRACE("(%s)\n",debugstr_w(lpszPath));
604 
605   if(lpszPath)
606   {
607     /* Skip directory or UNC path */
608     if (*lpszPath == '\\')
609       lpszFileSpec = ++lpszPath;
610     if (*lpszPath == '\\')
611       lpszFileSpec = ++lpszPath;
612 
613     while (*lpszPath)
614     {
615       if(*lpszPath == '\\')
616         lpszFileSpec = lpszPath; /* Skip dir */
617       else if(*lpszPath == ':')
618       {
619         lpszFileSpec = ++lpszPath; /* Skip drive */
620         if (*lpszPath == '\\')
621           lpszFileSpec++;
622       }
623       lpszPath++;
624     }
625 
626     if (*lpszFileSpec)
627     {
628       *lpszFileSpec = '\0';
629       bModified = TRUE;
630     }
631   }
632   return bModified;
633 }
634 
635 /*************************************************************************
636  * PathStripPathA	[SHLWAPI.@]
637  *
638  * Remove the initial path from the beginning of a filename
639  *
640  * PARAMS
641  *  lpszPath [I/O] Path to remove the initial path from
642  *
643  * RETURNS
644  *  Nothing.
645  */
646 void WINAPI PathStripPathA(LPSTR lpszPath)
647 {
648   TRACE("(%s)\n", debugstr_a(lpszPath));
649 
650   if (lpszPath)
651   {
652     LPSTR lpszFileName = PathFindFileNameA(lpszPath);
653     if(lpszFileName != lpszPath)
654       RtlMoveMemory(lpszPath, lpszFileName, strlen(lpszFileName)+1);
655   }
656 }
657 
658 /*************************************************************************
659  * PathStripPathW	[SHLWAPI.@]
660  *
661  * See PathStripPathA.
662  */
663 void WINAPI PathStripPathW(LPWSTR lpszPath)
664 {
665   LPWSTR lpszFileName;
666 
667   TRACE("(%s)\n", debugstr_w(lpszPath));
668   lpszFileName = PathFindFileNameW(lpszPath);
669   if(lpszFileName != lpszPath)
670     RtlMoveMemory(lpszPath, lpszFileName, (strlenW(lpszFileName)+1)*sizeof(WCHAR));
671 }
672 
673 /*************************************************************************
674  * PathStripToRootA	[SHLWAPI.@]
675  *
676  * Reduce a path to its root.
677  *
678  * PARAMS
679  *  lpszPath [I/O] the path to reduce
680  *
681  * RETURNS
682  *  Success: TRUE if the stripped path is a root path
683  *  Failure: FALSE if the path cannot be stripped or is NULL
684  */
685 BOOL WINAPI PathStripToRootA(LPSTR lpszPath)
686 {
687   TRACE("(%s)\n", debugstr_a(lpszPath));
688 
689   if (!lpszPath)
690     return FALSE;
691   while(!PathIsRootA(lpszPath))
692     if (!PathRemoveFileSpecA(lpszPath))
693       return FALSE;
694   return TRUE;
695 }
696 
697 /*************************************************************************
698  * PathStripToRootW	[SHLWAPI.@]
699  *
700  * See PathStripToRootA.
701  */
702 BOOL WINAPI PathStripToRootW(LPWSTR lpszPath)
703 {
704   TRACE("(%s)\n", debugstr_w(lpszPath));
705 
706   if (!lpszPath)
707     return FALSE;
708   while(!PathIsRootW(lpszPath))
709     if (!PathRemoveFileSpecW(lpszPath))
710       return FALSE;
711   return TRUE;
712 }
713 
714 /*************************************************************************
715  * PathRemoveArgsA	[SHLWAPI.@]
716  *
717  * Strip space separated arguments from a path.
718  *
719  * PARAMS
720  *  lpszPath [I/O] Path to remove arguments from
721  *
722  * RETURNS
723  *  Nothing.
724  */
725 void WINAPI PathRemoveArgsA(LPSTR lpszPath)
726 {
727   TRACE("(%s)\n",debugstr_a(lpszPath));
728 
729   if(lpszPath)
730   {
731     LPSTR lpszArgs = PathGetArgsA(lpszPath);
732     if (*lpszArgs)
733       lpszArgs[-1] = '\0';
734     else
735     {
736       LPSTR lpszLastChar = CharPrevA(lpszPath, lpszArgs);
737       if(*lpszLastChar == ' ')
738         *lpszLastChar = '\0';
739     }
740   }
741 }
742 
743 /*************************************************************************
744  * PathRemoveArgsW	[SHLWAPI.@]
745  *
746  * See PathRemoveArgsA.
747  */
748 void WINAPI PathRemoveArgsW(LPWSTR lpszPath)
749 {
750   TRACE("(%s)\n",debugstr_w(lpszPath));
751 
752   if(lpszPath)
753   {
754     LPWSTR lpszArgs = PathGetArgsW(lpszPath);
755     if (*lpszArgs || (lpszArgs > lpszPath && lpszArgs[-1] == ' '))
756       lpszArgs[-1] = '\0';
757   }
758 }
759 
760 /*************************************************************************
761  * PathRemoveExtensionA		[SHLWAPI.@]
762  *
763  * Remove the file extension from a path
764  *
765  * PARAMS
766  *  lpszPath [I/O] Path to remove the extension from
767  *
768  * NOTES
769  *  The NUL terminator must be written only if extension exists
770  *  and if the pointed character is not already NUL.
771  *
772  * RETURNS
773  *  Nothing.
774  */
775 void WINAPI PathRemoveExtensionA(LPSTR lpszPath)
776 {
777   TRACE("(%s)\n", debugstr_a(lpszPath));
778 
779   if (lpszPath)
780   {
781     lpszPath = PathFindExtensionA(lpszPath);
782     if (lpszPath && *lpszPath != '\0')
783       *lpszPath = '\0';
784   }
785 }
786 
787 /*************************************************************************
788  * PathRemoveExtensionW		[SHLWAPI.@]
789  *
790  * See PathRemoveExtensionA.
791 */
792 void WINAPI PathRemoveExtensionW(LPWSTR lpszPath)
793 {
794   TRACE("(%s)\n", debugstr_w(lpszPath));
795 
796   if (lpszPath)
797   {
798     lpszPath = PathFindExtensionW(lpszPath);
799     if (lpszPath && *lpszPath != '\0')
800       *lpszPath = '\0';
801   }
802 }
803 
804 /*************************************************************************
805  * PathRemoveBackslashA	[SHLWAPI.@]
806  *
807  * Remove a trailing backslash from a path.
808  *
809  * PARAMS
810  *  lpszPath [I/O] Path to remove backslash from
811  *
812  * RETURNS
813  *  Success: A pointer to the end of the path
814  *  Failure: NULL, if lpszPath is NULL
815  */
816 LPSTR WINAPI PathRemoveBackslashA( LPSTR lpszPath )
817 {
818   LPSTR szTemp = NULL;
819 
820   TRACE("(%s)\n", debugstr_a(lpszPath));
821 
822   if(lpszPath)
823   {
824     szTemp = CharPrevA(lpszPath, lpszPath + strlen(lpszPath));
825     if (!PathIsRootA(lpszPath) && *szTemp == '\\')
826       *szTemp = '\0';
827   }
828   return szTemp;
829 }
830 
831 /*************************************************************************
832  * PathRemoveBackslashW	[SHLWAPI.@]
833  *
834  * See PathRemoveBackslashA.
835  */
836 LPWSTR WINAPI PathRemoveBackslashW( LPWSTR lpszPath )
837 {
838   LPWSTR szTemp = NULL;
839 
840   TRACE("(%s)\n", debugstr_w(lpszPath));
841 
842   if(lpszPath)
843   {
844     szTemp = lpszPath + strlenW(lpszPath);
845     if (szTemp > lpszPath) szTemp--;
846     if (!PathIsRootW(lpszPath) && *szTemp == '\\')
847       *szTemp = '\0';
848   }
849   return szTemp;
850 }
851 
852 /*************************************************************************
853  * PathRemoveBlanksA [SHLWAPI.@]
854  *
855  * Remove Spaces from the start and end of a path.
856  *
857  * PARAMS
858  *  lpszPath [I/O] Path to strip blanks from
859  *
860  * RETURNS
861  *  Nothing.
862  */
863 VOID WINAPI PathRemoveBlanksA(LPSTR lpszPath)
864 {
865   TRACE("(%s)\n", debugstr_a(lpszPath));
866 
867   if(lpszPath && *lpszPath)
868   {
869     LPSTR start = lpszPath;
870 
871     while (*lpszPath == ' ')
872       lpszPath = CharNextA(lpszPath);
873 
874     while(*lpszPath)
875       *start++ = *lpszPath++;
876 
877     if (start != lpszPath)
878       while (start[-1] == ' ')
879         start--;
880     *start = '\0';
881   }
882 }
883 
884 /*************************************************************************
885  * PathRemoveBlanksW [SHLWAPI.@]
886  *
887  * See PathRemoveBlanksA.
888  */
889 VOID WINAPI PathRemoveBlanksW(LPWSTR lpszPath)
890 {
891   TRACE("(%s)\n", debugstr_w(lpszPath));
892 
893   if(lpszPath && *lpszPath)
894   {
895     LPWSTR start = lpszPath;
896 
897     while (*lpszPath == ' ')
898       lpszPath++;
899 
900     while(*lpszPath)
901       *start++ = *lpszPath++;
902 
903     if (start != lpszPath)
904       while (start[-1] == ' ')
905         start--;
906     *start = '\0';
907   }
908 }
909 
910 /*************************************************************************
911  * PathQuoteSpacesA [SHLWAPI.@]
912  *
913  * Surround a path containing spaces in quotes.
914  *
915  * PARAMS
916  *  lpszPath [I/O] Path to quote
917  *
918  * RETURNS
919  *  Nothing.
920  *
921  * NOTES
922  *  The path is not changed if it is invalid or has no spaces.
923  */
924 VOID WINAPI PathQuoteSpacesA(LPSTR lpszPath)
925 {
926   TRACE("(%s)\n", debugstr_a(lpszPath));
927 
928   if(lpszPath && StrChrA(lpszPath,' '))
929   {
930     size_t iLen = strlen(lpszPath) + 1;
931 
932     if (iLen + 2 < MAX_PATH)
933     {
934       memmove(lpszPath + 1, lpszPath, iLen);
935       lpszPath[0] = '"';
936       lpszPath[iLen] = '"';
937       lpszPath[iLen + 1] = '\0';
938     }
939   }
940 }
941 
942 /*************************************************************************
943  * PathQuoteSpacesW [SHLWAPI.@]
944  *
945  * See PathQuoteSpacesA.
946  */
947 VOID WINAPI PathQuoteSpacesW(LPWSTR lpszPath)
948 {
949   TRACE("(%s)\n", debugstr_w(lpszPath));
950 
951   if(lpszPath && StrChrW(lpszPath,' '))
952   {
953     int iLen = strlenW(lpszPath) + 1;
954 
955     if (iLen + 2 < MAX_PATH)
956     {
957       memmove(lpszPath + 1, lpszPath, iLen * sizeof(WCHAR));
958       lpszPath[0] = '"';
959       lpszPath[iLen] = '"';
960       lpszPath[iLen + 1] = '\0';
961     }
962   }
963 }
964 
965 /*************************************************************************
966  * PathUnquoteSpacesA [SHLWAPI.@]
967  *
968  * Remove quotes ("") from around a path, if present.
969  *
970  * PARAMS
971  *  lpszPath [I/O] Path to strip quotes from
972  *
973  * RETURNS
974  *  Nothing
975  *
976  * NOTES
977  *  If the path contains a single quote only, an empty string will result.
978  *  Otherwise quotes are only removed if they appear at the start and end
979  *  of the path.
980  */
981 VOID WINAPI PathUnquoteSpacesA(LPSTR lpszPath)
982 {
983   TRACE("(%s)\n", debugstr_a(lpszPath));
984 
985   if (lpszPath && *lpszPath == '"')
986   {
987     DWORD dwLen = strlen(lpszPath) - 1;
988 
989     if (lpszPath[dwLen] == '"')
990     {
991       lpszPath[dwLen] = '\0';
992       for (; *lpszPath; lpszPath++)
993         *lpszPath = lpszPath[1];
994     }
995   }
996 }
997 
998 /*************************************************************************
999  * PathUnquoteSpacesW [SHLWAPI.@]
1000  *
1001  * See PathUnquoteSpacesA.
1002  */
1003 VOID WINAPI PathUnquoteSpacesW(LPWSTR lpszPath)
1004 {
1005   TRACE("(%s)\n", debugstr_w(lpszPath));
1006 
1007   if (lpszPath && *lpszPath == '"')
1008   {
1009     DWORD dwLen = strlenW(lpszPath) - 1;
1010 
1011     if (lpszPath[dwLen] == '"')
1012     {
1013       lpszPath[dwLen] = '\0';
1014       for (; *lpszPath; lpszPath++)
1015         *lpszPath = lpszPath[1];
1016     }
1017   }
1018 }
1019 
1020 /*************************************************************************
1021  * PathParseIconLocationA  [SHLWAPI.@]
1022  *
1023  * Parse the location of an icon from a path.
1024  *
1025  * PARAMS
1026  *  lpszPath [I/O] The path to parse the icon location from.
1027  *
1028  * RETURNS
1029  *  Success: The number of the icon
1030  *  Failure: 0 if the path does not contain an icon location or is NULL
1031  *
1032  * NOTES
1033  *  The path has surrounding quotes and spaces removed regardless
1034  *  of whether the call succeeds or not.
1035  */
1036 int WINAPI PathParseIconLocationA(LPSTR lpszPath)
1037 {
1038   int iRet = 0;
1039   LPSTR lpszComma;
1040 
1041   TRACE("(%s)\n", debugstr_a(lpszPath));
1042 
1043   if (lpszPath)
1044   {
1045     if ((lpszComma = strchr(lpszPath, ',')))
1046     {
1047       *lpszComma++ = '\0';
1048       iRet = StrToIntA(lpszComma);
1049     }
1050     PathUnquoteSpacesA(lpszPath);
1051     PathRemoveBlanksA(lpszPath);
1052   }
1053   return iRet;
1054 }
1055 
1056 /*************************************************************************
1057  * PathParseIconLocationW  [SHLWAPI.@]
1058  *
1059  * See PathParseIconLocationA.
1060  */
1061 int WINAPI PathParseIconLocationW(LPWSTR lpszPath)
1062 {
1063   int iRet = 0;
1064   LPWSTR lpszComma;
1065 
1066   TRACE("(%s)\n", debugstr_w(lpszPath));
1067 
1068   if (lpszPath)
1069   {
1070     if ((lpszComma = StrChrW(lpszPath, ',')))
1071     {
1072       *lpszComma++ = '\0';
1073       iRet = StrToIntW(lpszComma);
1074     }
1075     PathUnquoteSpacesW(lpszPath);
1076     PathRemoveBlanksW(lpszPath);
1077   }
1078   return iRet;
1079 }
1080 
1081 /*************************************************************************
1082  * @	[SHLWAPI.4]
1083  *
1084  * Unicode version of PathFileExistsDefExtA.
1085  */
1086 BOOL WINAPI PathFileExistsDefExtW(LPWSTR lpszPath,DWORD dwWhich)
1087 {
1088   static const WCHAR pszExts[][5]  = { { '.', 'p', 'i', 'f', 0},
1089                                        { '.', 'c', 'o', 'm', 0},
1090                                        { '.', 'e', 'x', 'e', 0},
1091                                        { '.', 'b', 'a', 't', 0},
1092                                        { '.', 'l', 'n', 'k', 0},
1093                                        { '.', 'c', 'm', 'd', 0},
1094                                        { 0, 0, 0, 0, 0} };
1095 
1096   TRACE("(%s,%d)\n", debugstr_w(lpszPath), dwWhich);
1097 
1098   if (!lpszPath || PathIsUNCServerW(lpszPath) || PathIsUNCServerShareW(lpszPath))
1099     return FALSE;
1100 
1101   if (dwWhich)
1102   {
1103     LPCWSTR szExt = PathFindExtensionW(lpszPath);
1104     if (!*szExt || dwWhich & 0x40)
1105     {
1106       size_t iChoose = 0;
1107       int iLen = lstrlenW(lpszPath);
1108       if (iLen > (MAX_PATH - 5))
1109         return FALSE;
1110       while ( (dwWhich & 0x1) && pszExts[iChoose][0] )
1111       {
1112         lstrcpyW(lpszPath + iLen, pszExts[iChoose]);
1113         if (PathFileExistsW(lpszPath))
1114           return TRUE;
1115         iChoose++;
1116         dwWhich >>= 1;
1117       }
1118       *(lpszPath + iLen) = (WCHAR)'\0';
1119       return FALSE;
1120     }
1121   }
1122   return PathFileExistsW(lpszPath);
1123 }
1124 
1125 /*************************************************************************
1126  * @	[SHLWAPI.3]
1127  *
1128  * Determine if a file exists locally and is of an executable type.
1129  *
1130  * PARAMS
1131  *  lpszPath       [I/O] File to search for
1132  *  dwWhich        [I]   Type of executable to search for
1133  *
1134  * RETURNS
1135  *  TRUE  If the file was found. lpszPath contains the file name.
1136  *  FALSE Otherwise.
1137  *
1138  * NOTES
1139  *  lpszPath is modified in place and must be at least MAX_PATH in length.
1140  *  If the function returns FALSE, the path is modified to its original state.
1141  *  If the given path contains an extension or dwWhich is 0, executable
1142  *  extensions are not checked.
1143  *
1144  *  Ordinals 3-6 are a classic case of MS exposing limited functionality to
1145  *  users (here through PathFindOnPathA()) and keeping advanced functionality for
1146  *  their own developers exclusive use. Monopoly, anyone?
1147  */
1148 BOOL WINAPI PathFileExistsDefExtA(LPSTR lpszPath,DWORD dwWhich)
1149 {
1150   BOOL bRet = FALSE;
1151 
1152   TRACE("(%s,%d)\n", debugstr_a(lpszPath), dwWhich);
1153 
1154   if (lpszPath)
1155   {
1156     WCHAR szPath[MAX_PATH];
1157     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
1158     bRet = PathFileExistsDefExtW(szPath, dwWhich);
1159     if (bRet)
1160       WideCharToMultiByte(CP_ACP,0,szPath,-1,lpszPath,MAX_PATH,0,0);
1161   }
1162   return bRet;
1163 }
1164 
1165 /*************************************************************************
1166  * SHLWAPI_PathFindInOtherDirs
1167  *
1168  * Internal helper for SHLWAPI_PathFindOnPathExA/W.
1169  */
1170 static BOOL SHLWAPI_PathFindInOtherDirs(LPWSTR lpszFile, DWORD dwWhich)
1171 {
1172   static const WCHAR szSystem[] = { 'S','y','s','t','e','m','\0'};
1173   static const WCHAR szPath[] = { 'P','A','T','H','\0'};
1174   DWORD dwLenPATH;
1175   LPCWSTR lpszCurr;
1176   WCHAR *lpszPATH;
1177   WCHAR buff[MAX_PATH];
1178 
1179   TRACE("(%s,%08x)\n", debugstr_w(lpszFile), dwWhich);
1180 
1181   /* Try system directories */
1182   GetSystemDirectoryW(buff, MAX_PATH);
1183   if (!PathAppendW(buff, lpszFile))
1184      return FALSE;
1185   if (PathFileExistsDefExtW(buff, dwWhich))
1186   {
1187     strcpyW(lpszFile, buff);
1188     return TRUE;
1189   }
1190   GetWindowsDirectoryW(buff, MAX_PATH);
1191   if (!PathAppendW(buff, szSystem ) || !PathAppendW(buff, lpszFile))
1192     return FALSE;
1193   if (PathFileExistsDefExtW(buff, dwWhich))
1194   {
1195     strcpyW(lpszFile, buff);
1196     return TRUE;
1197   }
1198   GetWindowsDirectoryW(buff, MAX_PATH);
1199   if (!PathAppendW(buff, lpszFile))
1200     return FALSE;
1201   if (PathFileExistsDefExtW(buff, dwWhich))
1202   {
1203     strcpyW(lpszFile, buff);
1204     return TRUE;
1205   }
1206   /* Try dirs listed in %PATH% */
1207   dwLenPATH = GetEnvironmentVariableW(szPath, buff, MAX_PATH);
1208 
1209   if (!dwLenPATH || !(lpszPATH = HeapAlloc(GetProcessHeap(), 0, (dwLenPATH + 1) * sizeof (WCHAR))))
1210     return FALSE;
1211 
1212   GetEnvironmentVariableW(szPath, lpszPATH, dwLenPATH + 1);
1213   lpszCurr = lpszPATH;
1214   while (lpszCurr)
1215   {
1216     LPCWSTR lpszEnd = lpszCurr;
1217     LPWSTR pBuff = buff;
1218 
1219     while (*lpszEnd == ' ')
1220       lpszEnd++;
1221     while (*lpszEnd && *lpszEnd != ';')
1222       *pBuff++ = *lpszEnd++;
1223     *pBuff = '\0';
1224 
1225     if (*lpszEnd)
1226       lpszCurr = lpszEnd + 1;
1227     else
1228       lpszCurr = NULL; /* Last Path, terminate after this */
1229 
1230     if (!PathAppendW(buff, lpszFile))
1231     {
1232       HeapFree(GetProcessHeap(), 0, lpszPATH);
1233       return FALSE;
1234     }
1235     if (PathFileExistsDefExtW(buff, dwWhich))
1236     {
1237       strcpyW(lpszFile, buff);
1238       HeapFree(GetProcessHeap(), 0, lpszPATH);
1239       return TRUE;
1240     }
1241   }
1242   HeapFree(GetProcessHeap(), 0, lpszPATH);
1243   return FALSE;
1244 }
1245 
1246 /*************************************************************************
1247  * @	[SHLWAPI.5]
1248  *
1249  * Search a range of paths for a specific type of executable.
1250  *
1251  * PARAMS
1252  *  lpszFile       [I/O] File to search for
1253  *  lppszOtherDirs [I]   Other directories to look in
1254  *  dwWhich        [I]   Type of executable to search for
1255  *
1256  * RETURNS
1257  *  Success: TRUE. The path to the executable is stored in lpszFile.
1258  *  Failure: FALSE. The path to the executable is unchanged.
1259  */
1260 BOOL WINAPI PathFindOnPathExA(LPSTR lpszFile,LPCSTR *lppszOtherDirs,DWORD dwWhich)
1261 {
1262   WCHAR szFile[MAX_PATH];
1263   WCHAR buff[MAX_PATH];
1264 
1265   TRACE("(%s,%p,%08x)\n", debugstr_a(lpszFile), lppszOtherDirs, dwWhich);
1266 
1267   if (!lpszFile || !PathIsFileSpecA(lpszFile))
1268     return FALSE;
1269 
1270   MultiByteToWideChar(CP_ACP,0,lpszFile,-1,szFile,MAX_PATH);
1271 
1272   /* Search provided directories first */
1273   if (lppszOtherDirs && *lppszOtherDirs)
1274   {
1275     WCHAR szOther[MAX_PATH];
1276     LPCSTR *lpszOtherPath = lppszOtherDirs;
1277 
1278     while (lpszOtherPath && *lpszOtherPath && (*lpszOtherPath)[0])
1279     {
1280       MultiByteToWideChar(CP_ACP,0,*lpszOtherPath,-1,szOther,MAX_PATH);
1281       PathCombineW(buff, szOther, szFile);
1282       if (PathFileExistsDefExtW(buff, dwWhich))
1283       {
1284         WideCharToMultiByte(CP_ACP,0,buff,-1,lpszFile,MAX_PATH,0,0);
1285         return TRUE;
1286       }
1287       lpszOtherPath++;
1288     }
1289   }
1290   /* Not found, try system and path dirs */
1291   if (SHLWAPI_PathFindInOtherDirs(szFile, dwWhich))
1292   {
1293     WideCharToMultiByte(CP_ACP,0,szFile,-1,lpszFile,MAX_PATH,0,0);
1294     return TRUE;
1295   }
1296   return FALSE;
1297 }
1298 
1299 /*************************************************************************
1300  * @	[SHLWAPI.6]
1301  *
1302  * Unicode version of PathFindOnPathExA.
1303  */
1304 BOOL WINAPI PathFindOnPathExW(LPWSTR lpszFile,LPCWSTR *lppszOtherDirs,DWORD dwWhich)
1305 {
1306   WCHAR buff[MAX_PATH];
1307 
1308   TRACE("(%s,%p,%08x)\n", debugstr_w(lpszFile), lppszOtherDirs, dwWhich);
1309 
1310   if (!lpszFile || !PathIsFileSpecW(lpszFile))
1311     return FALSE;
1312 
1313   /* Search provided directories first */
1314   if (lppszOtherDirs && *lppszOtherDirs)
1315   {
1316     LPCWSTR *lpszOtherPath = lppszOtherDirs;
1317     while (lpszOtherPath && *lpszOtherPath && (*lpszOtherPath)[0])
1318     {
1319       PathCombineW(buff, *lpszOtherPath, lpszFile);
1320       if (PathFileExistsDefExtW(buff, dwWhich))
1321       {
1322         strcpyW(lpszFile, buff);
1323         return TRUE;
1324       }
1325       lpszOtherPath++;
1326     }
1327   }
1328   /* Not found, try system and path dirs */
1329   return SHLWAPI_PathFindInOtherDirs(lpszFile, dwWhich);
1330 }
1331 
1332 /*************************************************************************
1333  * PathFindOnPathA	[SHLWAPI.@]
1334  *
1335  * Search a range of paths for an executable.
1336  *
1337  * PARAMS
1338  *  lpszFile       [I/O] File to search for
1339  *  lppszOtherDirs [I]   Other directories to look in
1340  *
1341  * RETURNS
1342  *  Success: TRUE. The path to the executable is stored in lpszFile.
1343  *  Failure: FALSE. The path to the executable is unchanged.
1344  */
1345 BOOL WINAPI PathFindOnPathA(LPSTR lpszFile, LPCSTR *lppszOtherDirs)
1346 {
1347   TRACE("(%s,%p)\n", debugstr_a(lpszFile), lppszOtherDirs);
1348   return PathFindOnPathExA(lpszFile, lppszOtherDirs, 0);
1349  }
1350 
1351 /*************************************************************************
1352  * PathFindOnPathW      [SHLWAPI.@]
1353  *
1354  * See PathFindOnPathA.
1355  */
1356 BOOL WINAPI PathFindOnPathW(LPWSTR lpszFile, LPCWSTR *lppszOtherDirs)
1357 {
1358   TRACE("(%s,%p)\n", debugstr_w(lpszFile), lppszOtherDirs);
1359   return PathFindOnPathExW(lpszFile,lppszOtherDirs, 0);
1360 }
1361 
1362 /*************************************************************************
1363  * PathCompactPathExA   [SHLWAPI.@]
1364  *
1365  * Compact a path into a given number of characters.
1366  *
1367  * PARAMS
1368  *  lpszDest [O] Destination for compacted path
1369  *  lpszPath [I] Source path
1370  *  cchMax   [I] Maximum size of compacted path
1371  *  dwFlags  [I] Reserved
1372  *
1373  * RETURNS
1374  *  Success: TRUE. The compacted path is written to lpszDest.
1375  *  Failure: FALSE. lpszPath is undefined.
1376  *
1377  * NOTES
1378  *  If cchMax is given as 0, lpszDest will still be NUL terminated.
1379  *
1380  *  The Win32 version of this function contains a bug: When cchMax == 7,
1381  *  8 bytes will be written to lpszDest. This bug is fixed in the Wine
1382  *  implementation.
1383  *
1384  *  Some relative paths will be different when cchMax == 5 or 6. This occurs
1385  *  because Win32 will insert a "\" in lpszDest, even if one is
1386  *  not present in the original path.
1387  */
1388 BOOL WINAPI PathCompactPathExA(LPSTR lpszDest, LPCSTR lpszPath,
1389                                UINT cchMax, DWORD dwFlags)
1390 {
1391   BOOL bRet = FALSE;
1392 
1393   TRACE("(%p,%s,%d,0x%08x)\n", lpszDest, debugstr_a(lpszPath), cchMax, dwFlags);
1394 
1395   if (lpszPath && lpszDest)
1396   {
1397     WCHAR szPath[MAX_PATH];
1398     WCHAR szDest[MAX_PATH];
1399 
1400     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
1401     szDest[0] = '\0';
1402     bRet = PathCompactPathExW(szDest, szPath, cchMax, dwFlags);
1403     WideCharToMultiByte(CP_ACP,0,szDest,-1,lpszDest,MAX_PATH,0,0);
1404   }
1405   return bRet;
1406 }
1407 
1408 /*************************************************************************
1409  * PathCompactPathExW   [SHLWAPI.@]
1410  *
1411  * See PathCompactPathExA.
1412  */
1413 BOOL WINAPI PathCompactPathExW(LPWSTR lpszDest, LPCWSTR lpszPath,
1414                                UINT cchMax, DWORD dwFlags)
1415 {
1416   static const WCHAR szEllipses[] = { '.', '.', '.', '\0' };
1417   LPCWSTR lpszFile;
1418   DWORD dwLen, dwFileLen = 0;
1419 
1420   TRACE("(%p,%s,%d,0x%08x)\n", lpszDest, debugstr_w(lpszPath), cchMax, dwFlags);
1421 
1422   if (!lpszPath)
1423     return FALSE;
1424 
1425   if (!lpszDest)
1426   {
1427     WARN("Invalid lpszDest would crash under Win32!\n");
1428     return FALSE;
1429   }
1430 
1431   *lpszDest = '\0';
1432 
1433   if (cchMax < 2)
1434     return TRUE;
1435 
1436   dwLen = strlenW(lpszPath) + 1;
1437 
1438   if (dwLen < cchMax)
1439   {
1440     /* Don't need to compact */
1441     memcpy(lpszDest, lpszPath, dwLen * sizeof(WCHAR));
1442     return TRUE;
1443   }
1444 
1445   /* Path must be compacted to fit into lpszDest */
1446   lpszFile = PathFindFileNameW(lpszPath);
1447   dwFileLen = lpszPath + dwLen - lpszFile;
1448 
1449   if (dwFileLen == dwLen)
1450   {
1451     /* No root in psth */
1452     if (cchMax <= 4)
1453     {
1454       while (--cchMax > 0) /* No room left for anything but ellipses */
1455         *lpszDest++ = '.';
1456       *lpszDest = '\0';
1457       return TRUE;
1458     }
1459     /* Compact the file name with ellipses at the end */
1460     cchMax -= 4;
1461     memcpy(lpszDest, lpszFile, cchMax * sizeof(WCHAR));
1462     strcpyW(lpszDest + cchMax, szEllipses);
1463     return TRUE;
1464   }
1465   /* We have a root in the path */
1466   lpszFile--; /* Start compacted filename with the path separator */
1467   dwFileLen++;
1468 
1469   if (dwFileLen + 3 > cchMax)
1470   {
1471     /* Compact the file name */
1472     if (cchMax <= 4)
1473     {
1474       while (--cchMax > 0) /* No room left for anything but ellipses */
1475         *lpszDest++ = '.';
1476       *lpszDest = '\0';
1477       return TRUE;
1478     }
1479     strcpyW(lpszDest, szEllipses);
1480     lpszDest += 3;
1481     cchMax -= 4;
1482     *lpszDest++ = *lpszFile++;
1483     if (cchMax <= 4)
1484     {
1485       while (--cchMax > 0) /* No room left for anything but ellipses */
1486         *lpszDest++ = '.';
1487       *lpszDest = '\0';
1488       return TRUE;
1489     }
1490     cchMax -= 4;
1491     memcpy(lpszDest, lpszFile, cchMax * sizeof(WCHAR));
1492     strcpyW(lpszDest + cchMax, szEllipses);
1493     return TRUE;
1494   }
1495 
1496   /* Only the root needs to be Compacted */
1497   dwLen = cchMax - dwFileLen - 3;
1498   memcpy(lpszDest, lpszPath, dwLen * sizeof(WCHAR));
1499   strcpyW(lpszDest + dwLen, szEllipses);
1500   strcpyW(lpszDest + dwLen + 3, lpszFile);
1501   return TRUE;
1502 }
1503 
1504 /*************************************************************************
1505  * PathIsRelativeA	[SHLWAPI.@]
1506  *
1507  * Determine if a path is a relative path.
1508  *
1509  * PARAMS
1510  *  lpszPath [I] Path to check
1511  *
1512  * RETURNS
1513  *  TRUE:  The path is relative, or is invalid.
1514  *  FALSE: The path is not relative.
1515  */
1516 BOOL WINAPI PathIsRelativeA (LPCSTR lpszPath)
1517 {
1518   TRACE("(%s)\n",debugstr_a(lpszPath));
1519 
1520   if (!lpszPath || !*lpszPath || IsDBCSLeadByte(*lpszPath))
1521     return TRUE;
1522   if (*lpszPath == '\\' || (*lpszPath && lpszPath[1] == ':'))
1523     return FALSE;
1524   return TRUE;
1525 }
1526 
1527 /*************************************************************************
1528  *  PathIsRelativeW	[SHLWAPI.@]
1529  *
1530  * See PathIsRelativeA.
1531  */
1532 BOOL WINAPI PathIsRelativeW (LPCWSTR lpszPath)
1533 {
1534   TRACE("(%s)\n",debugstr_w(lpszPath));
1535 
1536   if (!lpszPath || !*lpszPath)
1537     return TRUE;
1538   if (*lpszPath == '\\' || (*lpszPath && lpszPath[1] == ':'))
1539     return FALSE;
1540   return TRUE;
1541 }
1542 
1543 /*************************************************************************
1544  * PathIsRootA		[SHLWAPI.@]
1545  *
1546  * Determine if a path is a root path.
1547  *
1548  * PARAMS
1549  *  lpszPath [I] Path to check
1550  *
1551  * RETURNS
1552  *  TRUE  If lpszPath is valid and a root path,
1553  *  FALSE Otherwise
1554  */
1555 BOOL WINAPI PathIsRootA(LPCSTR lpszPath)
1556 {
1557   TRACE("(%s)\n", debugstr_a(lpszPath));
1558 
1559   if (lpszPath && *lpszPath)
1560   {
1561     if (*lpszPath == '\\')
1562     {
1563       if (!lpszPath[1])
1564         return TRUE; /* \ */
1565       else if (lpszPath[1]=='\\')
1566       {
1567         BOOL bSeenSlash = FALSE;
1568         lpszPath += 2;
1569 
1570         /* Check for UNC root path */
1571         while (*lpszPath)
1572         {
1573           if (*lpszPath == '\\')
1574           {
1575             if (bSeenSlash)
1576               return FALSE;
1577             bSeenSlash = TRUE;
1578           }
1579           lpszPath = CharNextA(lpszPath);
1580         }
1581         return TRUE;
1582       }
1583     }
1584     else if (lpszPath[1] == ':' && lpszPath[2] == '\\' && lpszPath[3] == '\0')
1585       return TRUE; /* X:\ */
1586   }
1587   return FALSE;
1588 }
1589 
1590 /*************************************************************************
1591  * PathIsRootW		[SHLWAPI.@]
1592  *
1593  * See PathIsRootA.
1594  */
1595 BOOL WINAPI PathIsRootW(LPCWSTR lpszPath)
1596 {
1597   TRACE("(%s)\n", debugstr_w(lpszPath));
1598 
1599   if (lpszPath && *lpszPath)
1600   {
1601     if (*lpszPath == '\\')
1602     {
1603       if (!lpszPath[1])
1604         return TRUE; /* \ */
1605       else if (lpszPath[1]=='\\')
1606       {
1607         BOOL bSeenSlash = FALSE;
1608         lpszPath += 2;
1609 
1610         /* Check for UNC root path */
1611         while (*lpszPath)
1612         {
1613           if (*lpszPath == '\\')
1614           {
1615             if (bSeenSlash)
1616               return FALSE;
1617             bSeenSlash = TRUE;
1618           }
1619           lpszPath++;
1620         }
1621         return TRUE;
1622       }
1623     }
1624     else if (lpszPath[1] == ':' && lpszPath[2] == '\\' && lpszPath[3] == '\0')
1625       return TRUE; /* X:\ */
1626   }
1627   return FALSE;
1628 }
1629 
1630 /*************************************************************************
1631  * PathIsDirectoryA	[SHLWAPI.@]
1632  *
1633  * Determine if a path is a valid directory
1634  *
1635  * PARAMS
1636  *  lpszPath [I] Path to check.
1637  *
1638  * RETURNS
1639  *  FILE_ATTRIBUTE_DIRECTORY if lpszPath exists and can be read (See Notes)
1640  *  FALSE if lpszPath is invalid or not a directory.
1641  *
1642  * NOTES
1643  *  Although this function is prototyped as returning a BOOL, it returns
1644  *  FILE_ATTRIBUTE_DIRECTORY for success. This means that code such as:
1645  *
1646  *|  if (PathIsDirectoryA("c:\\windows\\") != FALSE)
1647  *|    ...
1648  *
1649  *  will always fail.
1650  */
1651 BOOL WINAPI PathIsDirectoryA(LPCSTR lpszPath)
1652 {
1653   DWORD dwAttr;
1654 
1655   TRACE("(%s)\n", debugstr_a(lpszPath));
1656 
1657   if (!lpszPath || PathIsUNCServerA(lpszPath))
1658     return FALSE;
1659 
1660   if (PathIsUNCServerShareA(lpszPath))
1661   {
1662     FIXME("UNC Server Share not yet supported - FAILING\n");
1663     return FALSE;
1664   }
1665 
1666   if ((dwAttr = GetFileAttributesA(lpszPath)) == INVALID_FILE_ATTRIBUTES)
1667     return FALSE;
1668   return dwAttr & FILE_ATTRIBUTE_DIRECTORY;
1669 }
1670 
1671 /*************************************************************************
1672  * PathIsDirectoryW	[SHLWAPI.@]
1673  *
1674  * See PathIsDirectoryA.
1675  */
1676 BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
1677 {
1678   DWORD dwAttr;
1679 
1680   TRACE("(%s)\n", debugstr_w(lpszPath));
1681 
1682   if (!lpszPath || PathIsUNCServerW(lpszPath))
1683     return FALSE;
1684 
1685   if (PathIsUNCServerShareW(lpszPath))
1686   {
1687     FIXME("UNC Server Share not yet supported - FAILING\n");
1688     return FALSE;
1689   }
1690 
1691   if ((dwAttr = GetFileAttributesW(lpszPath)) == INVALID_FILE_ATTRIBUTES)
1692     return FALSE;
1693   return dwAttr & FILE_ATTRIBUTE_DIRECTORY;
1694 }
1695 
1696 /*************************************************************************
1697  * PathFileExistsA	[SHLWAPI.@]
1698  *
1699  * Determine if a file exists.
1700  *
1701  * PARAMS
1702  *  lpszPath [I] Path to check
1703  *
1704  * RETURNS
1705  *  TRUE  If the file exists and is readable
1706  *  FALSE Otherwise
1707  */
1708 BOOL WINAPI PathFileExistsA(LPCSTR lpszPath)
1709 {
1710   UINT iPrevErrMode;
1711   DWORD dwAttr;
1712 
1713   TRACE("(%s)\n",debugstr_a(lpszPath));
1714 
1715   if (!lpszPath)
1716     return FALSE;
1717 
1718   /* Prevent a dialog box if path is on a disk that has been ejected. */
1719   iPrevErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
1720   dwAttr = GetFileAttributesA(lpszPath);
1721   SetErrorMode(iPrevErrMode);
1722   return dwAttr != INVALID_FILE_ATTRIBUTES;
1723 }
1724 
1725 /*************************************************************************
1726  * PathFileExistsW	[SHLWAPI.@]
1727  *
1728  * See PathFileExistsA.
1729  */
1730 BOOL WINAPI PathFileExistsW(LPCWSTR lpszPath)
1731 {
1732   UINT iPrevErrMode;
1733   DWORD dwAttr;
1734 
1735   TRACE("(%s)\n",debugstr_w(lpszPath));
1736 
1737   if (!lpszPath)
1738     return FALSE;
1739 
1740   iPrevErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
1741   dwAttr = GetFileAttributesW(lpszPath);
1742   SetErrorMode(iPrevErrMode);
1743   return dwAttr != INVALID_FILE_ATTRIBUTES;
1744 }
1745 
1746 /*************************************************************************
1747  * PathFileExistsAndAttributesA	[SHLWAPI.445]
1748  *
1749  * Determine if a file exists.
1750  *
1751  * PARAMS
1752  *  lpszPath [I] Path to check
1753  *  dwAttr   [O] attributes of file
1754  *
1755  * RETURNS
1756  *  TRUE  If the file exists and is readable
1757  *  FALSE Otherwise
1758  */
1759 BOOL WINAPI PathFileExistsAndAttributesA(LPCSTR lpszPath, DWORD *dwAttr)
1760 {
1761   UINT iPrevErrMode;
1762   DWORD dwVal = 0;
1763 
1764   TRACE("(%s %p)\n", debugstr_a(lpszPath), dwAttr);
1765 
1766   if (dwAttr)
1767     *dwAttr = INVALID_FILE_ATTRIBUTES;
1768 
1769   if (!lpszPath)
1770     return FALSE;
1771 
1772   iPrevErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
1773   dwVal = GetFileAttributesA(lpszPath);
1774   SetErrorMode(iPrevErrMode);
1775   if (dwAttr)
1776     *dwAttr = dwVal;
1777   return (dwVal != INVALID_FILE_ATTRIBUTES);
1778 }
1779 
1780 /*************************************************************************
1781  * PathFileExistsAndAttributesW	[SHLWAPI.446]
1782  *
1783  * See PathFileExistsA.
1784  */
1785 BOOL WINAPI PathFileExistsAndAttributesW(LPCWSTR lpszPath, DWORD *dwAttr)
1786 {
1787   UINT iPrevErrMode;
1788   DWORD dwVal;
1789 
1790   TRACE("(%s %p)\n", debugstr_w(lpszPath), dwAttr);
1791 
1792   if (!lpszPath)
1793     return FALSE;
1794 
1795   iPrevErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
1796   dwVal = GetFileAttributesW(lpszPath);
1797   SetErrorMode(iPrevErrMode);
1798   if (dwAttr)
1799     *dwAttr = dwVal;
1800   return (dwVal != INVALID_FILE_ATTRIBUTES);
1801 }
1802 
1803 /*************************************************************************
1804  * PathMatchSingleMaskA	[internal]
1805  */
1806 static BOOL PathMatchSingleMaskA(LPCSTR name, LPCSTR mask)
1807 {
1808   while (*name && *mask && *mask!=';')
1809   {
1810     if (*mask == '*')
1811     {
1812       do
1813       {
1814         if (PathMatchSingleMaskA(name,mask+1))
1815           return TRUE;  /* try substrings */
1816       } while (*name++);
1817       return FALSE;
1818     }
1819 
1820     if (toupper(*mask) != toupper(*name) && *mask != '?')
1821       return FALSE;
1822 
1823     name = CharNextA(name);
1824     mask = CharNextA(mask);
1825   }
1826 
1827   if (!*name)
1828   {
1829     while (*mask == '*')
1830       mask++;
1831     if (!*mask || *mask == ';')
1832       return TRUE;
1833   }
1834   return FALSE;
1835 }
1836 
1837 /*************************************************************************
1838  * PathMatchSingleMaskW	[internal]
1839  */
1840 static BOOL PathMatchSingleMaskW(LPCWSTR name, LPCWSTR mask)
1841 {
1842   while (*name && *mask && *mask != ';')
1843   {
1844     if (*mask == '*')
1845     {
1846       do
1847       {
1848         if (PathMatchSingleMaskW(name,mask+1))
1849           return TRUE;  /* try substrings */
1850       } while (*name++);
1851       return FALSE;
1852     }
1853 
1854     if (toupperW(*mask) != toupperW(*name) && *mask != '?')
1855       return FALSE;
1856 
1857     name++;
1858     mask++;
1859   }
1860   if (!*name)
1861   {
1862     while (*mask == '*')
1863       mask++;
1864     if (!*mask || *mask == ';')
1865       return TRUE;
1866   }
1867   return FALSE;
1868 }
1869 
1870 /*************************************************************************
1871  * PathMatchSpecA	[SHLWAPI.@]
1872  *
1873  * Determine if a path matches one or more search masks.
1874  *
1875  * PARAMS
1876  *  lpszPath [I] Path to check
1877  *  lpszMask [I] Search mask(s)
1878  *
1879  * RETURNS
1880  *  TRUE  If lpszPath is valid and is matched
1881  *  FALSE Otherwise
1882  *
1883  * NOTES
1884  *  Multiple search masks may be given if they are separated by ";". The
1885  *  pattern "*.*" is treated specially in that it matches all paths (for
1886  *  backwards compatibility with DOS).
1887  */
1888 BOOL WINAPI PathMatchSpecA(LPCSTR lpszPath, LPCSTR lpszMask)
1889 {
1890   TRACE("(%s,%s)\n", lpszPath, lpszMask);
1891 
1892   if (!lstrcmpA(lpszMask, "*.*"))
1893     return TRUE; /* Matches every path */
1894 
1895   while (*lpszMask)
1896   {
1897     while (*lpszMask == ' ')
1898       lpszMask++; /* Eat leading spaces */
1899 
1900     if (PathMatchSingleMaskA(lpszPath, lpszMask))
1901       return TRUE; /* Matches the current mask */
1902 
1903     while (*lpszMask && *lpszMask != ';')
1904       lpszMask = CharNextA(lpszMask); /* masks separated by ';' */
1905 
1906     if (*lpszMask == ';')
1907       lpszMask++;
1908   }
1909   return FALSE;
1910 }
1911 
1912 /*************************************************************************
1913  * PathMatchSpecW	[SHLWAPI.@]
1914  *
1915  * See PathMatchSpecA.
1916  */
1917 BOOL WINAPI PathMatchSpecW(LPCWSTR lpszPath, LPCWSTR lpszMask)
1918 {
1919   static const WCHAR szStarDotStar[] = { '*', '.', '*', '\0' };
1920 
1921   TRACE("(%s,%s)\n", debugstr_w(lpszPath), debugstr_w(lpszMask));
1922 
1923   if (!lstrcmpW(lpszMask, szStarDotStar))
1924     return TRUE; /* Matches every path */
1925 
1926   while (*lpszMask)
1927   {
1928     while (*lpszMask == ' ')
1929       lpszMask++; /* Eat leading spaces */
1930 
1931     if (PathMatchSingleMaskW(lpszPath, lpszMask))
1932       return TRUE; /* Matches the current path */
1933 
1934     while (*lpszMask && *lpszMask != ';')
1935       lpszMask++; /* masks separated by ';' */
1936 
1937     if (*lpszMask == ';')
1938       lpszMask++;
1939   }
1940   return FALSE;
1941 }
1942 
1943 /*************************************************************************
1944  * PathIsSameRootA	[SHLWAPI.@]
1945  *
1946  * Determine if two paths share the same root.
1947  *
1948  * PARAMS
1949  *  lpszPath1 [I] Source path
1950  *  lpszPath2 [I] Path to compare with
1951  *
1952  * RETURNS
1953  *  TRUE  If both paths are valid and share the same root.
1954  *  FALSE If either path is invalid or the paths do not share the same root.
1955  */
1956 BOOL WINAPI PathIsSameRootA(LPCSTR lpszPath1, LPCSTR lpszPath2)
1957 {
1958   LPCSTR lpszStart;
1959   int dwLen;
1960 
1961   TRACE("(%s,%s)\n", debugstr_a(lpszPath1), debugstr_a(lpszPath2));
1962 
1963   if (!lpszPath1 || !lpszPath2 || !(lpszStart = PathSkipRootA(lpszPath1)))
1964     return FALSE;
1965 
1966   dwLen = PathCommonPrefixA(lpszPath1, lpszPath2, NULL) + 1;
1967   if (lpszStart - lpszPath1 > dwLen)
1968     return FALSE; /* Paths not common up to length of the root */
1969   return TRUE;
1970 }
1971 
1972 /*************************************************************************
1973  * PathIsSameRootW	[SHLWAPI.@]
1974  *
1975  * See PathIsSameRootA.
1976  */
1977 BOOL WINAPI PathIsSameRootW(LPCWSTR lpszPath1, LPCWSTR lpszPath2)
1978 {
1979   LPCWSTR lpszStart;
1980   int dwLen;
1981 
1982   TRACE("(%s,%s)\n", debugstr_w(lpszPath1), debugstr_w(lpszPath2));
1983 
1984   if (!lpszPath1 || !lpszPath2 || !(lpszStart = PathSkipRootW(lpszPath1)))
1985     return FALSE;
1986 
1987   dwLen = PathCommonPrefixW(lpszPath1, lpszPath2, NULL) + 1;
1988   if (lpszStart - lpszPath1 > dwLen)
1989     return FALSE; /* Paths not common up to length of the root */
1990   return TRUE;
1991 }
1992 
1993 /*************************************************************************
1994  * PathIsContentTypeA   [SHLWAPI.@]
1995  *
1996  * Determine if a file is of a given registered content type.
1997  *
1998  * PARAMS
1999  *  lpszPath        [I] File to check
2000  *  lpszContentType [I] Content type to check for
2001  *
2002  * RETURNS
2003  *  TRUE  If lpszPath is a given registered content type,
2004  *  FALSE Otherwise.
2005  *
2006  * NOTES
2007  *  This function looks up the registered content type for lpszPath. If
2008  *  a content type is registered, it is compared (case insensitively) to
2009  *  lpszContentType. Only if this matches does the function succeed.
2010  */
2011 BOOL WINAPI PathIsContentTypeA(LPCSTR lpszPath, LPCSTR lpszContentType)
2012 {
2013   LPCSTR szExt;
2014   DWORD dwDummy;
2015   char szBuff[MAX_PATH];
2016 
2017   TRACE("(%s,%s)\n", debugstr_a(lpszPath), debugstr_a(lpszContentType));
2018 
2019   if (lpszPath && (szExt = PathFindExtensionA(lpszPath)) && *szExt &&
2020       !SHGetValueA(HKEY_CLASSES_ROOT, szExt, "Content Type",
2021                    REG_NONE, szBuff, &dwDummy) &&
2022       !strcasecmp(lpszContentType, szBuff))
2023   {
2024     return TRUE;
2025   }
2026   return FALSE;
2027 }
2028 
2029 /*************************************************************************
2030  * PathIsContentTypeW   [SHLWAPI.@]
2031  *
2032  * See PathIsContentTypeA.
2033  */
2034 BOOL WINAPI PathIsContentTypeW(LPCWSTR lpszPath, LPCWSTR lpszContentType)
2035 {
2036   static const WCHAR szContentType[] = { 'C','o','n','t','e','n','t',' ','T','y','p','e','\0' };
2037   LPCWSTR szExt;
2038   DWORD dwDummy;
2039   WCHAR szBuff[MAX_PATH];
2040 
2041   TRACE("(%s,%s)\n", debugstr_w(lpszPath), debugstr_w(lpszContentType));
2042 
2043   if (lpszPath && (szExt = PathFindExtensionW(lpszPath)) && *szExt &&
2044       !SHGetValueW(HKEY_CLASSES_ROOT, szExt, szContentType,
2045                    REG_NONE, szBuff, &dwDummy) &&
2046       !strcmpiW(lpszContentType, szBuff))
2047   {
2048     return TRUE;
2049   }
2050   return FALSE;
2051 }
2052 
2053 /*************************************************************************
2054  * PathIsFileSpecA   [SHLWAPI.@]
2055  *
2056  * Determine if a path is a file specification.
2057  *
2058  * PARAMS
2059  *  lpszPath [I] Path to check
2060  *
2061  * RETURNS
2062  *  TRUE  If lpszPath is a file specification (i.e. Contains no directories).
2063  *  FALSE Otherwise.
2064  */
2065 BOOL WINAPI PathIsFileSpecA(LPCSTR lpszPath)
2066 {
2067   TRACE("(%s)\n", debugstr_a(lpszPath));
2068 
2069   if (!lpszPath)
2070     return FALSE;
2071 
2072   while (*lpszPath)
2073   {
2074     if (*lpszPath == '\\' || *lpszPath == ':')
2075       return FALSE;
2076     lpszPath = CharNextA(lpszPath);
2077   }
2078   return TRUE;
2079 }
2080 
2081 /*************************************************************************
2082  * PathIsFileSpecW   [SHLWAPI.@]
2083  *
2084  * See PathIsFileSpecA.
2085  */
2086 BOOL WINAPI PathIsFileSpecW(LPCWSTR lpszPath)
2087 {
2088   TRACE("(%s)\n", debugstr_w(lpszPath));
2089 
2090   if (!lpszPath)
2091     return FALSE;
2092 
2093   while (*lpszPath)
2094   {
2095     if (*lpszPath == '\\' || *lpszPath == ':')
2096       return FALSE;
2097     lpszPath++;
2098   }
2099   return TRUE;
2100 }
2101 
2102 /*************************************************************************
2103  * PathIsPrefixA   [SHLWAPI.@]
2104  *
2105  * Determine if a path is a prefix of another.
2106  *
2107  * PARAMS
2108  *  lpszPrefix [I] Prefix
2109  *  lpszPath   [I] Path to check
2110  *
2111  * RETURNS
2112  *  TRUE  If lpszPath has lpszPrefix as its prefix,
2113  *  FALSE If either path is NULL or lpszPrefix is not a prefix
2114  */
2115 BOOL WINAPI PathIsPrefixA (LPCSTR lpszPrefix, LPCSTR lpszPath)
2116 {
2117   TRACE("(%s,%s)\n", debugstr_a(lpszPrefix), debugstr_a(lpszPath));
2118 
2119   if (lpszPrefix && lpszPath &&
2120       PathCommonPrefixA(lpszPath, lpszPrefix, NULL) == (int)strlen(lpszPrefix))
2121     return TRUE;
2122   return FALSE;
2123 }
2124 
2125 /*************************************************************************
2126  *  PathIsPrefixW   [SHLWAPI.@]
2127  *
2128  *  See PathIsPrefixA.
2129  */
2130 BOOL WINAPI PathIsPrefixW(LPCWSTR lpszPrefix, LPCWSTR lpszPath)
2131 {
2132   TRACE("(%s,%s)\n", debugstr_w(lpszPrefix), debugstr_w(lpszPath));
2133 
2134   if (lpszPrefix && lpszPath &&
2135       PathCommonPrefixW(lpszPath, lpszPrefix, NULL) == (int)strlenW(lpszPrefix))
2136     return TRUE;
2137   return FALSE;
2138 }
2139 
2140 /*************************************************************************
2141  * PathIsSystemFolderA   [SHLWAPI.@]
2142  *
2143  * Determine if a path or file attributes are a system folder.
2144  *
2145  * PARAMS
2146  *  lpszPath  [I] Path to check.
2147  *  dwAttrib  [I] Attributes to check, if lpszPath is NULL.
2148  *
2149  * RETURNS
2150  *  TRUE   If lpszPath or dwAttrib are a system folder.
2151  *  FALSE  If GetFileAttributesA() fails or neither parameter is a system folder.
2152  */
2153 BOOL WINAPI PathIsSystemFolderA(LPCSTR lpszPath, DWORD dwAttrib)
2154 {
2155   TRACE("(%s,0x%08x)\n", debugstr_a(lpszPath), dwAttrib);
2156 
2157   if (lpszPath && *lpszPath)
2158     dwAttrib = GetFileAttributesA(lpszPath);
2159 
2160   if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY) ||
2161       !(dwAttrib & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)))
2162     return FALSE;
2163   return TRUE;
2164 }
2165 
2166 /*************************************************************************
2167  * PathIsSystemFolderW   [SHLWAPI.@]
2168  *
2169  * See PathIsSystemFolderA.
2170  */
2171 BOOL WINAPI PathIsSystemFolderW(LPCWSTR lpszPath, DWORD dwAttrib)
2172 {
2173   TRACE("(%s,0x%08x)\n", debugstr_w(lpszPath), dwAttrib);
2174 
2175   if (lpszPath && *lpszPath)
2176     dwAttrib = GetFileAttributesW(lpszPath);
2177 
2178   if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY) ||
2179       !(dwAttrib & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)))
2180     return FALSE;
2181   return TRUE;
2182 }
2183 
2184 /*************************************************************************
2185  * PathIsUNCA		[SHLWAPI.@]
2186  *
2187  * Determine if a path is in UNC format.
2188  *
2189  * PARAMS
2190  *  lpszPath [I] Path to check
2191  *
2192  * RETURNS
2193  *  TRUE: The path is UNC.
2194  *  FALSE: The path is not UNC or is NULL.
2195  */
2196 BOOL WINAPI PathIsUNCA(LPCSTR lpszPath)
2197 {
2198   TRACE("(%s)\n",debugstr_a(lpszPath));
2199 
2200 /*
2201  * On Windows 2003, tests show that strings starting with "\\?" are
2202  * considered UNC, while on Windows Vista+ this is not the case anymore.
2203  */
2204 // #ifdef __REACTOS__
2205 #if (WINVER >= _WIN32_WINNT_VISTA)
2206   if (lpszPath && (lpszPath[0]=='\\') && (lpszPath[1]=='\\') && (lpszPath[2]!='?'))
2207 #else
2208   if (lpszPath && (lpszPath[0]=='\\') && (lpszPath[1]=='\\'))
2209 #endif
2210     return TRUE;
2211   return FALSE;
2212 }
2213 
2214 /*************************************************************************
2215  * PathIsUNCW		[SHLWAPI.@]
2216  *
2217  * See PathIsUNCA.
2218  */
2219 BOOL WINAPI PathIsUNCW(LPCWSTR lpszPath)
2220 {
2221   TRACE("(%s)\n",debugstr_w(lpszPath));
2222 
2223 /*
2224  * On Windows 2003, tests show that strings starting with "\\?" are
2225  * considered UNC, while on Windows Vista+ this is not the case anymore.
2226  */
2227 // #ifdef __REACTOS__
2228 #if (WINVER >= _WIN32_WINNT_VISTA)
2229   if (lpszPath && (lpszPath[0]=='\\') && (lpszPath[1]=='\\') && (lpszPath[2]!='?'))
2230 #else
2231   if (lpszPath && (lpszPath[0]=='\\') && (lpszPath[1]=='\\'))
2232 #endif
2233     return TRUE;
2234   return FALSE;
2235 }
2236 
2237 /*************************************************************************
2238  * PathIsUNCServerA   [SHLWAPI.@]
2239  *
2240  * Determine if a path is a UNC server name ("\\SHARENAME").
2241  *
2242  * PARAMS
2243  *  lpszPath  [I] Path to check.
2244  *
2245  * RETURNS
2246  *  TRUE   If lpszPath is a valid UNC server name.
2247  *  FALSE  Otherwise.
2248  *
2249  * NOTES
2250  *  This routine is bug compatible with Win32: Server names with a
2251  *  trailing backslash (e.g. "\\FOO\"), return FALSE incorrectly.
2252  *  Fixing this bug may break other shlwapi functions!
2253  */
2254 BOOL WINAPI PathIsUNCServerA(LPCSTR lpszPath)
2255 {
2256   TRACE("(%s)\n", debugstr_a(lpszPath));
2257 
2258   if (lpszPath && *lpszPath++ == '\\' && *lpszPath++ == '\\')
2259   {
2260     while (*lpszPath)
2261     {
2262       if (*lpszPath == '\\')
2263         return FALSE;
2264       lpszPath = CharNextA(lpszPath);
2265     }
2266     return TRUE;
2267   }
2268   return FALSE;
2269 }
2270 
2271 /*************************************************************************
2272  * PathIsUNCServerW   [SHLWAPI.@]
2273  *
2274  * See PathIsUNCServerA.
2275  */
2276 BOOL WINAPI PathIsUNCServerW(LPCWSTR lpszPath)
2277 {
2278   TRACE("(%s)\n", debugstr_w(lpszPath));
2279 
2280   if (lpszPath && lpszPath[0] == '\\' && lpszPath[1] == '\\')
2281   {
2282       return !strchrW( lpszPath + 2, '\\' );
2283   }
2284   return FALSE;
2285 }
2286 
2287 /*************************************************************************
2288  * PathIsUNCServerShareA   [SHLWAPI.@]
2289  *
2290  * Determine if a path is a UNC server share ("\\SHARENAME\SHARE").
2291  *
2292  * PARAMS
2293  *  lpszPath  [I] Path to check.
2294  *
2295  * RETURNS
2296  *  TRUE   If lpszPath is a valid UNC server share.
2297  *  FALSE  Otherwise.
2298  *
2299  * NOTES
2300  *  This routine is bug compatible with Win32: Server shares with a
2301  *  trailing backslash (e.g. "\\FOO\BAR\"), return FALSE incorrectly.
2302  *  Fixing this bug may break other shlwapi functions!
2303  */
2304 BOOL WINAPI PathIsUNCServerShareA(LPCSTR lpszPath)
2305 {
2306   TRACE("(%s)\n", debugstr_a(lpszPath));
2307 
2308   if (lpszPath && *lpszPath++ == '\\' && *lpszPath++ == '\\')
2309   {
2310     BOOL bSeenSlash = FALSE;
2311     while (*lpszPath)
2312     {
2313       if (*lpszPath == '\\')
2314       {
2315         if (bSeenSlash)
2316           return FALSE;
2317         bSeenSlash = TRUE;
2318       }
2319       lpszPath = CharNextA(lpszPath);
2320     }
2321     return bSeenSlash;
2322   }
2323   return FALSE;
2324 }
2325 
2326 /*************************************************************************
2327  * PathIsUNCServerShareW   [SHLWAPI.@]
2328  *
2329  * See PathIsUNCServerShareA.
2330  */
2331 BOOL WINAPI PathIsUNCServerShareW(LPCWSTR lpszPath)
2332 {
2333   TRACE("(%s)\n", debugstr_w(lpszPath));
2334 
2335   if (lpszPath && *lpszPath++ == '\\' && *lpszPath++ == '\\')
2336   {
2337     BOOL bSeenSlash = FALSE;
2338     while (*lpszPath)
2339     {
2340       if (*lpszPath == '\\')
2341       {
2342         if (bSeenSlash)
2343           return FALSE;
2344         bSeenSlash = TRUE;
2345       }
2346       lpszPath++;
2347     }
2348     return bSeenSlash;
2349   }
2350   return FALSE;
2351 }
2352 
2353 /*************************************************************************
2354  * PathCanonicalizeA   [SHLWAPI.@]
2355  *
2356  * Convert a path to its canonical form.
2357  *
2358  * PARAMS
2359  *  lpszBuf  [O] Output path
2360  *  lpszPath [I] Path to canonicalize
2361  *
2362  * RETURNS
2363  *  Success: TRUE.  lpszBuf contains the output path,
2364  *  Failure: FALSE, If input path is invalid. lpszBuf is undefined
2365  */
2366 BOOL WINAPI PathCanonicalizeA(LPSTR lpszBuf, LPCSTR lpszPath)
2367 {
2368   BOOL bRet = FALSE;
2369 
2370   TRACE("(%p,%s)\n", lpszBuf, debugstr_a(lpszPath));
2371 
2372   if (lpszBuf)
2373     *lpszBuf = '\0';
2374 
2375   if (!lpszBuf || !lpszPath)
2376     SetLastError(ERROR_INVALID_PARAMETER);
2377   else
2378   {
2379     WCHAR szPath[MAX_PATH];
2380     WCHAR szBuff[MAX_PATH];
2381     int ret = MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
2382 
2383     if (!ret) {
2384 	WARN("Failed to convert string to widechar (too long?), LE %d.\n", GetLastError());
2385 	return FALSE;
2386     }
2387     bRet = PathCanonicalizeW(szBuff, szPath);
2388     WideCharToMultiByte(CP_ACP,0,szBuff,-1,lpszBuf,MAX_PATH,0,0);
2389   }
2390   return bRet;
2391 }
2392 
2393 
2394 /*************************************************************************
2395  * PathCanonicalizeW   [SHLWAPI.@]
2396  *
2397  * See PathCanonicalizeA.
2398  */
2399 BOOL WINAPI PathCanonicalizeW(LPWSTR lpszBuf, LPCWSTR lpszPath)
2400 {
2401   LPWSTR lpszDst = lpszBuf;
2402   LPCWSTR lpszSrc = lpszPath;
2403 
2404   TRACE("(%p,%s)\n", lpszBuf, debugstr_w(lpszPath));
2405 
2406   if (lpszBuf)
2407     *lpszDst = '\0';
2408 
2409   if (!lpszBuf || !lpszPath)
2410   {
2411     SetLastError(ERROR_INVALID_PARAMETER);
2412     return FALSE;
2413   }
2414 
2415   if (!*lpszPath)
2416   {
2417     *lpszBuf++ = '\\';
2418     *lpszBuf = '\0';
2419     return TRUE;
2420   }
2421 
2422   /* Copy path root */
2423   if (*lpszSrc == '\\')
2424   {
2425     *lpszDst++ = *lpszSrc++;
2426   }
2427   else if (*lpszSrc && lpszSrc[1] == ':')
2428   {
2429     /* X:\ */
2430     *lpszDst++ = *lpszSrc++;
2431     *lpszDst++ = *lpszSrc++;
2432     if (*lpszSrc == '\\')
2433       *lpszDst++ = *lpszSrc++;
2434   }
2435 
2436   /* Canonicalize the rest of the path */
2437   while (*lpszSrc)
2438   {
2439     if (*lpszSrc == '.')
2440     {
2441       if (lpszSrc[1] == '\\' && (lpszSrc == lpszPath || lpszSrc[-1] == '\\' || lpszSrc[-1] == ':'))
2442       {
2443         lpszSrc += 2; /* Skip .\ */
2444       }
2445       else if (lpszSrc[1] == '.' && (lpszDst == lpszBuf || lpszDst[-1] == '\\'))
2446       {
2447         /* \.. backs up a directory, over the root if it has no \ following X:.
2448          * .. is ignored if it would remove a UNC server name or initial \\
2449          */
2450         if (lpszDst != lpszBuf)
2451         {
2452           *lpszDst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
2453           if (lpszDst > lpszBuf+1 && lpszDst[-1] == '\\' &&
2454              (lpszDst[-2] != '\\' || lpszDst > lpszBuf+2))
2455           {
2456             if (lpszDst[-2] == ':' && (lpszDst > lpszBuf+3 || lpszDst[-3] == ':'))
2457             {
2458               lpszDst -= 2;
2459               while (lpszDst > lpszBuf && *lpszDst != '\\')
2460                 lpszDst--;
2461               if (*lpszDst == '\\')
2462                 lpszDst++; /* Reset to last '\' */
2463               else
2464                 lpszDst = lpszBuf; /* Start path again from new root */
2465             }
2466             else if (lpszDst[-2] != ':' && !PathIsUNCServerShareW(lpszBuf))
2467               lpszDst -= 2;
2468           }
2469           while (lpszDst > lpszBuf && *lpszDst != '\\')
2470             lpszDst--;
2471           if (lpszDst == lpszBuf)
2472           {
2473             *lpszDst++ = '\\';
2474             lpszSrc++;
2475           }
2476         }
2477         lpszSrc += 2; /* Skip .. in src path */
2478       }
2479       else
2480         *lpszDst++ = *lpszSrc++;
2481     }
2482     else
2483       *lpszDst++ = *lpszSrc++;
2484   }
2485   /* Append \ to naked drive specs */
2486   if (lpszDst - lpszBuf == 2 && lpszDst[-1] == ':')
2487     *lpszDst++ = '\\';
2488   *lpszDst++ = '\0';
2489   return TRUE;
2490 }
2491 
2492 /*************************************************************************
2493  * PathFindNextComponentA   [SHLWAPI.@]
2494  *
2495  * Find the next component in a path.
2496  *
2497  * PARAMS
2498  *   lpszPath [I] Path to find next component in
2499  *
2500  * RETURNS
2501  *  Success: A pointer to the next component, or the end of the string.
2502  *  Failure: NULL, If lpszPath is invalid
2503  *
2504  * NOTES
2505  *  A 'component' is either a backslash character (\) or UNC marker (\\).
2506  *  Because of this, relative paths (e.g "c:foo") are regarded as having
2507  *  only one component.
2508  */
2509 LPSTR WINAPI PathFindNextComponentA(LPCSTR lpszPath)
2510 {
2511   LPSTR lpszSlash;
2512 
2513   TRACE("(%s)\n", debugstr_a(lpszPath));
2514 
2515   if(!lpszPath || !*lpszPath)
2516     return NULL;
2517 
2518   if ((lpszSlash = StrChrA(lpszPath, '\\')))
2519   {
2520     if (lpszSlash[1] == '\\')
2521       lpszSlash++;
2522     return lpszSlash + 1;
2523   }
2524   return (LPSTR)lpszPath + strlen(lpszPath);
2525 }
2526 
2527 /*************************************************************************
2528  * PathFindNextComponentW   [SHLWAPI.@]
2529  *
2530  * See PathFindNextComponentA.
2531  */
2532 LPWSTR WINAPI PathFindNextComponentW(LPCWSTR lpszPath)
2533 {
2534   LPWSTR lpszSlash;
2535 
2536   TRACE("(%s)\n", debugstr_w(lpszPath));
2537 
2538   if(!lpszPath || !*lpszPath)
2539     return NULL;
2540 
2541   if ((lpszSlash = StrChrW(lpszPath, '\\')))
2542   {
2543     if (lpszSlash[1] == '\\')
2544       lpszSlash++;
2545     return lpszSlash + 1;
2546   }
2547   return (LPWSTR)lpszPath + strlenW(lpszPath);
2548 }
2549 
2550 /*************************************************************************
2551  * PathAddExtensionA   [SHLWAPI.@]
2552  *
2553  * Add a file extension to a path
2554  *
2555  * PARAMS
2556  *  lpszPath      [I/O] Path to add extension to
2557  *  lpszExtension [I]   Extension to add to lpszPath
2558  *
2559  * RETURNS
2560  *  TRUE  If the path was modified,
2561  *  FALSE If lpszPath or lpszExtension are invalid, lpszPath has an
2562  *        extension already, or the new path length is too big.
2563  *
2564  * FIXME
2565  *  What version of shlwapi.dll adds "exe" if lpszExtension is NULL? Win2k
2566  *  does not do this, so the behaviour was removed.
2567  */
2568 BOOL WINAPI PathAddExtensionA(LPSTR lpszPath, LPCSTR lpszExtension)
2569 {
2570   size_t dwLen;
2571 
2572   TRACE("(%s,%s)\n", debugstr_a(lpszPath), debugstr_a(lpszExtension));
2573 
2574   if (!lpszPath || !lpszExtension || *(PathFindExtensionA(lpszPath)))
2575     return FALSE;
2576 
2577   dwLen = strlen(lpszPath);
2578 
2579   if (dwLen + strlen(lpszExtension) >= MAX_PATH)
2580     return FALSE;
2581 
2582   strcpy(lpszPath + dwLen, lpszExtension);
2583   return TRUE;
2584 }
2585 
2586 /*************************************************************************
2587  * PathAddExtensionW   [SHLWAPI.@]
2588  *
2589  * See PathAddExtensionA.
2590  */
2591 BOOL WINAPI PathAddExtensionW(LPWSTR lpszPath, LPCWSTR lpszExtension)
2592 {
2593   size_t dwLen;
2594 
2595   TRACE("(%s,%s)\n", debugstr_w(lpszPath), debugstr_w(lpszExtension));
2596 
2597   if (!lpszPath || !lpszExtension || *(PathFindExtensionW(lpszPath)))
2598     return FALSE;
2599 
2600   dwLen = strlenW(lpszPath);
2601 
2602   if (dwLen + strlenW(lpszExtension) >= MAX_PATH)
2603     return FALSE;
2604 
2605   strcpyW(lpszPath + dwLen, lpszExtension);
2606   return TRUE;
2607 }
2608 
2609 /*************************************************************************
2610  * PathMakePrettyA   [SHLWAPI.@]
2611  *
2612  * Convert an uppercase DOS filename into lowercase.
2613  *
2614  * PARAMS
2615  *  lpszPath [I/O] Path to convert.
2616  *
2617  * RETURNS
2618  *  TRUE  If the path was an uppercase DOS path and was converted,
2619  *  FALSE Otherwise.
2620  */
2621 BOOL WINAPI PathMakePrettyA(LPSTR lpszPath)
2622 {
2623   LPSTR pszIter = lpszPath;
2624 
2625   TRACE("(%s)\n", debugstr_a(lpszPath));
2626 
2627   if (!pszIter)
2628     return FALSE;
2629 
2630   if (*pszIter)
2631   {
2632     do
2633     {
2634       if (islower(*pszIter) || IsDBCSLeadByte(*pszIter))
2635         return FALSE; /* Not DOS path */
2636       pszIter++;
2637     } while (*pszIter);
2638     pszIter = lpszPath + 1;
2639     while (*pszIter)
2640     {
2641       *pszIter = tolower(*pszIter);
2642       pszIter++;
2643     }
2644   }
2645   return TRUE;
2646 }
2647 
2648 /*************************************************************************
2649  * PathMakePrettyW   [SHLWAPI.@]
2650  *
2651  * See PathMakePrettyA.
2652  */
2653 BOOL WINAPI PathMakePrettyW(LPWSTR lpszPath)
2654 {
2655   LPWSTR pszIter = lpszPath;
2656 
2657   TRACE("(%s)\n", debugstr_w(lpszPath));
2658 
2659   if (!pszIter)
2660     return FALSE;
2661 
2662   if (*pszIter)
2663   {
2664     do
2665     {
2666       if (islowerW(*pszIter))
2667         return FALSE; /* Not DOS path */
2668       pszIter++;
2669     } while (*pszIter);
2670     pszIter = lpszPath + 1;
2671     while (*pszIter)
2672     {
2673       *pszIter = tolowerW(*pszIter);
2674       pszIter++;
2675     }
2676   }
2677   return TRUE;
2678 }
2679 
2680 /*************************************************************************
2681  * PathCommonPrefixA   [SHLWAPI.@]
2682  *
2683  * Determine the length of the common prefix between two paths.
2684  *
2685  * PARAMS
2686  *  lpszFile1 [I] First path for comparison
2687  *  lpszFile2 [I] Second path for comparison
2688  *  achPath   [O] Destination for common prefix string
2689  *
2690  * RETURNS
2691  *  The length of the common prefix. This is 0 if there is no common
2692  *  prefix between the paths or if any parameters are invalid. If the prefix
2693  *  is non-zero and achPath is not NULL, achPath is filled with the common
2694  *  part of the prefix and NUL terminated.
2695  *
2696  * NOTES
2697  *  A common prefix of 2 is always returned as 3. It is thus possible for
2698  *  the length returned to be invalid (i.e. Longer than one or both of the
2699  *  strings given as parameters). This Win32 behaviour has been implemented
2700  *  here, and cannot be changed (fixed?) without breaking other SHLWAPI calls.
2701  *  To work around this when using this function, always check that the byte
2702  *  at [common_prefix_len-1] is not a NUL. If it is, deduct 1 from the prefix.
2703  */
2704 int WINAPI PathCommonPrefixA(LPCSTR lpszFile1, LPCSTR lpszFile2, LPSTR achPath)
2705 {
2706   size_t iLen = 0;
2707   LPCSTR lpszIter1 = lpszFile1;
2708   LPCSTR lpszIter2 = lpszFile2;
2709 
2710   TRACE("(%s,%s,%p)\n", debugstr_a(lpszFile1), debugstr_a(lpszFile2), achPath);
2711 
2712   if (achPath)
2713     *achPath = '\0';
2714 
2715   if (!lpszFile1 || !lpszFile2)
2716     return 0;
2717 
2718   /* Handle roots first */
2719   if (PathIsUNCA(lpszFile1))
2720   {
2721     if (!PathIsUNCA(lpszFile2))
2722       return 0;
2723     lpszIter1 += 2;
2724     lpszIter2 += 2;
2725   }
2726   else if (PathIsUNCA(lpszFile2))
2727       return 0; /* Know already lpszFile1 is not UNC */
2728 
2729   do
2730   {
2731     /* Update len */
2732     if ((!*lpszIter1 || *lpszIter1 == '\\') &&
2733         (!*lpszIter2 || *lpszIter2 == '\\'))
2734       iLen = lpszIter1 - lpszFile1; /* Common to this point */
2735 
2736     if (!*lpszIter1 || (tolower(*lpszIter1) != tolower(*lpszIter2)))
2737       break; /* Strings differ at this point */
2738 
2739     lpszIter1++;
2740     lpszIter2++;
2741   } while (1);
2742 
2743   if (iLen == 2)
2744     iLen++; /* Feature/Bug compatible with Win32 */
2745 
2746   if (iLen && achPath)
2747   {
2748     memcpy(achPath,lpszFile1,iLen);
2749     achPath[iLen] = '\0';
2750   }
2751   return iLen;
2752 }
2753 
2754 /*************************************************************************
2755  * PathCommonPrefixW   [SHLWAPI.@]
2756  *
2757  * See PathCommonPrefixA.
2758  */
2759 int WINAPI PathCommonPrefixW(LPCWSTR lpszFile1, LPCWSTR lpszFile2, LPWSTR achPath)
2760 {
2761   size_t iLen = 0;
2762   LPCWSTR lpszIter1 = lpszFile1;
2763   LPCWSTR lpszIter2 = lpszFile2;
2764 
2765   TRACE("(%s,%s,%p)\n", debugstr_w(lpszFile1), debugstr_w(lpszFile2), achPath);
2766 
2767   if (achPath)
2768     *achPath = '\0';
2769 
2770   if (!lpszFile1 || !lpszFile2)
2771     return 0;
2772 
2773   /* Handle roots first */
2774   if (PathIsUNCW(lpszFile1))
2775   {
2776     if (!PathIsUNCW(lpszFile2))
2777       return 0;
2778     lpszIter1 += 2;
2779     lpszIter2 += 2;
2780   }
2781   else if (PathIsUNCW(lpszFile2))
2782       return 0; /* Know already lpszFile1 is not UNC */
2783 
2784   do
2785   {
2786     /* Update len */
2787     if ((!*lpszIter1 || *lpszIter1 == '\\') &&
2788         (!*lpszIter2 || *lpszIter2 == '\\'))
2789       iLen = lpszIter1 - lpszFile1; /* Common to this point */
2790 
2791     if (!*lpszIter1 || (tolowerW(*lpszIter1) != tolowerW(*lpszIter2)))
2792       break; /* Strings differ at this point */
2793 
2794     lpszIter1++;
2795     lpszIter2++;
2796   } while (1);
2797 
2798   if (iLen == 2)
2799     iLen++; /* Feature/Bug compatible with Win32 */
2800 
2801   if (iLen && achPath)
2802   {
2803     memcpy(achPath,lpszFile1,iLen * sizeof(WCHAR));
2804     achPath[iLen] = '\0';
2805   }
2806   return iLen;
2807 }
2808 
2809 /*************************************************************************
2810  * PathCompactPathA   [SHLWAPI.@]
2811  *
2812  * Make a path fit into a given width when printed to a DC.
2813  *
2814  * PARAMS
2815  *  hDc      [I]   Destination DC
2816  *  lpszPath [I/O] Path to be printed to hDc
2817  *  dx       [I]   Desired width
2818  *
2819  * RETURNS
2820  *  TRUE  If the path was modified/went well.
2821  *  FALSE Otherwise.
2822  */
2823 BOOL WINAPI PathCompactPathA(HDC hDC, LPSTR lpszPath, UINT dx)
2824 {
2825   BOOL bRet = FALSE;
2826 
2827   TRACE("(%p,%s,%d)\n", hDC, debugstr_a(lpszPath), dx);
2828 
2829   if (lpszPath)
2830   {
2831     WCHAR szPath[MAX_PATH];
2832     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
2833     bRet = PathCompactPathW(hDC, szPath, dx);
2834     WideCharToMultiByte(CP_ACP,0,szPath,-1,lpszPath,MAX_PATH,0,0);
2835   }
2836   return bRet;
2837 }
2838 
2839 /*************************************************************************
2840  * PathCompactPathW   [SHLWAPI.@]
2841  *
2842  * See PathCompactPathA.
2843  */
2844 BOOL WINAPI PathCompactPathW(HDC hDC, LPWSTR lpszPath, UINT dx)
2845 {
2846   static const WCHAR szEllipses[] = { '.', '.', '.', '\0' };
2847   BOOL bRet = TRUE;
2848   HDC hdc = 0;
2849   WCHAR buff[MAX_PATH];
2850   SIZE size;
2851   DWORD dwLen;
2852 
2853   TRACE("(%p,%s,%d)\n", hDC, debugstr_w(lpszPath), dx);
2854 
2855   if (!lpszPath)
2856     return FALSE;
2857 
2858   if (!hDC)
2859     hdc = hDC = GetDC(0);
2860 
2861   /* Get the length of the whole path */
2862   dwLen = strlenW(lpszPath);
2863   GetTextExtentPointW(hDC, lpszPath, dwLen, &size);
2864 
2865   if ((UINT)size.cx > dx)
2866   {
2867     /* Path too big, must reduce it */
2868     LPWSTR sFile;
2869     DWORD dwEllipsesLen = 0, dwPathLen = 0;
2870 
2871     sFile = PathFindFileNameW(lpszPath);
2872     if (sFile != lpszPath) sFile--;
2873 
2874     /* Get the size of ellipses */
2875     GetTextExtentPointW(hDC, szEllipses, 3, &size);
2876     dwEllipsesLen = size.cx;
2877     /* Get the size of the file name */
2878     GetTextExtentPointW(hDC, sFile, strlenW(sFile), &size);
2879     dwPathLen = size.cx;
2880 
2881     if (sFile != lpszPath)
2882     {
2883       LPWSTR sPath = sFile;
2884       BOOL bEllipses = FALSE;
2885 
2886       /* The path includes a file name. Include as much of the path prior to
2887        * the file name as possible, allowing for the ellipses, e.g:
2888        * c:\some very long path\filename ==> c:\some v...\filename
2889        */
2890       lstrcpynW(buff, sFile, MAX_PATH);
2891 
2892       do
2893       {
2894         DWORD dwTotalLen = bEllipses? dwPathLen + dwEllipsesLen : dwPathLen;
2895 
2896         GetTextExtentPointW(hDC, lpszPath, sPath - lpszPath, &size);
2897         dwTotalLen += size.cx;
2898         if (dwTotalLen <= dx)
2899           break;
2900         sPath--;
2901         if (!bEllipses)
2902         {
2903           bEllipses = TRUE;
2904           sPath -= 2;
2905         }
2906       } while (sPath > lpszPath);
2907 
2908       if (sPath > lpszPath)
2909       {
2910         if (bEllipses)
2911         {
2912           strcpyW(sPath, szEllipses);
2913           strcpyW(sPath+3, buff);
2914         }
2915         bRet = TRUE;
2916         goto end;
2917       }
2918       strcpyW(lpszPath, szEllipses);
2919       strcpyW(lpszPath+3, buff);
2920       bRet = FALSE;
2921       goto end;
2922     }
2923 
2924     /* Trim the path by adding ellipses to the end, e.g:
2925      * A very long file name.txt ==> A very...
2926      */
2927     dwLen = strlenW(lpszPath);
2928 
2929     if (dwLen > MAX_PATH - 3)
2930       dwLen =  MAX_PATH - 3;
2931     lstrcpynW(buff, sFile, dwLen);
2932 
2933     do {
2934       dwLen--;
2935       GetTextExtentPointW(hDC, buff, dwLen, &size);
2936     } while (dwLen && size.cx + dwEllipsesLen > dx);
2937 
2938    if (!dwLen)
2939    {
2940      DWORD dwWritten = 0;
2941 
2942      dwEllipsesLen /= 3; /* Size of a single '.' */
2943 
2944      /* Write as much of the Ellipses string as possible */
2945      while (dwWritten + dwEllipsesLen < dx && dwLen < 3)
2946      {
2947        *lpszPath++ = '.';
2948        dwWritten += dwEllipsesLen;
2949        dwLen++;
2950      }
2951      *lpszPath = '\0';
2952      bRet = FALSE;
2953    }
2954    else
2955    {
2956      strcpyW(buff + dwLen, szEllipses);
2957      strcpyW(lpszPath, buff);
2958     }
2959   }
2960 
2961 end:
2962   if (hdc)
2963     ReleaseDC(0, hdc);
2964 
2965   return bRet;
2966 }
2967 
2968 /*************************************************************************
2969  * PathGetCharTypeA   [SHLWAPI.@]
2970  *
2971  * Categorise a character from a file path.
2972  *
2973  * PARAMS
2974  *  ch [I] Character to get the type of
2975  *
2976  * RETURNS
2977  *  A set of GCT_ bit flags (from "shlwapi.h") indicating the character type.
2978  */
2979 UINT WINAPI PathGetCharTypeA(UCHAR ch)
2980 {
2981   return PathGetCharTypeW(ch);
2982 }
2983 
2984 /*************************************************************************
2985  * PathGetCharTypeW   [SHLWAPI.@]
2986  *
2987  * See PathGetCharTypeA.
2988  */
2989 UINT WINAPI PathGetCharTypeW(WCHAR ch)
2990 {
2991   UINT flags = 0;
2992 
2993   TRACE("(%d)\n", ch);
2994 
2995   if (!ch || ch < ' ' || ch == '<' || ch == '>' ||
2996       ch == '"' || ch == '|' || ch == '/')
2997     flags = GCT_INVALID; /* Invalid */
2998   else if (ch == '*' || ch=='?')
2999     flags = GCT_WILD; /* Wildchars */
3000   else if ((ch == '\\') || (ch == ':'))
3001     return GCT_SEPARATOR; /* Path separators */
3002   else
3003   {
3004      if (ch < 126)
3005      {
3006          if (((ch & 0x1) && ch != ';') || !ch || isalnum(ch) || ch == '$' || ch == '&' || ch == '(' ||
3007             ch == '.' || ch == '@' || ch == '^' ||
3008             ch == '\'' || ch == 130 || ch == '`')
3009          flags |= GCT_SHORTCHAR; /* All these are valid for DOS */
3010      }
3011      else
3012        flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */
3013      flags |= GCT_LFNCHAR; /* Valid for long file names */
3014   }
3015   return flags;
3016 }
3017 
3018 /*************************************************************************
3019  * SHLWAPI_UseSystemForSystemFolders
3020  *
3021  * Internal helper for PathMakeSystemFolderW.
3022  */
3023 static BOOL SHLWAPI_UseSystemForSystemFolders(void)
3024 {
3025   static BOOL bCheckedReg = FALSE;
3026   static BOOL bUseSystemForSystemFolders = FALSE;
3027 
3028   if (!bCheckedReg)
3029   {
3030     bCheckedReg = TRUE;
3031 
3032     /* Key tells Win what file attributes to use on system folders */
3033     if (SHGetValueA(HKEY_LOCAL_MACHINE,
3034         "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer",
3035         "UseSystemForSystemFolders", 0, 0, 0))
3036       bUseSystemForSystemFolders = TRUE;
3037   }
3038   return bUseSystemForSystemFolders;
3039 }
3040 
3041 /*************************************************************************
3042  * PathMakeSystemFolderA   [SHLWAPI.@]
3043  *
3044  * Set system folder attribute for a path.
3045  *
3046  * PARAMS
3047  *  lpszPath [I] The path to turn into a system folder
3048  *
3049  * RETURNS
3050  *  TRUE  If the path was changed to/already was a system folder
3051  *  FALSE If the path is invalid or SetFileAttributesA() fails
3052  */
3053 BOOL WINAPI PathMakeSystemFolderA(LPCSTR lpszPath)
3054 {
3055   BOOL bRet = FALSE;
3056 
3057   TRACE("(%s)\n", debugstr_a(lpszPath));
3058 
3059   if (lpszPath && *lpszPath)
3060   {
3061     WCHAR szPath[MAX_PATH];
3062     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
3063     bRet = PathMakeSystemFolderW(szPath);
3064   }
3065   return bRet;
3066 }
3067 
3068 /*************************************************************************
3069  * PathMakeSystemFolderW   [SHLWAPI.@]
3070  *
3071  * See PathMakeSystemFolderA.
3072  */
3073 BOOL WINAPI PathMakeSystemFolderW(LPCWSTR lpszPath)
3074 {
3075   DWORD dwDefaultAttr = FILE_ATTRIBUTE_READONLY, dwAttr;
3076   WCHAR buff[MAX_PATH];
3077 
3078   TRACE("(%s)\n", debugstr_w(lpszPath));
3079 
3080   if (!lpszPath || !*lpszPath)
3081     return FALSE;
3082 
3083   /* If the directory is already a system directory, don't do anything */
3084   GetSystemDirectoryW(buff, MAX_PATH);
3085   if (!strcmpW(buff, lpszPath))
3086     return TRUE;
3087 
3088   GetWindowsDirectoryW(buff, MAX_PATH);
3089   if (!strcmpW(buff, lpszPath))
3090     return TRUE;
3091 
3092   /* "UseSystemForSystemFolders" Tells Win what attributes to use */
3093   if (SHLWAPI_UseSystemForSystemFolders())
3094     dwDefaultAttr = FILE_ATTRIBUTE_SYSTEM;
3095 
3096   if ((dwAttr = GetFileAttributesW(lpszPath)) == INVALID_FILE_ATTRIBUTES)
3097     return FALSE;
3098 
3099   /* Change file attributes to system attributes */
3100   dwAttr &= ~(FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_READONLY);
3101   return SetFileAttributesW(lpszPath, dwAttr | dwDefaultAttr);
3102 }
3103 
3104 /*************************************************************************
3105  * PathRenameExtensionA   [SHLWAPI.@]
3106  *
3107  * Swap the file extension in a path with another extension.
3108  *
3109  * PARAMS
3110  *  lpszPath [I/O] Path to swap the extension in
3111  *  lpszExt  [I]   The new extension
3112  *
3113  * RETURNS
3114  *  TRUE  if lpszPath was modified,
3115  *  FALSE if lpszPath or lpszExt is NULL, or the new path is too long
3116  */
3117 BOOL WINAPI PathRenameExtensionA(LPSTR lpszPath, LPCSTR lpszExt)
3118 {
3119   LPSTR lpszExtension;
3120 
3121   TRACE("(%s,%s)\n", debugstr_a(lpszPath), debugstr_a(lpszExt));
3122 
3123   lpszExtension = PathFindExtensionA(lpszPath);
3124 
3125   if (!lpszExtension || (lpszExtension - lpszPath + strlen(lpszExt) >= MAX_PATH))
3126     return FALSE;
3127 
3128   strcpy(lpszExtension, lpszExt);
3129   return TRUE;
3130 }
3131 
3132 /*************************************************************************
3133  * PathRenameExtensionW   [SHLWAPI.@]
3134  *
3135  * See PathRenameExtensionA.
3136  */
3137 BOOL WINAPI PathRenameExtensionW(LPWSTR lpszPath, LPCWSTR lpszExt)
3138 {
3139   LPWSTR lpszExtension;
3140 
3141   TRACE("(%s,%s)\n", debugstr_w(lpszPath), debugstr_w(lpszExt));
3142 
3143   lpszExtension = PathFindExtensionW(lpszPath);
3144 
3145   if (!lpszExtension || (lpszExtension - lpszPath + strlenW(lpszExt) >= MAX_PATH))
3146     return FALSE;
3147 
3148   strcpyW(lpszExtension, lpszExt);
3149   return TRUE;
3150 }
3151 
3152 /*************************************************************************
3153  * PathSearchAndQualifyA   [SHLWAPI.@]
3154  *
3155  * Determine if a given path is correct and fully qualified.
3156  *
3157  * PARAMS
3158  *  lpszPath [I] Path to check
3159  *  lpszBuf  [O] Output for correct path
3160  *  cchBuf   [I] Size of lpszBuf
3161  *
3162  * RETURNS
3163  *  Unknown.
3164  */
3165 BOOL WINAPI PathSearchAndQualifyA(LPCSTR lpszPath, LPSTR lpszBuf, UINT cchBuf)
3166 {
3167     TRACE("(%s,%p,0x%08x)\n", debugstr_a(lpszPath), lpszBuf, cchBuf);
3168 
3169     if(SearchPathA(NULL, lpszPath, NULL, cchBuf, lpszBuf, NULL))
3170         return TRUE;
3171     return !!GetFullPathNameA(lpszPath, cchBuf, lpszBuf, NULL);
3172 }
3173 
3174 /*************************************************************************
3175  * PathSearchAndQualifyW   [SHLWAPI.@]
3176  *
3177  * See PathSearchAndQualifyA.
3178  */
3179 BOOL WINAPI PathSearchAndQualifyW(LPCWSTR lpszPath, LPWSTR lpszBuf, UINT cchBuf)
3180 {
3181     TRACE("(%s,%p,0x%08x)\n", debugstr_w(lpszPath), lpszBuf, cchBuf);
3182 
3183     if(SearchPathW(NULL, lpszPath, NULL, cchBuf, lpszBuf, NULL))
3184         return TRUE;
3185     return !!GetFullPathNameW(lpszPath, cchBuf, lpszBuf, NULL);
3186 }
3187 
3188 /*************************************************************************
3189  * PathSkipRootA   [SHLWAPI.@]
3190  *
3191  * Return the portion of a path following the drive letter or mount point.
3192  *
3193  * PARAMS
3194  *  lpszPath [I] The path to skip on
3195  *
3196  * RETURNS
3197  *  Success: A pointer to the next character after the root.
3198  *  Failure: NULL, if lpszPath is invalid, has no root or is a multibyte string.
3199  */
3200 LPSTR WINAPI PathSkipRootA(LPCSTR lpszPath)
3201 {
3202   TRACE("(%s)\n", debugstr_a(lpszPath));
3203 
3204   if (!lpszPath || !*lpszPath)
3205     return NULL;
3206 
3207   if (*lpszPath == '\\' && lpszPath[1] == '\\')
3208   {
3209     /* Network share: skip share server and mount point */
3210     lpszPath += 2;
3211     if ((lpszPath = StrChrA(lpszPath, '\\')) &&
3212         (lpszPath = StrChrA(lpszPath + 1, '\\')))
3213       lpszPath++;
3214     return (LPSTR)lpszPath;
3215   }
3216 
3217   if (IsDBCSLeadByte(*lpszPath))
3218     return NULL;
3219 
3220   /* Check x:\ */
3221   if (lpszPath[0] && lpszPath[1] == ':' && lpszPath[2] == '\\')
3222     return (LPSTR)lpszPath + 3;
3223   return NULL;
3224 }
3225 
3226 /*************************************************************************
3227  * PathSkipRootW   [SHLWAPI.@]
3228  *
3229  * See PathSkipRootA.
3230  */
3231 LPWSTR WINAPI PathSkipRootW(LPCWSTR lpszPath)
3232 {
3233   TRACE("(%s)\n", debugstr_w(lpszPath));
3234 
3235   if (!lpszPath || !*lpszPath)
3236     return NULL;
3237 
3238   if (*lpszPath == '\\' && lpszPath[1] == '\\')
3239   {
3240     /* Network share: skip share server and mount point */
3241     lpszPath += 2;
3242     if ((lpszPath = StrChrW(lpszPath, '\\')) &&
3243         (lpszPath = StrChrW(lpszPath + 1, '\\')))
3244      lpszPath++;
3245     return (LPWSTR)lpszPath;
3246   }
3247 
3248   /* Check x:\ */
3249   if (lpszPath[0] && lpszPath[1] == ':' && lpszPath[2] == '\\')
3250     return (LPWSTR)lpszPath + 3;
3251   return NULL;
3252 }
3253 
3254 /*************************************************************************
3255  * PathCreateFromUrlA   [SHLWAPI.@]
3256  *
3257  * See PathCreateFromUrlW
3258  */
3259 HRESULT WINAPI PathCreateFromUrlA(LPCSTR pszUrl, LPSTR pszPath,
3260                                   LPDWORD pcchPath, DWORD dwReserved)
3261 {
3262     WCHAR bufW[MAX_PATH];
3263     WCHAR *pathW = bufW;
3264     UNICODE_STRING urlW;
3265     HRESULT ret;
3266     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
3267 
3268     if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
3269         return E_INVALIDARG;
3270 
3271     if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
3272         return E_INVALIDARG;
3273     if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
3274         pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
3275         ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
3276     }
3277     if(ret == S_OK) {
3278         RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
3279         if(*pcchPath > lenA) {
3280             RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
3281             pszPath[lenA] = 0;
3282             *pcchPath = lenA;
3283         } else {
3284             *pcchPath = lenA + 1;
3285             ret = E_POINTER;
3286         }
3287     }
3288     if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
3289     RtlFreeUnicodeString(&urlW);
3290     return ret;
3291 }
3292 
3293 /*************************************************************************
3294  * PathCreateFromUrlW   [SHLWAPI.@]
3295  *
3296  * Create a path from a URL
3297  *
3298  * PARAMS
3299  *  lpszUrl  [I] URL to convert into a path
3300  *  lpszPath [O] Output buffer for the resulting Path
3301  *  pcchPath [I] Length of lpszPath
3302  *  dwFlags  [I] Flags controlling the conversion
3303  *
3304  * RETURNS
3305  *  Success: S_OK. lpszPath contains the URL in path format,
3306  *  Failure: An HRESULT error code such as E_INVALIDARG.
3307  */
3308 HRESULT WINAPI PathCreateFromUrlW(LPCWSTR pszUrl, LPWSTR pszPath,
3309                                   LPDWORD pcchPath, DWORD dwReserved)
3310 {
3311     static const WCHAR file_colon[] = { 'f','i','l','e',':',0 };
3312     static const WCHAR localhost[] = { 'l','o','c','a','l','h','o','s','t',0 };
3313     DWORD nslashes, unescape, len;
3314     const WCHAR *src;
3315     WCHAR *tpath, *dst;
3316     HRESULT ret;
3317 
3318     TRACE("(%s,%p,%p,0x%08x)\n", debugstr_w(pszUrl), pszPath, pcchPath, dwReserved);
3319 
3320     if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
3321         return E_INVALIDARG;
3322 
3323     if (lstrlenW(pszUrl) < 5)
3324         return E_INVALIDARG;
3325 
3326     if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pszUrl, 5,
3327                        file_colon, 5) != CSTR_EQUAL)
3328         return E_INVALIDARG;
3329     pszUrl += 5;
3330     ret = S_OK;
3331 
3332     src = pszUrl;
3333     nslashes = 0;
3334     while (*src == '/' || *src == '\\') {
3335         nslashes++;
3336         src++;
3337     }
3338 
3339     /* We need a temporary buffer so we can compute what size to ask for.
3340      * We know that the final string won't be longer than the current pszUrl
3341      * plus at most two backslashes. All the other transformations make it
3342      * shorter.
3343      */
3344     len = 2 + lstrlenW(pszUrl) + 1;
3345     if (*pcchPath < len)
3346         tpath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
3347     else
3348         tpath = pszPath;
3349 
3350     len = 0;
3351     dst = tpath;
3352     unescape = 1;
3353     switch (nslashes)
3354     {
3355     case 0:
3356         /* 'file:' + escaped DOS path */
3357         break;
3358     case 1:
3359         /* 'file:/' + escaped DOS path */
3360         /* fall through */
3361     case 3:
3362         /* 'file:///' (implied localhost) + escaped DOS path */
3363         if (!isalphaW(*src) || (src[1] != ':' && src[1] != '|'))
3364             src -= 1;
3365         break;
3366     case 2:
3367         if (lstrlenW(src) >= 10 && CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE,
3368             src, 9, localhost, 9) == CSTR_EQUAL && (src[9] == '/' || src[9] == '\\'))
3369         {
3370             /* 'file://localhost/' + escaped DOS path */
3371             src += 10;
3372         }
3373         else if (isalphaW(*src) && (src[1] == ':' || src[1] == '|'))
3374         {
3375             /* 'file://' + unescaped DOS path */
3376             unescape = 0;
3377         }
3378         else
3379         {
3380             /*    'file://hostname:port/path' (where path is escaped)
3381              * or 'file:' + escaped UNC path (\\server\share\path)
3382              * The second form is clearly specific to Windows and it might
3383              * even be doing a network lookup to try to figure it out.
3384              */
3385             while (*src && *src != '/' && *src != '\\')
3386                 src++;
3387             len = src - pszUrl;
3388             StrCpyNW(dst, pszUrl, len + 1);
3389             dst += len;
3390             if (*src && isalphaW(src[1]) && (src[2] == ':' || src[2] == '|'))
3391             {
3392                 /* 'Forget' to add a trailing '/', just like Windows */
3393                 src++;
3394             }
3395         }
3396         break;
3397     case 4:
3398         /* 'file://' + unescaped UNC path (\\server\share\path) */
3399         unescape = 0;
3400         if (isalphaW(*src) && (src[1] == ':' || src[1] == '|'))
3401             break;
3402         /* fall through */
3403     default:
3404         /* 'file:/...' + escaped UNC path (\\server\share\path) */
3405         src -= 2;
3406     }
3407 
3408     /* Copy the remainder of the path */
3409     len += lstrlenW(src);
3410     StrCpyW(dst, src);
3411 
3412      /* First do the Windows-specific path conversions */
3413     for (dst = tpath; *dst; dst++)
3414         if (*dst == '/') *dst = '\\';
3415     if (isalphaW(*tpath) && tpath[1] == '|')
3416         tpath[1] = ':'; /* c| -> c: */
3417 
3418     /* And only then unescape the path (i.e. escaped slashes are left as is) */
3419     if (unescape)
3420     {
3421         ret = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3422         if (ret == S_OK)
3423         {
3424             /* When working in-place UrlUnescapeW() does not set len */
3425             len = lstrlenW(tpath);
3426         }
3427     }
3428 
3429     if (*pcchPath < len + 1)
3430     {
3431         ret = E_POINTER;
3432         *pcchPath = len + 1;
3433     }
3434     else
3435     {
3436         *pcchPath = len;
3437         if (tpath != pszPath)
3438             StrCpyW(pszPath, tpath);
3439     }
3440     if (tpath != pszPath)
3441       HeapFree(GetProcessHeap(), 0, tpath);
3442 
3443     TRACE("Returning (%u) %s\n", *pcchPath, debugstr_w(pszPath));
3444     return ret;
3445 }
3446 
3447 /*************************************************************************
3448  * PathCreateFromUrlAlloc   [SHLWAPI.@]
3449  */
3450 HRESULT WINAPI PathCreateFromUrlAlloc(LPCWSTR pszUrl, LPWSTR *pszPath,
3451                                       DWORD dwReserved)
3452 {
3453     WCHAR pathW[MAX_PATH];
3454     DWORD size;
3455     HRESULT hr;
3456 
3457     size = MAX_PATH;
3458     hr = PathCreateFromUrlW(pszUrl, pathW, &size, dwReserved);
3459     if (SUCCEEDED(hr))
3460     {
3461         /* Yes, this is supposed to crash if pszPath is NULL */
3462         *pszPath = StrDupW(pathW);
3463     }
3464     return hr;
3465 }
3466 
3467 /*************************************************************************
3468  * PathRelativePathToA   [SHLWAPI.@]
3469  *
3470  * Create a relative path from one path to another.
3471  *
3472  * PARAMS
3473  *  lpszPath   [O] Destination for relative path
3474  *  lpszFrom   [I] Source path
3475  *  dwAttrFrom [I] File attribute of source path
3476  *  lpszTo     [I] Destination path
3477  *  dwAttrTo   [I] File attributes of destination path
3478  *
3479  * RETURNS
3480  *  TRUE  If a relative path can be formed. lpszPath contains the new path
3481  *  FALSE If the paths are not relative or any parameters are invalid
3482  *
3483  * NOTES
3484  *  lpszTo should be at least MAX_PATH in length.
3485  *
3486  *  Calling this function with relative paths for lpszFrom or lpszTo may
3487  *  give erroneous results.
3488  *
3489  *  The Win32 version of this function contains a bug where the lpszTo string
3490  *  may be referenced 1 byte beyond the end of the string. As a result random
3491  *  garbage may be written to the output path, depending on what lies beyond
3492  *  the last byte of the string. This bug occurs because of the behaviour of
3493  *  PathCommonPrefix() (see notes for that function), and no workaround seems
3494  *  possible with Win32.
3495  *
3496  *  This bug has been fixed here, so for example the relative path from "\\"
3497  *  to "\\" is correctly determined as "." in this implementation.
3498  */
3499 BOOL WINAPI PathRelativePathToA(LPSTR lpszPath, LPCSTR lpszFrom, DWORD dwAttrFrom,
3500                                 LPCSTR lpszTo, DWORD dwAttrTo)
3501 {
3502   BOOL bRet = FALSE;
3503 
3504   TRACE("(%p,%s,0x%08x,%s,0x%08x)\n", lpszPath, debugstr_a(lpszFrom),
3505         dwAttrFrom, debugstr_a(lpszTo), dwAttrTo);
3506 
3507   if(lpszPath && lpszFrom && lpszTo)
3508   {
3509     WCHAR szPath[MAX_PATH];
3510     WCHAR szFrom[MAX_PATH];
3511     WCHAR szTo[MAX_PATH];
3512     MultiByteToWideChar(CP_ACP,0,lpszFrom,-1,szFrom,MAX_PATH);
3513     MultiByteToWideChar(CP_ACP,0,lpszTo,-1,szTo,MAX_PATH);
3514     bRet = PathRelativePathToW(szPath,szFrom,dwAttrFrom,szTo,dwAttrTo);
3515     WideCharToMultiByte(CP_ACP,0,szPath,-1,lpszPath,MAX_PATH,0,0);
3516   }
3517   return bRet;
3518 }
3519 
3520 /*************************************************************************
3521  * PathRelativePathToW   [SHLWAPI.@]
3522  *
3523  * See PathRelativePathToA.
3524  */
3525 BOOL WINAPI PathRelativePathToW(LPWSTR lpszPath, LPCWSTR lpszFrom, DWORD dwAttrFrom,
3526                                 LPCWSTR lpszTo, DWORD dwAttrTo)
3527 {
3528   static const WCHAR szPrevDirSlash[] = { '.', '.', '\\', '\0' };
3529   static const WCHAR szPrevDir[] = { '.', '.', '\0' };
3530   WCHAR szFrom[MAX_PATH];
3531   WCHAR szTo[MAX_PATH];
3532   DWORD dwLen;
3533 
3534   TRACE("(%p,%s,0x%08x,%s,0x%08x)\n", lpszPath, debugstr_w(lpszFrom),
3535         dwAttrFrom, debugstr_w(lpszTo), dwAttrTo);
3536 
3537   if(!lpszPath || !lpszFrom || !lpszTo)
3538     return FALSE;
3539 
3540   *lpszPath = '\0';
3541   lstrcpynW(szFrom, lpszFrom, MAX_PATH);
3542   lstrcpynW(szTo, lpszTo, MAX_PATH);
3543 
3544   if(!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
3545     PathRemoveFileSpecW(szFrom);
3546   if(!(dwAttrTo & FILE_ATTRIBUTE_DIRECTORY))
3547     PathRemoveFileSpecW(szTo);
3548 
3549   /* Paths can only be relative if they have a common root */
3550   if(!(dwLen = PathCommonPrefixW(szFrom, szTo, 0)))
3551     return FALSE;
3552 
3553   /* Strip off lpszFrom components to the root, by adding "..\" */
3554   lpszFrom = szFrom + dwLen;
3555   if (!*lpszFrom)
3556   {
3557     lpszPath[0] = '.';
3558     lpszPath[1] = '\0';
3559   }
3560   if (*lpszFrom == '\\')
3561     lpszFrom++;
3562 
3563   while (*lpszFrom)
3564   {
3565     lpszFrom = PathFindNextComponentW(lpszFrom);
3566     strcatW(lpszPath, *lpszFrom ? szPrevDirSlash : szPrevDir);
3567   }
3568 
3569   /* From the root add the components of lpszTo */
3570   lpszTo += dwLen;
3571   /* We check lpszTo[-1] to avoid skipping end of string. See the notes for
3572    * this function.
3573    */
3574   if (*lpszTo && lpszTo[-1])
3575   {
3576     if (*lpszTo != '\\')
3577       lpszTo--;
3578     dwLen = strlenW(lpszPath);
3579     if (dwLen + strlenW(lpszTo) >= MAX_PATH)
3580     {
3581       *lpszPath = '\0';
3582       return FALSE;
3583     }
3584     strcpyW(lpszPath + dwLen, lpszTo);
3585   }
3586   return TRUE;
3587 }
3588 
3589 /*************************************************************************
3590  * PathUnmakeSystemFolderA   [SHLWAPI.@]
3591  *
3592  * Remove the system folder attributes from a path.
3593  *
3594  * PARAMS
3595  *  lpszPath [I] The path to remove attributes from
3596  *
3597  * RETURNS
3598  *  Success: TRUE.
3599  *  Failure: FALSE, if lpszPath is NULL, empty, not a directory, or calling
3600  *           SetFileAttributesA() fails.
3601  */
3602 BOOL WINAPI PathUnmakeSystemFolderA(LPCSTR lpszPath)
3603 {
3604   DWORD dwAttr;
3605 
3606   TRACE("(%s)\n", debugstr_a(lpszPath));
3607 
3608   if (!lpszPath || !*lpszPath || (dwAttr = GetFileAttributesA(lpszPath)) == INVALID_FILE_ATTRIBUTES ||
3609       !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
3610     return FALSE;
3611 
3612   dwAttr &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
3613   return SetFileAttributesA(lpszPath, dwAttr);
3614 }
3615 
3616 /*************************************************************************
3617  * PathUnmakeSystemFolderW   [SHLWAPI.@]
3618  *
3619  * See PathUnmakeSystemFolderA.
3620  */
3621 BOOL WINAPI PathUnmakeSystemFolderW(LPCWSTR lpszPath)
3622 {
3623   DWORD dwAttr;
3624 
3625   TRACE("(%s)\n", debugstr_w(lpszPath));
3626 
3627   if (!lpszPath || !*lpszPath || (dwAttr = GetFileAttributesW(lpszPath)) == INVALID_FILE_ATTRIBUTES ||
3628     !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
3629     return FALSE;
3630 
3631   dwAttr &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
3632   return SetFileAttributesW(lpszPath, dwAttr);
3633 }
3634 
3635 
3636 /*************************************************************************
3637  * PathSetDlgItemPathA   [SHLWAPI.@]
3638  *
3639  * Set the text of a dialog item to a path, shrinking the path to fit
3640  * if it is too big for the item.
3641  *
3642  * PARAMS
3643  *  hDlg     [I] Dialog handle
3644  *  id       [I] ID of item in the dialog
3645  *  lpszPath [I] Path to set as the items text
3646  *
3647  * RETURNS
3648  *  Nothing.
3649  *
3650  * NOTES
3651  *  If lpszPath is NULL, a blank string ("") is set (i.e. The previous
3652  *  window text is erased).
3653  */
3654 VOID WINAPI PathSetDlgItemPathA(HWND hDlg, int id, LPCSTR lpszPath)
3655 {
3656   WCHAR szPath[MAX_PATH];
3657 
3658   TRACE("(%p,%8x,%s)\n",hDlg, id, debugstr_a(lpszPath));
3659 
3660   if (lpszPath)
3661     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
3662   else
3663     szPath[0] = '\0';
3664   PathSetDlgItemPathW(hDlg, id, szPath);
3665 }
3666 
3667 /*************************************************************************
3668  * PathSetDlgItemPathW   [SHLWAPI.@]
3669  *
3670  * See PathSetDlgItemPathA.
3671  */
3672 VOID WINAPI PathSetDlgItemPathW(HWND hDlg, int id, LPCWSTR lpszPath)
3673 {
3674   WCHAR path[MAX_PATH + 1];
3675   HWND hwItem;
3676   RECT rect;
3677   HDC hdc;
3678   HGDIOBJ hPrevObj;
3679 
3680   TRACE("(%p,%8x,%s)\n",hDlg, id, debugstr_w(lpszPath));
3681 
3682   if (!(hwItem = GetDlgItem(hDlg, id)))
3683     return;
3684 
3685   if (lpszPath)
3686     lstrcpynW(path, lpszPath, sizeof(path) / sizeof(WCHAR));
3687   else
3688     path[0] = '\0';
3689 
3690   GetClientRect(hwItem, &rect);
3691   hdc = GetDC(hDlg);
3692   hPrevObj = SelectObject(hdc, (HGDIOBJ)SendMessageW(hwItem,WM_GETFONT,0,0));
3693 
3694   if (hPrevObj)
3695   {
3696     PathCompactPathW(hdc, path, rect.right);
3697     SelectObject(hdc, hPrevObj);
3698   }
3699 
3700   ReleaseDC(hDlg, hdc);
3701   SetWindowTextW(hwItem, path);
3702 }
3703 
3704 /*************************************************************************
3705  * PathIsNetworkPathA [SHLWAPI.@]
3706  *
3707  * Determine if the given path is a network path.
3708  *
3709  * PARAMS
3710  *  lpszPath [I] Path to check
3711  *
3712  * RETURNS
3713  *  TRUE  If lpszPath is a UNC share or mapped network drive, or
3714  *  FALSE If lpszPath is a local drive or cannot be determined
3715  */
3716 BOOL WINAPI PathIsNetworkPathA(LPCSTR lpszPath)
3717 {
3718   int dwDriveNum;
3719 
3720   TRACE("(%s)\n",debugstr_a(lpszPath));
3721 
3722   if (!lpszPath)
3723     return FALSE;
3724   if (*lpszPath == '\\' && lpszPath[1] == '\\')
3725     return TRUE;
3726   dwDriveNum = PathGetDriveNumberA(lpszPath);
3727   if (dwDriveNum == -1)
3728     return FALSE;
3729 #ifdef __REACTOS__
3730   return IsNetDrive(dwDriveNum);
3731 #else
3732   GET_FUNC(pIsNetDrive, shell32, (LPCSTR)66, FALSE); /* ord 66 = shell32.IsNetDrive */
3733   return pIsNetDrive(dwDriveNum);
3734 #endif
3735 }
3736 
3737 /*************************************************************************
3738  * PathIsNetworkPathW [SHLWAPI.@]
3739  *
3740  * See PathIsNetworkPathA.
3741  */
3742 BOOL WINAPI PathIsNetworkPathW(LPCWSTR lpszPath)
3743 {
3744   int dwDriveNum;
3745 
3746   TRACE("(%s)\n", debugstr_w(lpszPath));
3747 
3748   if (!lpszPath)
3749     return FALSE;
3750   if (*lpszPath == '\\' && lpszPath[1] == '\\')
3751     return TRUE;
3752   dwDriveNum = PathGetDriveNumberW(lpszPath);
3753   if (dwDriveNum == -1)
3754     return FALSE;
3755 #ifdef __REACTOS__
3756   return IsNetDrive(dwDriveNum);
3757 #else
3758   GET_FUNC(pIsNetDrive, shell32, (LPCSTR)66, FALSE); /* ord 66 = shell32.IsNetDrive */
3759   return pIsNetDrive(dwDriveNum);
3760 #endif
3761 }
3762 
3763 /*************************************************************************
3764  * PathIsLFNFileSpecA [SHLWAPI.@]
3765  *
3766  * Determine if the given path is a long file name
3767  *
3768  * PARAMS
3769  *  lpszPath [I] Path to check
3770  *
3771  * RETURNS
3772  *  TRUE  If path is a long file name,
3773  *  FALSE If path is a valid DOS short file name
3774  */
3775 BOOL WINAPI PathIsLFNFileSpecA(LPCSTR lpszPath)
3776 {
3777   DWORD dwNameLen = 0, dwExtLen = 0;
3778 
3779   TRACE("(%s)\n",debugstr_a(lpszPath));
3780 
3781   if (!lpszPath)
3782     return FALSE;
3783 
3784   while (*lpszPath)
3785   {
3786     if (*lpszPath == ' ')
3787       return TRUE; /* DOS names cannot have spaces */
3788     if (*lpszPath == '.')
3789     {
3790       if (dwExtLen)
3791         return TRUE; /* DOS names have only one dot */
3792       dwExtLen = 1;
3793     }
3794     else if (dwExtLen)
3795     {
3796       dwExtLen++;
3797       if (dwExtLen > 4)
3798         return TRUE; /* DOS extensions are <= 3 chars*/
3799     }
3800     else
3801     {
3802       dwNameLen++;
3803       if (dwNameLen > 8)
3804         return TRUE; /* DOS names are <= 8 chars */
3805     }
3806     lpszPath += IsDBCSLeadByte(*lpszPath) ? 2 : 1;
3807   }
3808   return FALSE; /* Valid DOS path */
3809 }
3810 
3811 /*************************************************************************
3812  * PathIsLFNFileSpecW [SHLWAPI.@]
3813  *
3814  * See PathIsLFNFileSpecA.
3815  */
3816 BOOL WINAPI PathIsLFNFileSpecW(LPCWSTR lpszPath)
3817 {
3818   DWORD dwNameLen = 0, dwExtLen = 0;
3819 
3820   TRACE("(%s)\n",debugstr_w(lpszPath));
3821 
3822   if (!lpszPath)
3823     return FALSE;
3824 
3825   while (*lpszPath)
3826   {
3827     if (*lpszPath == ' ')
3828       return TRUE; /* DOS names cannot have spaces */
3829     if (*lpszPath == '.')
3830     {
3831       if (dwExtLen)
3832         return TRUE; /* DOS names have only one dot */
3833       dwExtLen = 1;
3834     }
3835     else if (dwExtLen)
3836     {
3837       dwExtLen++;
3838       if (dwExtLen > 4)
3839         return TRUE; /* DOS extensions are <= 3 chars*/
3840     }
3841     else
3842     {
3843       dwNameLen++;
3844       if (dwNameLen > 8)
3845         return TRUE; /* DOS names are <= 8 chars */
3846     }
3847     lpszPath++;
3848   }
3849   return FALSE; /* Valid DOS path */
3850 }
3851 
3852 /*************************************************************************
3853  * PathIsDirectoryEmptyA [SHLWAPI.@]
3854  *
3855  * Determine if a given directory is empty.
3856  *
3857  * PARAMS
3858  *  lpszPath [I] Directory to check
3859  *
3860  * RETURNS
3861  *  TRUE  If the directory exists and contains no files,
3862  *  FALSE Otherwise
3863  */
3864 BOOL WINAPI PathIsDirectoryEmptyA(LPCSTR lpszPath)
3865 {
3866   BOOL bRet = FALSE;
3867 
3868   TRACE("(%s)\n",debugstr_a(lpszPath));
3869 
3870   if (lpszPath)
3871   {
3872     WCHAR szPath[MAX_PATH];
3873     MultiByteToWideChar(CP_ACP,0,lpszPath,-1,szPath,MAX_PATH);
3874     bRet = PathIsDirectoryEmptyW(szPath);
3875   }
3876   return bRet;
3877 }
3878 
3879 /*************************************************************************
3880  * PathIsDirectoryEmptyW [SHLWAPI.@]
3881  *
3882  * See PathIsDirectoryEmptyA.
3883  */
3884 BOOL WINAPI PathIsDirectoryEmptyW(LPCWSTR lpszPath)
3885 {
3886   static const WCHAR szAllFiles[] = { '*', '.', '*', '\0' };
3887   WCHAR szSearch[MAX_PATH];
3888   DWORD dwLen;
3889   HANDLE hfind;
3890   BOOL retVal = TRUE;
3891   WIN32_FIND_DATAW find_data;
3892 
3893   TRACE("(%s)\n",debugstr_w(lpszPath));
3894 
3895   if (!lpszPath || !PathIsDirectoryW(lpszPath))
3896     return FALSE;
3897 
3898   lstrcpynW(szSearch, lpszPath, MAX_PATH);
3899   PathAddBackslashW(szSearch);
3900   dwLen = strlenW(szSearch);
3901   if (dwLen > MAX_PATH - 4)
3902     return FALSE;
3903 
3904   strcpyW(szSearch + dwLen, szAllFiles);
3905   hfind = FindFirstFileW(szSearch, &find_data);
3906   if (hfind == INVALID_HANDLE_VALUE)
3907     return FALSE;
3908 
3909   do
3910   {
3911     if (find_data.cFileName[0] == '.')
3912     {
3913       if (find_data.cFileName[1] == '\0') continue;
3914       if (find_data.cFileName[1] == '.' && find_data.cFileName[2] == '\0') continue;
3915     }
3916 
3917     retVal = FALSE;
3918     break;
3919   }
3920   while (FindNextFileW(hfind, &find_data));
3921 
3922   FindClose(hfind);
3923   return retVal;
3924 }
3925 
3926 
3927 /*************************************************************************
3928  * PathFindSuffixArrayA [SHLWAPI.@]
3929  *
3930  * Find a suffix string in an array of suffix strings
3931  *
3932  * PARAMS
3933  *  lpszSuffix [I] Suffix string to search for
3934  *  lppszArray [I] Array of suffix strings to search
3935  *  dwCount    [I] Number of elements in lppszArray
3936  *
3937  * RETURNS
3938  *  Success: The index of the position of lpszSuffix in lppszArray
3939  *  Failure: 0, if any parameters are invalid or lpszSuffix is not found
3940  *
3941  * NOTES
3942  *  The search is case sensitive.
3943  *  The match is made against the end of the suffix string, so for example:
3944  *  lpszSuffix="fooBAR" matches "BAR", but lpszSuffix="fooBARfoo" does not.
3945  */
3946 LPCSTR WINAPI PathFindSuffixArrayA(LPCSTR lpszSuffix, LPCSTR *lppszArray, int dwCount)
3947 {
3948   size_t dwLen;
3949   int dwRet = 0;
3950 
3951   TRACE("(%s,%p,%d)\n",debugstr_a(lpszSuffix), lppszArray, dwCount);
3952 
3953   if (lpszSuffix && lppszArray && dwCount > 0)
3954   {
3955     dwLen = strlen(lpszSuffix);
3956 
3957     while (dwRet < dwCount)
3958     {
3959       size_t dwCompareLen = strlen(*lppszArray);
3960       if (dwCompareLen < dwLen)
3961       {
3962         if (!strcmp(lpszSuffix + dwLen - dwCompareLen, *lppszArray))
3963           return *lppszArray; /* Found */
3964       }
3965       dwRet++;
3966       lppszArray++;
3967     }
3968   }
3969   return NULL;
3970 }
3971 
3972 /*************************************************************************
3973  * PathFindSuffixArrayW [SHLWAPI.@]
3974  *
3975  * See PathFindSuffixArrayA.
3976  */
3977 LPCWSTR WINAPI PathFindSuffixArrayW(LPCWSTR lpszSuffix, LPCWSTR *lppszArray, int dwCount)
3978 {
3979   size_t dwLen;
3980   int dwRet = 0;
3981 
3982   TRACE("(%s,%p,%d)\n",debugstr_w(lpszSuffix), lppszArray, dwCount);
3983 
3984   if (lpszSuffix && lppszArray && dwCount > 0)
3985   {
3986     dwLen = strlenW(lpszSuffix);
3987 
3988     while (dwRet < dwCount)
3989     {
3990       size_t dwCompareLen = strlenW(*lppszArray);
3991       if (dwCompareLen < dwLen)
3992       {
3993         if (!strcmpW(lpszSuffix + dwLen - dwCompareLen, *lppszArray))
3994           return *lppszArray; /* Found */
3995       }
3996       dwRet++;
3997       lppszArray++;
3998     }
3999   }
4000   return NULL;
4001 }
4002 
4003 /*************************************************************************
4004  * PathUndecorateA [SHLWAPI.@]
4005  *
4006  * Undecorate a file path
4007  *
4008  * PARAMS
4009  *  lpszPath [I/O] Path to remove any decoration from
4010  *
4011  * RETURNS
4012  *  Nothing
4013  *
4014  * NOTES
4015  *  A decorations form is "path[n].ext" where "n" is an optional decimal number.
4016  */
4017 VOID WINAPI PathUndecorateA(LPSTR lpszPath)
4018 {
4019   TRACE("(%s)\n",debugstr_a(lpszPath));
4020 
4021   if (lpszPath)
4022   {
4023     LPSTR lpszExt = PathFindExtensionA(lpszPath);
4024     if (lpszExt > lpszPath && lpszExt[-1] == ']')
4025     {
4026       LPSTR lpszSkip = lpszExt - 2;
4027       if (*lpszSkip == '[')
4028         lpszSkip++;  /* [] (no number) */
4029       else
4030         while (lpszSkip > lpszPath && isdigit(lpszSkip[-1]))
4031           lpszSkip--;
4032       if (lpszSkip > lpszPath && lpszSkip[-1] == '[' && lpszSkip[-2] != '\\')
4033       {
4034         /* remove the [n] */
4035         lpszSkip--;
4036         while (*lpszExt)
4037           *lpszSkip++ = *lpszExt++;
4038         *lpszSkip = '\0';
4039       }
4040     }
4041   }
4042 }
4043 
4044 /*************************************************************************
4045  * PathUndecorateW [SHLWAPI.@]
4046  *
4047  * See PathUndecorateA.
4048  */
4049 VOID WINAPI PathUndecorateW(LPWSTR lpszPath)
4050 {
4051   TRACE("(%s)\n",debugstr_w(lpszPath));
4052 
4053   if (lpszPath)
4054   {
4055     LPWSTR lpszExt = PathFindExtensionW(lpszPath);
4056     if (lpszExt > lpszPath && lpszExt[-1] == ']')
4057     {
4058       LPWSTR lpszSkip = lpszExt - 2;
4059       if (*lpszSkip == '[')
4060         lpszSkip++; /* [] (no number) */
4061       else
4062         while (lpszSkip > lpszPath && isdigitW(lpszSkip[-1]))
4063           lpszSkip--;
4064       if (lpszSkip > lpszPath && lpszSkip[-1] == '[' && lpszSkip[-2] != '\\')
4065       {
4066         /* remove the [n] */
4067         lpszSkip--;
4068         while (*lpszExt)
4069           *lpszSkip++ = *lpszExt++;
4070         *lpszSkip = '\0';
4071       }
4072     }
4073   }
4074 }
4075 
4076 /*************************************************************************
4077  * PathUnExpandEnvStringsA [SHLWAPI.@]
4078  *
4079  * Substitute folder names in a path with their corresponding environment
4080  * strings.
4081  *
4082  * PARAMS
4083  *  path    [I] Buffer containing the path to unexpand.
4084  *  buffer  [O] Buffer to receive the unexpanded path.
4085  *  buf_len [I] Size of pszBuf in characters.
4086  *
4087  * RETURNS
4088  *  Success: TRUE
4089  *  Failure: FALSE
4090  */
4091 BOOL WINAPI PathUnExpandEnvStringsA(LPCSTR path, LPSTR buffer, UINT buf_len)
4092 {
4093     WCHAR bufferW[MAX_PATH], *pathW;
4094     DWORD len;
4095     BOOL ret;
4096 
4097     TRACE("(%s, %p, %d)\n", debugstr_a(path), buffer, buf_len);
4098 
4099     pathW = heap_strdupAtoW(path);
4100     if (!pathW) return FALSE;
4101 
4102     ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
4103     HeapFree(GetProcessHeap(), 0, pathW);
4104     if (!ret) return FALSE;
4105 
4106     len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
4107     if (buf_len < len + 1) return FALSE;
4108 
4109     WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
4110     return TRUE;
4111 }
4112 
4113 static const WCHAR allusersprofileW[] = {'%','A','L','L','U','S','E','R','S','P','R','O','F','I','L','E','%',0};
4114 static const WCHAR appdataW[] = {'%','A','P','P','D','A','T','A','%',0};
4115 static const WCHAR computernameW[] = {'%','C','O','M','P','U','T','E','R','N','A','M','E','%',0};
4116 static const WCHAR programfilesW[] = {'%','P','r','o','g','r','a','m','F','i','l','e','s','%',0};
4117 static const WCHAR systemrootW[] = {'%','S','y','s','t','e','m','R','o','o','t','%',0};
4118 static const WCHAR systemdriveW[] = {'%','S','y','s','t','e','m','D','r','i','v','e','%',0};
4119 static const WCHAR userprofileW[] = {'%','U','S','E','R','P','R','O','F','I','L','E','%',0};
4120 
4121 struct envvars_map
4122 {
4123     const WCHAR *var;
4124     UINT  varlen;
4125     WCHAR path[MAX_PATH];
4126     DWORD len;
4127 };
4128 
4129 static void init_envvars_map(struct envvars_map *map)
4130 {
4131     while (map->var)
4132     {
4133         map->len = ExpandEnvironmentStringsW(map->var, map->path, sizeof(map->path)/sizeof(WCHAR));
4134         /* exclude null from length */
4135         if (map->len) map->len--;
4136         map++;
4137     }
4138 }
4139 
4140 /*************************************************************************
4141  * PathUnExpandEnvStringsW [SHLWAPI.@]
4142  *
4143  * Unicode version of PathUnExpandEnvStringsA.
4144  */
4145 BOOL WINAPI PathUnExpandEnvStringsW(LPCWSTR path, LPWSTR buffer, UINT buf_len)
4146 {
4147     static struct envvars_map null_var = {NULL, 0, {0}, 0};
4148     struct envvars_map *match = &null_var, *cur;
4149     struct envvars_map envvars[] = {
4150         { allusersprofileW, sizeof(allusersprofileW)/sizeof(WCHAR) },
4151         { appdataW,         sizeof(appdataW)/sizeof(WCHAR)         },
4152         { computernameW,    sizeof(computernameW)/sizeof(WCHAR)    },
4153         { programfilesW,    sizeof(programfilesW)/sizeof(WCHAR)    },
4154         { systemrootW,      sizeof(systemrootW)/sizeof(WCHAR)      },
4155         { systemdriveW,     sizeof(systemdriveW)/sizeof(WCHAR)     },
4156         { userprofileW,     sizeof(userprofileW)/sizeof(WCHAR)     },
4157         { NULL }
4158     };
4159     DWORD pathlen;
4160     UINT  needed;
4161 
4162     TRACE("(%s, %p, %d)\n", debugstr_w(path), buffer, buf_len);
4163 
4164     pathlen = strlenW(path);
4165     init_envvars_map(envvars);
4166     cur = envvars;
4167     while (cur->var)
4168     {
4169         /* path can't contain expanded value or value wasn't retrieved */
4170         if (cur->len == 0 || cur->len > pathlen || strncmpiW(cur->path, path, cur->len))
4171         {
4172             cur++;
4173             continue;
4174         }
4175 
4176         if (cur->len > match->len)
4177             match = cur;
4178         cur++;
4179     }
4180 
4181     /* 'varlen' includes NULL termination char */
4182     needed = match->varlen + pathlen - match->len;
4183     if (match->len == 0 || needed > buf_len) return FALSE;
4184 
4185     strcpyW(buffer, match->var);
4186     strcatW(buffer, &path[match->len]);
4187     TRACE("ret %s\n", debugstr_w(buffer));
4188 
4189     return TRUE;
4190 }
4191 
4192 /*************************************************************************
4193  * @     [SHLWAPI.440]
4194  *
4195  * Find localised or default web content in "%WINDOWS%\web\".
4196  *
4197  * PARAMS
4198  *  lpszFile  [I] File name containing content to look for
4199  *  lpszPath  [O] Buffer to contain the full path to the file
4200  *  dwPathLen [I] Length of lpszPath
4201  *
4202  * RETURNS
4203  *  Success: S_OK. lpszPath contains the full path to the content.
4204  *  Failure: E_FAIL. The content does not exist or lpszPath is too short.
4205  */
4206 HRESULT WINAPI SHGetWebFolderFilePathA(LPCSTR lpszFile, LPSTR lpszPath, DWORD dwPathLen)
4207 {
4208   WCHAR szFile[MAX_PATH], szPath[MAX_PATH];
4209   HRESULT hRet;
4210 
4211   TRACE("(%s,%p,%d)\n", lpszFile, lpszPath, dwPathLen);
4212 
4213   MultiByteToWideChar(CP_ACP, 0, lpszFile, -1, szFile, MAX_PATH);
4214   szPath[0] = '\0';
4215   hRet = SHGetWebFolderFilePathW(szFile, szPath, dwPathLen);
4216   WideCharToMultiByte(CP_ACP, 0, szPath, -1, lpszPath, dwPathLen, 0, 0);
4217   return hRet;
4218 }
4219 
4220 /*************************************************************************
4221  * @     [SHLWAPI.441]
4222  *
4223  * Unicode version of SHGetWebFolderFilePathA.
4224  */
4225 HRESULT WINAPI SHGetWebFolderFilePathW(LPCWSTR lpszFile, LPWSTR lpszPath, DWORD dwPathLen)
4226 {
4227   static const WCHAR szWeb[] = {'\\','W','e','b','\\','\0'};
4228   static const WCHAR szWebMui[] = {'m','u','i','\\','%','0','4','x','\\','\0'};
4229 #define szWebLen (sizeof(szWeb)/sizeof(WCHAR))
4230 #define szWebMuiLen ((sizeof(szWebMui)+1)/sizeof(WCHAR))
4231   DWORD dwLen, dwFileLen;
4232   LANGID lidSystem, lidUser;
4233 
4234   TRACE("(%s,%p,%d)\n", debugstr_w(lpszFile), lpszPath, dwPathLen);
4235 
4236   /* Get base directory for web content */
4237   dwLen = GetSystemWindowsDirectoryW(lpszPath, dwPathLen);
4238   if (dwLen > 0 && lpszPath[dwLen-1] == '\\')
4239     dwLen--;
4240 
4241   dwFileLen = strlenW(lpszFile);
4242 
4243   if (dwLen + dwFileLen + szWebLen >= dwPathLen)
4244     return E_FAIL; /* lpszPath too short */
4245 
4246   strcpyW(lpszPath+dwLen, szWeb);
4247   dwLen += szWebLen;
4248   dwPathLen = dwPathLen - dwLen; /* Remaining space */
4249 
4250   lidSystem = GetSystemDefaultUILanguage();
4251   lidUser = GetUserDefaultUILanguage();
4252 
4253   if (lidSystem != lidUser)
4254   {
4255     if (dwFileLen + szWebMuiLen < dwPathLen)
4256     {
4257       /* Use localised content in the users UI language if present */
4258       wsprintfW(lpszPath + dwLen, szWebMui, lidUser);
4259       strcpyW(lpszPath + dwLen + szWebMuiLen, lpszFile);
4260       if (PathFileExistsW(lpszPath))
4261         return S_OK;
4262     }
4263   }
4264 
4265   /* Fall back to OS default installed content */
4266   strcpyW(lpszPath + dwLen, lpszFile);
4267   if (PathFileExistsW(lpszPath))
4268     return S_OK;
4269   return E_FAIL;
4270 }
4271 
4272 #define PATH_CHAR_CLASS_LETTER      0x00000001
4273 #define PATH_CHAR_CLASS_ASTERIX     0x00000002
4274 #define PATH_CHAR_CLASS_DOT         0x00000004
4275 #define PATH_CHAR_CLASS_BACKSLASH   0x00000008
4276 #define PATH_CHAR_CLASS_COLON       0x00000010
4277 #define PATH_CHAR_CLASS_SEMICOLON   0x00000020
4278 #define PATH_CHAR_CLASS_COMMA       0x00000040
4279 #define PATH_CHAR_CLASS_SPACE       0x00000080
4280 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
4281 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
4282 
4283 #define PATH_CHAR_CLASS_INVALID     0x00000000
4284 #define PATH_CHAR_CLASS_ANY         0xffffffff
4285 
4286 static const DWORD SHELL_charclass[] =
4287 {
4288     /* 0x00 */  PATH_CHAR_CLASS_INVALID,      /* 0x01 */  PATH_CHAR_CLASS_INVALID,
4289     /* 0x02 */  PATH_CHAR_CLASS_INVALID,      /* 0x03 */  PATH_CHAR_CLASS_INVALID,
4290     /* 0x04 */  PATH_CHAR_CLASS_INVALID,      /* 0x05 */  PATH_CHAR_CLASS_INVALID,
4291     /* 0x06 */  PATH_CHAR_CLASS_INVALID,      /* 0x07 */  PATH_CHAR_CLASS_INVALID,
4292     /* 0x08 */  PATH_CHAR_CLASS_INVALID,      /* 0x09 */  PATH_CHAR_CLASS_INVALID,
4293     /* 0x0a */  PATH_CHAR_CLASS_INVALID,      /* 0x0b */  PATH_CHAR_CLASS_INVALID,
4294     /* 0x0c */  PATH_CHAR_CLASS_INVALID,      /* 0x0d */  PATH_CHAR_CLASS_INVALID,
4295     /* 0x0e */  PATH_CHAR_CLASS_INVALID,      /* 0x0f */  PATH_CHAR_CLASS_INVALID,
4296     /* 0x10 */  PATH_CHAR_CLASS_INVALID,      /* 0x11 */  PATH_CHAR_CLASS_INVALID,
4297     /* 0x12 */  PATH_CHAR_CLASS_INVALID,      /* 0x13 */  PATH_CHAR_CLASS_INVALID,
4298     /* 0x14 */  PATH_CHAR_CLASS_INVALID,      /* 0x15 */  PATH_CHAR_CLASS_INVALID,
4299     /* 0x16 */  PATH_CHAR_CLASS_INVALID,      /* 0x17 */  PATH_CHAR_CLASS_INVALID,
4300     /* 0x18 */  PATH_CHAR_CLASS_INVALID,      /* 0x19 */  PATH_CHAR_CLASS_INVALID,
4301     /* 0x1a */  PATH_CHAR_CLASS_INVALID,      /* 0x1b */  PATH_CHAR_CLASS_INVALID,
4302     /* 0x1c */  PATH_CHAR_CLASS_INVALID,      /* 0x1d */  PATH_CHAR_CLASS_INVALID,
4303     /* 0x1e */  PATH_CHAR_CLASS_INVALID,      /* 0x1f */  PATH_CHAR_CLASS_INVALID,
4304     /* ' '  */  PATH_CHAR_CLASS_SPACE,        /* '!'  */  PATH_CHAR_CLASS_OTHER_VALID,
4305     /* '"'  */  PATH_CHAR_CLASS_DOUBLEQUOTE,  /* '#'  */  PATH_CHAR_CLASS_OTHER_VALID,
4306     /* '$'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '%'  */  PATH_CHAR_CLASS_OTHER_VALID,
4307     /* '&'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '\'' */  PATH_CHAR_CLASS_OTHER_VALID,
4308     /* '('  */  PATH_CHAR_CLASS_OTHER_VALID,  /* ')'  */  PATH_CHAR_CLASS_OTHER_VALID,
4309     /* '*'  */  PATH_CHAR_CLASS_ASTERIX,      /* '+'  */  PATH_CHAR_CLASS_OTHER_VALID,
4310     /* ','  */  PATH_CHAR_CLASS_COMMA,        /* '-'  */  PATH_CHAR_CLASS_OTHER_VALID,
4311     /* '.'  */  PATH_CHAR_CLASS_DOT,          /* '/'  */  PATH_CHAR_CLASS_INVALID,
4312     /* '0'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '1'  */  PATH_CHAR_CLASS_OTHER_VALID,
4313     /* '2'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '3'  */  PATH_CHAR_CLASS_OTHER_VALID,
4314     /* '4'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '5'  */  PATH_CHAR_CLASS_OTHER_VALID,
4315     /* '6'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '7'  */  PATH_CHAR_CLASS_OTHER_VALID,
4316     /* '8'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '9'  */  PATH_CHAR_CLASS_OTHER_VALID,
4317     /* ':'  */  PATH_CHAR_CLASS_COLON,        /* ';'  */  PATH_CHAR_CLASS_SEMICOLON,
4318     /* '<'  */  PATH_CHAR_CLASS_INVALID,      /* '='  */  PATH_CHAR_CLASS_OTHER_VALID,
4319     /* '>'  */  PATH_CHAR_CLASS_INVALID,      /* '?'  */  PATH_CHAR_CLASS_LETTER,
4320     /* '@'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* 'A'  */  PATH_CHAR_CLASS_ANY,
4321     /* 'B'  */  PATH_CHAR_CLASS_ANY,          /* 'C'  */  PATH_CHAR_CLASS_ANY,
4322     /* 'D'  */  PATH_CHAR_CLASS_ANY,          /* 'E'  */  PATH_CHAR_CLASS_ANY,
4323     /* 'F'  */  PATH_CHAR_CLASS_ANY,          /* 'G'  */  PATH_CHAR_CLASS_ANY,
4324     /* 'H'  */  PATH_CHAR_CLASS_ANY,          /* 'I'  */  PATH_CHAR_CLASS_ANY,
4325     /* 'J'  */  PATH_CHAR_CLASS_ANY,          /* 'K'  */  PATH_CHAR_CLASS_ANY,
4326     /* 'L'  */  PATH_CHAR_CLASS_ANY,          /* 'M'  */  PATH_CHAR_CLASS_ANY,
4327     /* 'N'  */  PATH_CHAR_CLASS_ANY,          /* 'O'  */  PATH_CHAR_CLASS_ANY,
4328     /* 'P'  */  PATH_CHAR_CLASS_ANY,          /* 'Q'  */  PATH_CHAR_CLASS_ANY,
4329     /* 'R'  */  PATH_CHAR_CLASS_ANY,          /* 'S'  */  PATH_CHAR_CLASS_ANY,
4330     /* 'T'  */  PATH_CHAR_CLASS_ANY,          /* 'U'  */  PATH_CHAR_CLASS_ANY,
4331     /* 'V'  */  PATH_CHAR_CLASS_ANY,          /* 'W'  */  PATH_CHAR_CLASS_ANY,
4332     /* 'X'  */  PATH_CHAR_CLASS_ANY,          /* 'Y'  */  PATH_CHAR_CLASS_ANY,
4333     /* 'Z'  */  PATH_CHAR_CLASS_ANY,          /* '['  */  PATH_CHAR_CLASS_OTHER_VALID,
4334     /* '\\' */  PATH_CHAR_CLASS_BACKSLASH,    /* ']'  */  PATH_CHAR_CLASS_OTHER_VALID,
4335     /* '^'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* '_'  */  PATH_CHAR_CLASS_OTHER_VALID,
4336     /* '`'  */  PATH_CHAR_CLASS_OTHER_VALID,  /* 'a'  */  PATH_CHAR_CLASS_ANY,
4337     /* 'b'  */  PATH_CHAR_CLASS_ANY,          /* 'c'  */  PATH_CHAR_CLASS_ANY,
4338     /* 'd'  */  PATH_CHAR_CLASS_ANY,          /* 'e'  */  PATH_CHAR_CLASS_ANY,
4339     /* 'f'  */  PATH_CHAR_CLASS_ANY,          /* 'g'  */  PATH_CHAR_CLASS_ANY,
4340     /* 'h'  */  PATH_CHAR_CLASS_ANY,          /* 'i'  */  PATH_CHAR_CLASS_ANY,
4341     /* 'j'  */  PATH_CHAR_CLASS_ANY,          /* 'k'  */  PATH_CHAR_CLASS_ANY,
4342     /* 'l'  */  PATH_CHAR_CLASS_ANY,          /* 'm'  */  PATH_CHAR_CLASS_ANY,
4343     /* 'n'  */  PATH_CHAR_CLASS_ANY,          /* 'o'  */  PATH_CHAR_CLASS_ANY,
4344     /* 'p'  */  PATH_CHAR_CLASS_ANY,          /* 'q'  */  PATH_CHAR_CLASS_ANY,
4345     /* 'r'  */  PATH_CHAR_CLASS_ANY,          /* 's'  */  PATH_CHAR_CLASS_ANY,
4346     /* 't'  */  PATH_CHAR_CLASS_ANY,          /* 'u'  */  PATH_CHAR_CLASS_ANY,
4347     /* 'v'  */  PATH_CHAR_CLASS_ANY,          /* 'w'  */  PATH_CHAR_CLASS_ANY,
4348     /* 'x'  */  PATH_CHAR_CLASS_ANY,          /* 'y'  */  PATH_CHAR_CLASS_ANY,
4349     /* 'z'  */  PATH_CHAR_CLASS_ANY,          /* '{'  */  PATH_CHAR_CLASS_OTHER_VALID,
4350     /* '|'  */  PATH_CHAR_CLASS_INVALID,      /* '}'  */  PATH_CHAR_CLASS_OTHER_VALID,
4351     /* '~'  */  PATH_CHAR_CLASS_OTHER_VALID
4352 };
4353 
4354 /*************************************************************************
4355  * @     [SHLWAPI.455]
4356  *
4357  * Check if an ASCII char is of a certain class
4358  */
4359 BOOL WINAPI PathIsValidCharA( char c, DWORD class )
4360 {
4361     if ((unsigned)c > 0x7e)
4362         return class & PATH_CHAR_CLASS_OTHER_VALID;
4363 
4364     return class & SHELL_charclass[(unsigned)c];
4365 }
4366 
4367 /*************************************************************************
4368  * @     [SHLWAPI.456]
4369  *
4370  * Check if a Unicode char is of a certain class
4371  */
4372 BOOL WINAPI PathIsValidCharW( WCHAR c, DWORD class )
4373 {
4374     if (c > 0x7e)
4375         return class & PATH_CHAR_CLASS_OTHER_VALID;
4376 
4377     return class & SHELL_charclass[c];
4378 }
4379