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