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