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