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