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