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