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