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