2 * Copyright 2018 Nikolay Sivov
3 * Copyright 2018 Zhiyi Zhang
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
32 #include "kernelbase.h"
33 #include "wine/exception.h"
34 #include "wine/debug.h"
35 #include "wine/heap.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(path
);
39 #define iswalnum(ch) (iswctype((ch), C1_ALPHA|C1_DIGIT|C1_LOWER|C1_UPPER))
40 #define isxdigit(ch) (((ch) >= '0' && (ch) <= '9') || \
41 ((ch) >= 'A' && (ch) <= 'F') || \
42 ((ch) >= 'a' && (ch) <= 'f'))
44 static const char hexDigits
[] = "0123456789ABCDEF";
46 static const unsigned char hashdata_lookup
[256] =
48 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33,
49 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41,
50 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c,
51 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90,
52 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe,
53 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd,
54 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d,
55 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd,
56 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2,
57 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b,
58 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70,
59 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b,
60 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47,
61 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d,
62 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8,
63 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1,
68 const WCHAR
*scheme
; /* [out] start of scheme */
69 DWORD scheme_len
; /* [out] size of scheme (until colon) */
70 const WCHAR
*username
; /* [out] start of Username */
71 DWORD username_len
; /* [out] size of Username (until ":" or "@") */
72 const WCHAR
*password
; /* [out] start of Password */
73 DWORD password_len
; /* [out] size of Password (until "@") */
74 const WCHAR
*hostname
; /* [out] start of Hostname */
75 DWORD hostname_len
; /* [out] size of Hostname (until ":" or "/") */
76 const WCHAR
*port
; /* [out] start of Port */
77 DWORD port_len
; /* [out] size of Port (until "/" or eos) */
78 const WCHAR
*query
; /* [out] start of Query */
79 DWORD query_len
; /* [out] size of Query (until eos) */
90 static WCHAR
*heap_strdupAtoW(const char *str
)
98 len
= MultiByteToWideChar(CP_ACP
, 0, str
, -1, NULL
, 0);
99 ret
= heap_alloc(len
* sizeof(WCHAR
));
100 MultiByteToWideChar(CP_ACP
, 0, str
, -1, ret
, len
);
106 static SIZE_T
strnlenW(const WCHAR
*string
, SIZE_T maxlen
)
110 for (i
= 0; i
< maxlen
; i
++)
111 if (!string
[i
]) break;
115 static BOOL
is_prefixed_unc(const WCHAR
*string
)
117 static const WCHAR prefixed_unc
[] = {'\\', '\\', '?', '\\', 'U', 'N', 'C', '\\'};
118 return !wcsnicmp(string
, prefixed_unc
, ARRAY_SIZE(prefixed_unc
));
121 static BOOL
is_prefixed_disk(const WCHAR
*string
)
123 static const WCHAR prefix
[] = {'\\', '\\', '?', '\\'};
124 return !wcsncmp(string
, prefix
, ARRAY_SIZE(prefix
)) && iswalpha(string
[4]) && string
[5] == ':';
127 static BOOL
is_prefixed_volume(const WCHAR
*string
)
129 static const WCHAR prefixed_volume
[] = {'\\', '\\', '?', '\\', 'V', 'o', 'l', 'u', 'm', 'e'};
133 if (wcsnicmp(string
, prefixed_volume
, ARRAY_SIZE(prefixed_volume
))) return FALSE
;
135 guid
= string
+ ARRAY_SIZE(prefixed_volume
);
142 if (guid
[i
] != '{') return FALSE
;
148 if (guid
[i
] != '-') return FALSE
;
151 if (guid
[i
] != '}') return FALSE
;
154 if (!iswxdigit(guid
[i
])) return FALSE
;
163 /* Get the next character beyond end of the segment.
164 Return TRUE if the last segment ends with a backslash */
165 static BOOL
get_next_segment(const WCHAR
*next
, const WCHAR
**next_segment
)
167 while (*next
&& *next
!= '\\') next
++;
170 *next_segment
= next
+ 1;
175 *next_segment
= next
;
180 /* Find the last character of the root in a path, if there is one, without any segments */
181 static const WCHAR
*get_root_end(const WCHAR
*path
)
184 if (is_prefixed_volume(path
))
185 return path
[48] == '\\' ? path
+ 48 : path
+ 47;
186 else if (is_prefixed_unc(path
))
188 else if (is_prefixed_disk(path
))
189 return path
[6] == '\\' ? path
+ 6 : path
+ 5;
191 else if (path
[0] == '\\' && path
[1] == '\\')
194 else if (path
[0] == '\\')
197 else if (iswalpha(path
[0]) && path
[1] == ':')
198 return path
[2] == '\\' ? path
+ 2 : path
+ 1;
203 HRESULT WINAPI
PathAllocCanonicalize(const WCHAR
*path_in
, DWORD flags
, WCHAR
**path_out
)
207 const WCHAR
*root_end
;
208 SIZE_T buffer_size
, length
;
210 TRACE("%s %#x %p\n", debugstr_w(path_in
), flags
, path_out
);
212 if (!path_in
|| !path_out
213 || ((flags
& PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS
) && (flags
& PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS
))
214 || (flags
& (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS
| PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS
)
215 && !(flags
& PATHCCH_ALLOW_LONG_PATHS
))
216 || ((flags
& PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
) && (flags
& PATHCCH_ALLOW_LONG_PATHS
)))
218 if (path_out
) *path_out
= NULL
;
222 length
= lstrlenW(path_in
);
223 if ((length
+ 1 > MAX_PATH
&& !(flags
& (PATHCCH_ALLOW_LONG_PATHS
| PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
)))
224 || (length
+ 1 > PATHCCH_MAX_CCH
))
227 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE
);
230 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
231 if (flags
& PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
) flags
|= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS
;
233 /* path length + possible \\?\ addition + possible \ addition + NUL */
234 buffer_size
= (length
+ 6) * sizeof(WCHAR
);
235 buffer
= LocalAlloc(LMEM_ZEROINIT
, buffer_size
);
239 return E_OUTOFMEMORY
;
245 root_end
= get_root_end(path_in
);
246 if (root_end
) root_end
= buffer
+ (root_end
- path_in
);
251 memcpy(dst
, src
, (root_end
- buffer
+ 1) * sizeof(WCHAR
));
252 src
+= root_end
- buffer
+ 1;
253 if(PathCchStripPrefix(dst
, length
+ 6) == S_OK
)
255 /* Fill in \ in X:\ if the \ is missing */
256 if(iswalpha(dst
[0]) && dst
[1] == ':' && dst
[2]!= '\\')
261 dst
= buffer
+ lstrlenW(buffer
);
265 dst
+= root_end
- buffer
+ 1;
274 /* Keep one . after * */
275 if (dst
> buffer
&& dst
[-1] == '*')
281 /* Keep the .. if not surrounded by \ */
282 if ((src
[2] != '\\' && src
[2]) || (dst
> buffer
&& dst
[-1] != '\\'))
289 /* Remove the \ before .. if the \ is not part of root */
290 if (dst
> buffer
&& dst
[-1] == '\\' && (!root_end
|| dst
- 1 > root_end
))
293 /* Remove characters until a \ is encountered */
305 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
306 else if (src
[2] == '\\')
313 /* Keep the . if not surrounded by \ */
314 if ((src
[1] != '\\' && src
[1]) || (dst
> buffer
&& dst
[-1] != '\\'))
320 /* Remove the \ before . if the \ is not part of root */
321 if (dst
> buffer
&& dst
[-1] == '\\' && (!root_end
|| dst
- 1 > root_end
)) dst
--;
322 /* Remove the extra \ after . if the \ before . wasn't deleted */
323 else if (src
[1] == '\\')
329 /* If X:\ is not complete, then complete it */
330 if (iswalpha(buffer
[0]) && buffer
[1] == ':' && buffer
[2] != '\\')
332 root_end
= buffer
+ 2;
335 /* If next character is \, use the \ to fill in */
336 if (src
[0] == '\\') src
++;
346 /* Strip multiple trailing . */
347 if (!(flags
& PATHCCH_DO_NOT_NORMALIZE_SEGMENTS
))
349 while (dst
> buffer
&& dst
[-1] == '.')
351 /* Keep a . after * */
352 if (dst
- 1 > buffer
&& dst
[-2] == '*')
354 /* If . follow a : at the second character, remove the . and add a \ */
355 else if (dst
- 1 > buffer
&& dst
[-2] == ':' && dst
- 2 == buffer
+ 1)
362 /* If result path is empty, fill in \ */
369 /* Extend the path if needed */
370 length
= lstrlenW(buffer
);
371 if (((length
+ 1 > MAX_PATH
&& iswalpha(buffer
[0]) && buffer
[1] == ':')
372 || (iswalpha(buffer
[0]) && buffer
[1] == ':' && flags
& PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
))
373 && !(flags
& PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS
))
375 memmove(buffer
+ 4, buffer
, (length
+ 1) * sizeof(WCHAR
));
382 /* Add a trailing backslash to the path if needed */
383 if (flags
& PATHCCH_ENSURE_TRAILING_SLASH
)
384 PathCchAddBackslash(buffer
, buffer_size
);
390 HRESULT WINAPI
PathAllocCombine(const WCHAR
*path1
, const WCHAR
*path2
, DWORD flags
, WCHAR
**out
)
392 SIZE_T combined_length
, length2
;
393 WCHAR
*combined_path
;
394 BOOL from_path2
= FALSE
;
397 TRACE("%s %s %#x %p\n", wine_dbgstr_w(path1
), wine_dbgstr_w(path2
), flags
, out
);
399 if ((!path1
&& !path2
) || !out
)
401 if (out
) *out
= NULL
;
405 if (!path1
|| !path2
) return PathAllocCanonicalize(path1
? path1
: path2
, flags
, out
);
407 /* If path2 is fully qualified, use path2 only */
408 if ((iswalpha(path2
[0]) && path2
[1] == ':') || (path2
[0] == '\\' && path2
[1] == '\\'))
415 length2
= path2
? lstrlenW(path2
) : 0;
416 /* path1 length + path2 length + possible backslash + NULL */
417 combined_length
= lstrlenW(path1
) + length2
+ 2;
419 combined_path
= HeapAlloc(GetProcessHeap(), 0, combined_length
* sizeof(WCHAR
));
423 return E_OUTOFMEMORY
;
426 lstrcpyW(combined_path
, path1
);
427 PathCchStripPrefix(combined_path
, combined_length
);
428 if (from_path2
) PathCchAddBackslashEx(combined_path
, combined_length
, NULL
, NULL
);
430 if (path2
&& path2
[0])
432 if (path2
[0] == '\\' && path2
[1] != '\\')
434 PathCchStripToRoot(combined_path
, combined_length
);
438 PathCchAddBackslashEx(combined_path
, combined_length
, NULL
, NULL
);
439 lstrcatW(combined_path
, path2
);
442 hr
= PathAllocCanonicalize(combined_path
, flags
, out
);
443 HeapFree(GetProcessHeap(), 0, combined_path
);
447 HRESULT WINAPI
PathCchAddBackslash(WCHAR
*path
, SIZE_T size
)
449 return PathCchAddBackslashEx(path
, size
, NULL
, NULL
);
452 HRESULT WINAPI
PathCchAddBackslashEx(WCHAR
*path
, SIZE_T size
, WCHAR
**endptr
, SIZE_T
*remaining
)
454 BOOL needs_termination
;
457 TRACE("%s, %lu, %p, %p\n", debugstr_w(path
), size
, endptr
, remaining
);
459 length
= lstrlenW(path
);
460 needs_termination
= size
&& length
&& path
[length
- 1] != '\\';
462 if (length
>= (needs_termination
? size
- 1 : size
))
464 if (endptr
) *endptr
= NULL
;
465 if (remaining
) *remaining
= 0;
466 return STRSAFE_E_INSUFFICIENT_BUFFER
;
469 if (!needs_termination
)
471 if (endptr
) *endptr
= path
+ length
;
472 if (remaining
) *remaining
= size
- length
;
476 path
[length
++] = '\\';
479 if (endptr
) *endptr
= path
+ length
;
480 if (remaining
) *remaining
= size
- length
;
485 HRESULT WINAPI
PathCchAddExtension(WCHAR
*path
, SIZE_T size
, const WCHAR
*extension
)
487 const WCHAR
*existing_extension
, *next
;
488 SIZE_T path_length
, extension_length
, dot_length
;
492 TRACE("%s %lu %s\n", wine_dbgstr_w(path
), size
, wine_dbgstr_w(extension
));
494 if (!path
|| !size
|| size
> PATHCCH_MAX_CCH
|| !extension
) return E_INVALIDARG
;
499 if ((*next
== '.' && next
> extension
) || *next
== ' ' || *next
== '\\') return E_INVALIDARG
;
503 has_dot
= extension
[0] == '.';
505 hr
= PathCchFindExtension(path
, size
, &existing_extension
);
506 if (FAILED(hr
)) return hr
;
507 if (*existing_extension
) return S_FALSE
;
509 path_length
= strnlenW(path
, size
);
510 dot_length
= has_dot
? 0 : 1;
511 extension_length
= lstrlenW(extension
);
513 if (path_length
+ dot_length
+ extension_length
+ 1 > size
) return STRSAFE_E_INSUFFICIENT_BUFFER
;
515 /* If extension is empty or only dot, return S_OK with path unchanged */
516 if (!extension
[0] || (extension
[0] == '.' && !extension
[1])) return S_OK
;
520 path
[path_length
] = '.';
524 lstrcpyW(path
+ path_length
, extension
);
528 HRESULT WINAPI
PathCchAppend(WCHAR
*path1
, SIZE_T size
, const WCHAR
*path2
)
530 TRACE("%s %lu %s\n", wine_dbgstr_w(path1
), size
, wine_dbgstr_w(path2
));
532 return PathCchAppendEx(path1
, size
, path2
, PATHCCH_NONE
);
535 HRESULT WINAPI
PathCchAppendEx(WCHAR
*path1
, SIZE_T size
, const WCHAR
*path2
, DWORD flags
)
540 TRACE("%s %lu %s %#x\n", wine_dbgstr_w(path1
), size
, wine_dbgstr_w(path2
), flags
);
542 if (!path1
|| !size
) return E_INVALIDARG
;
544 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
545 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
546 * buffer for PathCchCombineEx */
547 result
= HeapAlloc(GetProcessHeap(), 0, size
* sizeof(WCHAR
));
548 if (!result
) return E_OUTOFMEMORY
;
550 /* Avoid the single backslash behavior with PathCchCombineEx when appending */
551 if (path2
&& path2
[0] == '\\' && path2
[1] != '\\') path2
++;
553 hr
= PathCchCombineEx(result
, size
, path1
, path2
, flags
);
554 if (SUCCEEDED(hr
)) memcpy(path1
, result
, size
* sizeof(WCHAR
));
556 HeapFree(GetProcessHeap(), 0, result
);
560 HRESULT WINAPI
PathCchCanonicalize(WCHAR
*out
, SIZE_T size
, const WCHAR
*in
)
562 TRACE("%p %lu %s\n", out
, size
, wine_dbgstr_w(in
));
564 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
565 if (lstrlenW(in
) > MAX_PATH
- 4 && !(iswalpha(in
[0]) && in
[1] == ':' && in
[2] == '\\'))
566 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE
);
568 return PathCchCanonicalizeEx(out
, size
, in
, PATHCCH_NONE
);
571 HRESULT WINAPI
PathCchCanonicalizeEx(WCHAR
*out
, SIZE_T size
, const WCHAR
*in
, DWORD flags
)
577 TRACE("%p %lu %s %#x\n", out
, size
, wine_dbgstr_w(in
), flags
);
579 if (!size
) return E_INVALIDARG
;
581 hr
= PathAllocCanonicalize(in
, flags
, &buffer
);
582 if (FAILED(hr
)) return hr
;
584 length
= lstrlenW(buffer
);
585 if (size
< length
+ 1)
587 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
588 if (length
> MAX_PATH
- 4 && !(in
[0] == '\\' || (iswalpha(in
[0]) && in
[1] == ':' && in
[2] == '\\')))
589 hr
= HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE
);
591 hr
= STRSAFE_E_INSUFFICIENT_BUFFER
;
596 memcpy(out
, buffer
, (length
+ 1) * sizeof(WCHAR
));
598 /* Fill a backslash at the end of X: */
599 if (iswalpha(out
[0]) && out
[1] == ':' && !out
[2] && size
> 3)
610 HRESULT WINAPI
PathCchCombine(WCHAR
*out
, SIZE_T size
, const WCHAR
*path1
, const WCHAR
*path2
)
612 TRACE("%p %s %s\n", out
, wine_dbgstr_w(path1
), wine_dbgstr_w(path2
));
614 return PathCchCombineEx(out
, size
, path1
, path2
, PATHCCH_NONE
);
617 HRESULT WINAPI
PathCchCombineEx(WCHAR
*out
, SIZE_T size
, const WCHAR
*path1
, const WCHAR
*path2
, DWORD flags
)
623 TRACE("%p %s %s %#x\n", out
, wine_dbgstr_w(path1
), wine_dbgstr_w(path2
), flags
);
625 if (!out
|| !size
|| size
> PATHCCH_MAX_CCH
) return E_INVALIDARG
;
627 hr
= PathAllocCombine(path1
, path2
, flags
, &buffer
);
634 length
= lstrlenW(buffer
);
635 if (length
+ 1 > size
)
639 return STRSAFE_E_INSUFFICIENT_BUFFER
;
643 memcpy(out
, buffer
, (length
+ 1) * sizeof(WCHAR
));
649 HRESULT WINAPI
PathCchFindExtension(const WCHAR
*path
, SIZE_T size
, const WCHAR
**extension
)
651 const WCHAR
*lastpoint
= NULL
;
654 TRACE("%s %lu %p\n", wine_dbgstr_w(path
), size
, extension
);
656 if (!path
|| !size
|| size
> PATHCCH_MAX_CCH
)
664 if (*path
== '\\' || *path
== ' ')
666 else if (*path
== '.')
671 if (counter
== size
|| counter
== PATHCCH_MAX_CCH
)
678 *extension
= lastpoint
? lastpoint
: path
;
682 BOOL WINAPI
PathCchIsRoot(const WCHAR
*path
)
684 const WCHAR
*root_end
;
688 TRACE("%s\n", wine_dbgstr_w(path
));
690 if (!path
|| !*path
) return FALSE
;
692 root_end
= get_root_end(path
);
693 if (!root_end
) return FALSE
;
695 if ((is_unc
= is_prefixed_unc(path
)) || (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '?'))
698 /* No extra segments */
699 if ((is_unc
&& !*next
) || (!is_unc
&& !*next
)) return TRUE
;
701 /* Has first segment with an ending backslash but no remaining characters */
702 if (get_next_segment(next
, &next
) && !*next
) return FALSE
;
703 /* Has first segment with no ending backslash */
706 /* Has first segment with an ending backslash and has remaining characters*/
710 /* Second segment must have no backslash and no remaining characters */
711 return !get_next_segment(next
, &next
) && !*next
;
714 else if (*root_end
== '\\' && !root_end
[1])
720 HRESULT WINAPI
PathCchRemoveBackslash(WCHAR
*path
, SIZE_T path_size
)
725 TRACE("%s %lu\n", debugstr_w(path
), path_size
);
727 return PathCchRemoveBackslashEx(path
, path_size
, &path_end
, &free_size
);
730 HRESULT WINAPI
PathCchRemoveBackslashEx(WCHAR
*path
, SIZE_T path_size
, WCHAR
**path_end
, SIZE_T
*free_size
)
732 const WCHAR
*root_end
;
735 TRACE("%s %lu %p %p\n", debugstr_w(path
), path_size
, path_end
, free_size
);
737 if (!path_size
|| !path_end
|| !free_size
)
739 if (path_end
) *path_end
= NULL
;
740 if (free_size
) *free_size
= 0;
744 path_length
= strnlenW(path
, path_size
);
745 if (path_length
== path_size
&& !path
[path_length
]) return E_INVALIDARG
;
747 root_end
= get_root_end(path
);
748 if (path_length
> 0 && path
[path_length
- 1] == '\\')
750 *path_end
= path
+ path_length
- 1;
751 *free_size
= path_size
- path_length
+ 1;
752 /* If the last character is beyond end of root */
753 if (!root_end
|| path
+ path_length
- 1 > root_end
)
755 path
[path_length
- 1] = 0;
763 *path_end
= path
+ path_length
;
764 *free_size
= path_size
- path_length
;
769 HRESULT WINAPI
PathCchRemoveExtension(WCHAR
*path
, SIZE_T size
)
771 const WCHAR
*extension
;
775 TRACE("%s %lu\n", wine_dbgstr_w(path
), size
);
777 if (!path
|| !size
|| size
> PATHCCH_MAX_CCH
) return E_INVALIDARG
;
779 hr
= PathCchFindExtension(path
, size
, &extension
);
780 if (FAILED(hr
)) return hr
;
782 next
= path
+ (extension
- path
);
783 while (next
- path
< size
&& *next
) *next
++ = 0;
785 return next
== extension
? S_FALSE
: S_OK
;
788 HRESULT WINAPI
PathCchRemoveFileSpec(WCHAR
*path
, SIZE_T size
)
790 const WCHAR
*root_end
= NULL
;
794 TRACE("%s %lu\n", wine_dbgstr_w(path
), size
);
796 if (!path
|| !size
|| size
> PATHCCH_MAX_CCH
) return E_INVALIDARG
;
798 if (PathCchIsRoot(path
)) return S_FALSE
;
800 PathCchSkipRoot(path
, &root_end
);
802 /* The backslash at the end of UNC and \\* are not considered part of root in this case */
803 if (root_end
&& root_end
> path
&& root_end
[-1] == '\\'
804 && (is_prefixed_unc(path
) || (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '?')))
807 length
= lstrlenW(path
);
808 last
= path
+ length
- 1;
809 while (last
>= path
&& (!root_end
|| last
>= root_end
))
811 if (last
- path
>= size
) return E_INVALIDARG
;
822 return last
!= path
+ length
- 1 ? S_OK
: S_FALSE
;
825 HRESULT WINAPI
PathCchRenameExtension(WCHAR
*path
, SIZE_T size
, const WCHAR
*extension
)
829 TRACE("%s %lu %s\n", wine_dbgstr_w(path
), size
, wine_dbgstr_w(extension
));
831 hr
= PathCchRemoveExtension(path
, size
);
832 if (FAILED(hr
)) return hr
;
834 hr
= PathCchAddExtension(path
, size
, extension
);
835 return FAILED(hr
) ? hr
: S_OK
;
838 HRESULT WINAPI
PathCchSkipRoot(const WCHAR
*path
, const WCHAR
**root_end
)
840 static const WCHAR unc_prefix
[] = {'\\', '\\', '?'};
842 TRACE("%s %p\n", debugstr_w(path
), root_end
);
844 if (!path
|| !path
[0] || !root_end
845 || (!wcsnicmp(unc_prefix
, path
, ARRAY_SIZE(unc_prefix
)) && !is_prefixed_volume(path
) && !is_prefixed_unc(path
)
846 && !is_prefixed_disk(path
)))
849 *root_end
= get_root_end(path
);
853 if (is_prefixed_unc(path
))
855 get_next_segment(*root_end
, root_end
);
856 get_next_segment(*root_end
, root_end
);
858 else if (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '?')
860 /* Skip share server */
861 get_next_segment(*root_end
, root_end
);
862 /* If mount point is empty, don't skip over mount point */
863 if (**root_end
!= '\\') get_next_segment(*root_end
, root_end
);
867 return *root_end
? S_OK
: E_INVALIDARG
;
870 HRESULT WINAPI
PathCchStripPrefix(WCHAR
*path
, SIZE_T size
)
872 TRACE("%s %lu\n", wine_dbgstr_w(path
), size
);
874 if (!path
|| !size
|| size
> PATHCCH_MAX_CCH
) return E_INVALIDARG
;
876 if (is_prefixed_unc(path
))
878 /* \\?\UNC\a -> \\a */
879 if (size
< lstrlenW(path
+ 8) + 3) return E_INVALIDARG
;
880 lstrcpyW(path
+ 2, path
+ 8);
883 else if (is_prefixed_disk(path
))
886 if (size
< lstrlenW(path
+ 4) + 1) return E_INVALIDARG
;
887 lstrcpyW(path
, path
+ 4);
894 HRESULT WINAPI
PathCchStripToRoot(WCHAR
*path
, SIZE_T size
)
896 const WCHAR
*root_end
;
900 TRACE("%s %lu\n", wine_dbgstr_w(path
), size
);
902 if (!path
|| !*path
|| !size
|| size
> PATHCCH_MAX_CCH
) return E_INVALIDARG
;
904 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
905 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
906 * \\\\a\\b\\c -> \\\\a\\b */
907 if ((is_unc
= is_prefixed_unc(path
)) || (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '?'))
909 root_end
= is_unc
? path
+ 8 : path
+ 3;
910 if (!get_next_segment(root_end
, &root_end
)) return S_FALSE
;
911 if (!get_next_segment(root_end
, &root_end
)) return S_FALSE
;
913 if (root_end
- path
>= size
) return E_INVALIDARG
;
915 segment_end
= path
+ (root_end
- path
) - 1;
919 else if (PathCchSkipRoot(path
, &root_end
) == S_OK
)
921 if (root_end
- path
>= size
) return E_INVALIDARG
;
923 segment_end
= path
+ (root_end
- path
);
924 if (!*segment_end
) return S_FALSE
;
933 BOOL WINAPI
PathIsUNCEx(const WCHAR
*path
, const WCHAR
**server
)
935 const WCHAR
*result
= NULL
;
937 TRACE("%s %p\n", wine_dbgstr_w(path
), server
);
939 if (is_prefixed_unc(path
))
941 else if (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '?')
944 if (server
) *server
= result
;
948 BOOL WINAPI
PathIsUNCA(const char *path
)
950 TRACE("%s\n", wine_dbgstr_a(path
));
952 return path
&& (path
[0] == '\\') && (path
[1] == '\\');
955 BOOL WINAPI
PathIsUNCW(const WCHAR
*path
)
957 TRACE("%s\n", wine_dbgstr_w(path
));
959 return path
&& (path
[0] == '\\') && (path
[1] == '\\');
962 BOOL WINAPI
PathIsRelativeA(const char *path
)
964 TRACE("%s\n", wine_dbgstr_a(path
));
966 if (!path
|| !*path
|| IsDBCSLeadByte(*path
))
969 return !(*path
== '\\' || (*path
&& path
[1] == ':'));
972 BOOL WINAPI
PathIsRelativeW(const WCHAR
*path
)
974 TRACE("%s\n", wine_dbgstr_w(path
));
979 return !(*path
== '\\' || (*path
&& path
[1] == ':'));
982 BOOL WINAPI
PathIsUNCServerShareA(const char *path
)
984 BOOL seen_slash
= FALSE
;
986 TRACE("%s\n", wine_dbgstr_a(path
));
988 if (path
&& *path
++ == '\\' && *path
++ == '\\')
999 path
= CharNextA(path
);
1006 BOOL WINAPI
PathIsUNCServerShareW(const WCHAR
*path
)
1008 BOOL seen_slash
= FALSE
;
1010 TRACE("%s\n", wine_dbgstr_w(path
));
1012 if (path
&& *path
++ == '\\' && *path
++ == '\\')
1030 BOOL WINAPI
PathIsRootA(const char *path
)
1032 TRACE("%s\n", wine_dbgstr_a(path
));
1034 if (!path
|| !*path
)
1040 return TRUE
; /* \ */
1041 else if (path
[1] == '\\')
1043 BOOL seen_slash
= FALSE
;
1046 /* Check for UNC root path */
1056 path
= CharNextA(path
);
1062 else if (path
[1] == ':' && path
[2] == '\\' && path
[3] == '\0')
1063 return TRUE
; /* X:\ */
1068 BOOL WINAPI
PathIsRootW(const WCHAR
*path
)
1070 TRACE("%s\n", wine_dbgstr_w(path
));
1072 if (!path
|| !*path
)
1078 return TRUE
; /* \ */
1079 else if (path
[1] == '\\')
1081 BOOL seen_slash
= FALSE
;
1084 /* Check for UNC root path */
1099 else if (path
[1] == ':' && path
[2] == '\\' && path
[3] == '\0')
1100 return TRUE
; /* X:\ */
1105 BOOL WINAPI
PathRemoveFileSpecA(char *path
)
1107 char *filespec
= path
;
1108 BOOL modified
= FALSE
;
1110 TRACE("%s\n", wine_dbgstr_a(path
));
1115 /* Skip directory or UNC path */
1124 filespec
= path
; /* Skip dir */
1125 else if (*path
== ':')
1127 filespec
= ++path
; /* Skip drive */
1131 if (!(path
= CharNextA(path
)))
1144 BOOL WINAPI
PathRemoveFileSpecW(WCHAR
*path
)
1146 WCHAR
*filespec
= path
;
1147 BOOL modified
= FALSE
;
1149 TRACE("%s\n", wine_dbgstr_w(path
));
1154 /* Skip directory or UNC path */
1163 filespec
= path
; /* Skip dir */
1164 else if (*path
== ':')
1166 filespec
= ++path
; /* Skip drive */
1183 BOOL WINAPI
PathStripToRootA(char *path
)
1185 TRACE("%s\n", wine_dbgstr_a(path
));
1190 while (!PathIsRootA(path
))
1191 if (!PathRemoveFileSpecA(path
))
1197 BOOL WINAPI
PathStripToRootW(WCHAR
*path
)
1199 TRACE("%s\n", wine_dbgstr_w(path
));
1204 while (!PathIsRootW(path
))
1205 if (!PathRemoveFileSpecW(path
))
1211 LPSTR WINAPI
PathAddBackslashA(char *path
)
1216 TRACE("%s\n", wine_dbgstr_a(path
));
1218 if (!path
|| (len
= strlen(path
)) >= MAX_PATH
)
1225 path
= CharNextA(prev
);
1240 LPWSTR WINAPI
PathAddBackslashW(WCHAR
*path
)
1244 TRACE("%s\n", wine_dbgstr_w(path
));
1246 if (!path
|| (len
= lstrlenW(path
)) >= MAX_PATH
)
1252 if (path
[-1] != '\\')
1262 LPSTR WINAPI
PathFindExtensionA(const char *path
)
1264 const char *lastpoint
= NULL
;
1266 TRACE("%s\n", wine_dbgstr_a(path
));
1272 if (*path
== '\\' || *path
== ' ')
1274 else if (*path
== '.')
1276 path
= CharNextA(path
);
1280 return (LPSTR
)(lastpoint
? lastpoint
: path
);
1283 LPWSTR WINAPI
PathFindExtensionW(const WCHAR
*path
)
1285 const WCHAR
*lastpoint
= NULL
;
1287 TRACE("%s\n", wine_dbgstr_w(path
));
1293 if (*path
== '\\' || *path
== ' ')
1295 else if (*path
== '.')
1301 return (LPWSTR
)(lastpoint
? lastpoint
: path
);
1304 BOOL WINAPI
PathAddExtensionA(char *path
, const char *ext
)
1308 TRACE("%s, %s\n", wine_dbgstr_a(path
), wine_dbgstr_a(ext
));
1310 if (!path
|| !ext
|| *(PathFindExtensionA(path
)))
1314 if (len
+ strlen(ext
) >= MAX_PATH
)
1317 strcpy(path
+ len
, ext
);
1321 BOOL WINAPI
PathAddExtensionW(WCHAR
*path
, const WCHAR
*ext
)
1325 TRACE("%s, %s\n", wine_dbgstr_w(path
), wine_dbgstr_w(ext
));
1327 if (!path
|| !ext
|| *(PathFindExtensionW(path
)))
1330 len
= lstrlenW(path
);
1331 if (len
+ lstrlenW(ext
) >= MAX_PATH
)
1334 lstrcpyW(path
+ len
, ext
);
1338 BOOL WINAPI
PathCanonicalizeW(WCHAR
*buffer
, const WCHAR
*path
)
1340 const WCHAR
*src
= path
;
1341 WCHAR
*dst
= buffer
;
1343 TRACE("%p, %s\n", buffer
, wine_dbgstr_w(path
));
1350 SetLastError(ERROR_INVALID_PARAMETER
);
1361 /* Copy path root */
1366 else if (*src
&& src
[1] == ':')
1375 /* Canonicalize the rest of the path */
1380 if (src
[1] == '\\' && (src
== path
|| src
[-1] == '\\' || src
[-1] == ':'))
1382 src
+= 2; /* Skip .\ */
1384 else if (src
[1] == '.' && (dst
== buffer
|| dst
[-1] == '\\'))
1386 /* \.. backs up a directory, over the root if it has no \ following X:.
1387 * .. is ignored if it would remove a UNC server name or initial \\
1391 *dst
= '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
1392 if (dst
> buffer
+ 1 && dst
[-1] == '\\' && (dst
[-2] != '\\' || dst
> buffer
+ 2))
1394 if (dst
[-2] == ':' && (dst
> buffer
+ 3 || dst
[-3] == ':'))
1397 while (dst
> buffer
&& *dst
!= '\\')
1400 dst
++; /* Reset to last '\' */
1402 dst
= buffer
; /* Start path again from new root */
1404 else if (dst
[-2] != ':' && !PathIsUNCServerShareW(buffer
))
1407 while (dst
> buffer
&& *dst
!= '\\')
1415 src
+= 2; /* Skip .. in src path */
1424 /* Append \ to naked drive specs */
1425 if (dst
- buffer
== 2 && dst
[-1] == ':')
1431 BOOL WINAPI
PathCanonicalizeA(char *buffer
, const char *path
)
1433 WCHAR pathW
[MAX_PATH
], bufferW
[MAX_PATH
];
1437 TRACE("%p, %s\n", buffer
, wine_dbgstr_a(path
));
1442 if (!buffer
|| !path
)
1444 SetLastError(ERROR_INVALID_PARAMETER
);
1448 len
= MultiByteToWideChar(CP_ACP
, 0, path
, -1, pathW
, ARRAY_SIZE(pathW
));
1452 ret
= PathCanonicalizeW(bufferW
, pathW
);
1453 WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, buffer
, MAX_PATH
, 0, 0);
1458 WCHAR
* WINAPI
PathCombineW(WCHAR
*dst
, const WCHAR
*dir
, const WCHAR
*file
)
1460 BOOL use_both
= FALSE
, strip
= FALSE
;
1461 WCHAR tmp
[MAX_PATH
];
1463 TRACE("%p, %s, %s\n", dst
, wine_dbgstr_w(dir
), wine_dbgstr_w(file
));
1465 /* Invalid parameters */
1475 if ((!file
|| !*file
) && dir
)
1478 lstrcpynW(tmp
, dir
, ARRAY_SIZE(tmp
));
1480 else if (!dir
|| !*dir
|| !PathIsRelativeW(file
))
1482 if (!dir
|| !*dir
|| *file
!= '\\' || PathIsUNCW(file
))
1485 lstrcpynW(tmp
, file
, ARRAY_SIZE(tmp
));
1498 lstrcpynW(tmp
, dir
, ARRAY_SIZE(tmp
));
1501 PathStripToRootW(tmp
);
1502 file
++; /* Skip '\' */
1505 if (!PathAddBackslashW(tmp
) || lstrlenW(tmp
) + lstrlenW(file
) >= MAX_PATH
)
1511 lstrcatW(tmp
, file
);
1514 PathCanonicalizeW(dst
, tmp
);
1518 LPSTR WINAPI
PathCombineA(char *dst
, const char *dir
, const char *file
)
1520 WCHAR dstW
[MAX_PATH
], dirW
[MAX_PATH
], fileW
[MAX_PATH
];
1522 TRACE("%p, %s, %s\n", dst
, wine_dbgstr_a(dir
), wine_dbgstr_a(file
));
1524 /* Invalid parameters */
1531 if (dir
&& !MultiByteToWideChar(CP_ACP
, 0, dir
, -1, dirW
, ARRAY_SIZE(dirW
)))
1534 if (file
&& !MultiByteToWideChar(CP_ACP
, 0, file
, -1, fileW
, ARRAY_SIZE(fileW
)))
1537 if (PathCombineW(dstW
, dir
? dirW
: NULL
, file
? fileW
: NULL
))
1538 if (WideCharToMultiByte(CP_ACP
, 0, dstW
, -1, dst
, MAX_PATH
, 0, 0))
1545 BOOL WINAPI
PathAppendA(char *path
, const char *append
)
1547 TRACE("%s, %s\n", wine_dbgstr_a(path
), wine_dbgstr_a(append
));
1551 if (!PathIsUNCA(append
))
1552 while (*append
== '\\')
1555 if (PathCombineA(path
, path
, append
))
1562 BOOL WINAPI
PathAppendW(WCHAR
*path
, const WCHAR
*append
)
1564 TRACE("%s, %s\n", wine_dbgstr_w(path
), wine_dbgstr_w(append
));
1568 if (!PathIsUNCW(append
))
1569 while (*append
== '\\')
1572 if (PathCombineW(path
, path
, append
))
1579 int WINAPI
PathCommonPrefixA(const char *file1
, const char *file2
, char *path
)
1581 const char *iter1
= file1
;
1582 const char *iter2
= file2
;
1583 unsigned int len
= 0;
1585 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1
), wine_dbgstr_a(file2
), path
);
1590 if (!file1
|| !file2
)
1593 /* Handle roots first */
1594 if (PathIsUNCA(file1
))
1596 if (!PathIsUNCA(file2
))
1601 else if (PathIsUNCA(file2
))
1607 if ((!*iter1
|| *iter1
== '\\') && (!*iter2
|| *iter2
== '\\'))
1608 len
= iter1
- file1
; /* Common to this point */
1610 if (!*iter1
|| (tolower(*iter1
) != tolower(*iter2
)))
1611 break; /* Strings differ at this point */
1618 len
++; /* Feature/Bug compatible with Win32 */
1622 memcpy(path
, file1
, len
);
1629 int WINAPI
PathCommonPrefixW(const WCHAR
*file1
, const WCHAR
*file2
, WCHAR
*path
)
1631 const WCHAR
*iter1
= file1
;
1632 const WCHAR
*iter2
= file2
;
1633 unsigned int len
= 0;
1635 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1
), wine_dbgstr_w(file2
), path
);
1640 if (!file1
|| !file2
)
1643 /* Handle roots first */
1644 if (PathIsUNCW(file1
))
1646 if (!PathIsUNCW(file2
))
1651 else if (PathIsUNCW(file2
))
1657 if ((!*iter1
|| *iter1
== '\\') && (!*iter2
|| *iter2
== '\\'))
1658 len
= iter1
- file1
; /* Common to this point */
1660 if (!*iter1
|| (towlower(*iter1
) != towlower(*iter2
)))
1661 break; /* Strings differ at this point */
1668 len
++; /* Feature/Bug compatible with Win32 */
1672 memcpy(path
, file1
, len
* sizeof(WCHAR
));
1679 BOOL WINAPI
PathIsPrefixA(const char *prefix
, const char *path
)
1681 TRACE("%s, %s\n", wine_dbgstr_a(prefix
), wine_dbgstr_a(path
));
1683 return prefix
&& path
&& PathCommonPrefixA(path
, prefix
, NULL
) == (int)strlen(prefix
);
1686 BOOL WINAPI
PathIsPrefixW(const WCHAR
*prefix
, const WCHAR
*path
)
1688 TRACE("%s, %s\n", wine_dbgstr_w(prefix
), wine_dbgstr_w(path
));
1690 return prefix
&& path
&& PathCommonPrefixW(path
, prefix
, NULL
) == (int)lstrlenW(prefix
);
1693 char * WINAPI
PathFindFileNameA(const char *path
)
1695 const char *last_slash
= path
;
1697 TRACE("%s\n", wine_dbgstr_a(path
));
1699 while (path
&& *path
)
1701 if ((*path
== '\\' || *path
== '/' || *path
== ':') &&
1702 path
[1] && path
[1] != '\\' && path
[1] != '/')
1703 last_slash
= path
+ 1;
1704 path
= CharNextA(path
);
1707 return (char *)last_slash
;
1710 WCHAR
* WINAPI
PathFindFileNameW(const WCHAR
*path
)
1712 const WCHAR
*last_slash
= path
;
1714 TRACE("%s\n", wine_dbgstr_w(path
));
1716 while (path
&& *path
)
1718 if ((*path
== '\\' || *path
== '/' || *path
== ':') &&
1719 path
[1] && path
[1] != '\\' && path
[1] != '/')
1720 last_slash
= path
+ 1;
1724 return (WCHAR
*)last_slash
;
1727 char * WINAPI
PathGetArgsA(const char *path
)
1729 BOOL seen_quote
= FALSE
;
1731 TRACE("%s\n", wine_dbgstr_a(path
));
1738 if (*path
== ' ' && !seen_quote
)
1739 return (char *)path
+ 1;
1742 seen_quote
= !seen_quote
;
1743 path
= CharNextA(path
);
1746 return (char *)path
;
1749 WCHAR
* WINAPI
PathGetArgsW(const WCHAR
*path
)
1751 BOOL seen_quote
= FALSE
;
1753 TRACE("%s\n", wine_dbgstr_w(path
));
1760 if (*path
== ' ' && !seen_quote
)
1761 return (WCHAR
*)path
+ 1;
1764 seen_quote
= !seen_quote
;
1768 return (WCHAR
*)path
;
1771 UINT WINAPI
PathGetCharTypeW(WCHAR ch
)
1777 if (!ch
|| ch
< ' ' || ch
== '<' || ch
== '>' || ch
== '"' || ch
== '|' || ch
== '/')
1778 flags
= GCT_INVALID
; /* Invalid */
1779 else if (ch
== '*' || ch
== '?')
1780 flags
= GCT_WILD
; /* Wildchars */
1781 else if (ch
== '\\' || ch
== ':')
1782 return GCT_SEPARATOR
; /* Path separators */
1787 if (((ch
& 0x1) && ch
!= ';') || !ch
|| iswalnum(ch
) || ch
== '$' || ch
== '&' || ch
== '(' ||
1788 ch
== '.' || ch
== '@' || ch
== '^' || ch
== '\'' || ch
== 130 || ch
== '`')
1790 flags
|= GCT_SHORTCHAR
; /* All these are valid for DOS */
1794 flags
|= GCT_SHORTCHAR
; /* Bug compatible with win32 */
1796 flags
|= GCT_LFNCHAR
; /* Valid for long file names */
1802 UINT WINAPI
PathGetCharTypeA(UCHAR ch
)
1804 return PathGetCharTypeW(ch
);
1807 int WINAPI
PathGetDriveNumberA(const char *path
)
1809 TRACE("%s\n", wine_dbgstr_a(path
));
1811 if (path
&& !IsDBCSLeadByte(*path
) && path
[1] == ':')
1813 if (*path
>= 'a' && *path
<= 'z') return *path
- 'a';
1814 if (*path
>= 'A' && *path
<= 'Z') return *path
- 'A';
1819 int WINAPI
PathGetDriveNumberW(const WCHAR
*path
)
1821 static const WCHAR nt_prefixW
[] = {'\\','\\','?','\\'};
1824 TRACE("%s\n", wine_dbgstr_w(path
));
1829 if (!wcsncmp(path
, nt_prefixW
, 4))
1832 drive
= towlower(path
[0]);
1833 if (drive
< 'a' || drive
> 'z' || path
[1] != ':')
1839 BOOL WINAPI
PathIsFileSpecA(const char *path
)
1841 TRACE("%s\n", wine_dbgstr_a(path
));
1848 if (*path
== '\\' || *path
== ':')
1850 path
= CharNextA(path
);
1856 BOOL WINAPI
PathIsFileSpecW(const WCHAR
*path
)
1858 TRACE("%s\n", wine_dbgstr_w(path
));
1865 if (*path
== '\\' || *path
== ':')
1873 BOOL WINAPI
PathIsUNCServerA(const char *path
)
1875 TRACE("%s\n", wine_dbgstr_a(path
));
1877 if (!(path
&& path
[0] == '\\' && path
[1] == '\\'))
1884 path
= CharNextA(path
);
1890 BOOL WINAPI
PathIsUNCServerW(const WCHAR
*path
)
1892 TRACE("%s\n", wine_dbgstr_w(path
));
1894 if (!(path
&& path
[0] == '\\' && path
[1] == '\\'))
1897 return !wcschr(path
+ 2, '\\');
1900 void WINAPI
PathRemoveBlanksA(char *path
)
1904 TRACE("%s\n", wine_dbgstr_a(path
));
1906 if (!path
|| !*path
)
1911 while (*path
== ' ')
1912 path
= CharNextA(path
);
1918 while (start
[-1] == ' ')
1924 void WINAPI
PathRemoveBlanksW(WCHAR
*path
)
1926 WCHAR
*start
= path
;
1928 TRACE("%s\n", wine_dbgstr_w(path
));
1930 if (!path
|| !*path
)
1933 while (*path
== ' ')
1940 while (start
[-1] == ' ')
1946 void WINAPI
PathRemoveExtensionA(char *path
)
1948 TRACE("%s\n", wine_dbgstr_a(path
));
1953 path
= PathFindExtensionA(path
);
1958 void WINAPI
PathRemoveExtensionW(WCHAR
*path
)
1960 TRACE("%s\n", wine_dbgstr_w(path
));
1965 path
= PathFindExtensionW(path
);
1970 BOOL WINAPI
PathRenameExtensionA(char *path
, const char *ext
)
1974 TRACE("%s, %s\n", wine_dbgstr_a(path
), wine_dbgstr_a(ext
));
1976 extension
= PathFindExtensionA(path
);
1978 if (!extension
|| (extension
- path
+ strlen(ext
) >= MAX_PATH
))
1981 strcpy(extension
, ext
);
1985 BOOL WINAPI
PathRenameExtensionW(WCHAR
*path
, const WCHAR
*ext
)
1989 TRACE("%s, %s\n", wine_dbgstr_w(path
), wine_dbgstr_w(ext
));
1991 extension
= PathFindExtensionW(path
);
1993 if (!extension
|| (extension
- path
+ lstrlenW(ext
) >= MAX_PATH
))
1996 lstrcpyW(extension
, ext
);
2000 void WINAPI
PathUnquoteSpacesA(char *path
)
2004 TRACE("%s\n", wine_dbgstr_a(path
));
2006 if (!path
|| *path
!= '"')
2009 len
= strlen(path
) - 1;
2010 if (path
[len
] == '"')
2013 for (; *path
; path
++)
2018 void WINAPI
PathUnquoteSpacesW(WCHAR
*path
)
2022 TRACE("%s\n", wine_dbgstr_w(path
));
2024 if (!path
|| *path
!= '"')
2027 len
= lstrlenW(path
) - 1;
2028 if (path
[len
] == '"')
2031 for (; *path
; path
++)
2036 char * WINAPI
PathRemoveBackslashA(char *path
)
2040 TRACE("%s\n", wine_dbgstr_a(path
));
2045 ptr
= CharPrevA(path
, path
+ strlen(path
));
2046 if (!PathIsRootA(path
) && *ptr
== '\\')
2052 WCHAR
* WINAPI
PathRemoveBackslashW(WCHAR
*path
)
2056 TRACE("%s\n", wine_dbgstr_w(path
));
2061 ptr
= path
+ lstrlenW(path
);
2062 if (ptr
> path
) ptr
--;
2063 if (!PathIsRootW(path
) && *ptr
== '\\')
2069 BOOL WINAPI
PathIsLFNFileSpecA(const char *path
)
2071 unsigned int name_len
= 0, ext_len
= 0;
2073 TRACE("%s\n", wine_dbgstr_a(path
));
2081 return TRUE
; /* DOS names cannot have spaces */
2085 return TRUE
; /* DOS names have only one dot */
2092 return TRUE
; /* DOS extensions are <= 3 chars*/
2098 return TRUE
; /* DOS names are <= 8 chars */
2100 path
= CharNextA(path
);
2103 return FALSE
; /* Valid DOS path */
2106 BOOL WINAPI
PathIsLFNFileSpecW(const WCHAR
*path
)
2108 unsigned int name_len
= 0, ext_len
= 0;
2110 TRACE("%s\n", wine_dbgstr_w(path
));
2118 return TRUE
; /* DOS names cannot have spaces */
2122 return TRUE
; /* DOS names have only one dot */
2129 return TRUE
; /* DOS extensions are <= 3 chars*/
2135 return TRUE
; /* DOS names are <= 8 chars */
2140 return FALSE
; /* Valid DOS path */
2143 #define PATH_CHAR_CLASS_LETTER 0x00000001
2144 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2145 #define PATH_CHAR_CLASS_DOT 0x00000004
2146 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2147 #define PATH_CHAR_CLASS_COLON 0x00000010
2148 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2149 #define PATH_CHAR_CLASS_COMMA 0x00000040
2150 #define PATH_CHAR_CLASS_SPACE 0x00000080
2151 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2152 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2154 #define PATH_CHAR_CLASS_INVALID 0x00000000
2155 #define PATH_CHAR_CLASS_ANY 0xffffffff
2157 static const DWORD path_charclass
[] =
2159 /* 0x00 */ PATH_CHAR_CLASS_INVALID
, /* 0x01 */ PATH_CHAR_CLASS_INVALID
,
2160 /* 0x02 */ PATH_CHAR_CLASS_INVALID
, /* 0x03 */ PATH_CHAR_CLASS_INVALID
,
2161 /* 0x04 */ PATH_CHAR_CLASS_INVALID
, /* 0x05 */ PATH_CHAR_CLASS_INVALID
,
2162 /* 0x06 */ PATH_CHAR_CLASS_INVALID
, /* 0x07 */ PATH_CHAR_CLASS_INVALID
,
2163 /* 0x08 */ PATH_CHAR_CLASS_INVALID
, /* 0x09 */ PATH_CHAR_CLASS_INVALID
,
2164 /* 0x0a */ PATH_CHAR_CLASS_INVALID
, /* 0x0b */ PATH_CHAR_CLASS_INVALID
,
2165 /* 0x0c */ PATH_CHAR_CLASS_INVALID
, /* 0x0d */ PATH_CHAR_CLASS_INVALID
,
2166 /* 0x0e */ PATH_CHAR_CLASS_INVALID
, /* 0x0f */ PATH_CHAR_CLASS_INVALID
,
2167 /* 0x10 */ PATH_CHAR_CLASS_INVALID
, /* 0x11 */ PATH_CHAR_CLASS_INVALID
,
2168 /* 0x12 */ PATH_CHAR_CLASS_INVALID
, /* 0x13 */ PATH_CHAR_CLASS_INVALID
,
2169 /* 0x14 */ PATH_CHAR_CLASS_INVALID
, /* 0x15 */ PATH_CHAR_CLASS_INVALID
,
2170 /* 0x16 */ PATH_CHAR_CLASS_INVALID
, /* 0x17 */ PATH_CHAR_CLASS_INVALID
,
2171 /* 0x18 */ PATH_CHAR_CLASS_INVALID
, /* 0x19 */ PATH_CHAR_CLASS_INVALID
,
2172 /* 0x1a */ PATH_CHAR_CLASS_INVALID
, /* 0x1b */ PATH_CHAR_CLASS_INVALID
,
2173 /* 0x1c */ PATH_CHAR_CLASS_INVALID
, /* 0x1d */ PATH_CHAR_CLASS_INVALID
,
2174 /* 0x1e */ PATH_CHAR_CLASS_INVALID
, /* 0x1f */ PATH_CHAR_CLASS_INVALID
,
2175 /* ' ' */ PATH_CHAR_CLASS_SPACE
, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID
,
2176 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE
, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID
,
2177 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID
,
2178 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID
,
2179 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID
, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID
,
2180 /* '*' */ PATH_CHAR_CLASS_ASTERIX
, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID
,
2181 /* ',' */ PATH_CHAR_CLASS_COMMA
, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID
,
2182 /* '.' */ PATH_CHAR_CLASS_DOT
, /* '/' */ PATH_CHAR_CLASS_INVALID
,
2183 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID
,
2184 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID
,
2185 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID
,
2186 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID
,
2187 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID
,
2188 /* ':' */ PATH_CHAR_CLASS_COLON
, /* ';' */ PATH_CHAR_CLASS_SEMICOLON
,
2189 /* '<' */ PATH_CHAR_CLASS_INVALID
, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID
,
2190 /* '>' */ PATH_CHAR_CLASS_INVALID
, /* '?' */ PATH_CHAR_CLASS_LETTER
,
2191 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID
, /* 'A' */ PATH_CHAR_CLASS_ANY
,
2192 /* 'B' */ PATH_CHAR_CLASS_ANY
, /* 'C' */ PATH_CHAR_CLASS_ANY
,
2193 /* 'D' */ PATH_CHAR_CLASS_ANY
, /* 'E' */ PATH_CHAR_CLASS_ANY
,
2194 /* 'F' */ PATH_CHAR_CLASS_ANY
, /* 'G' */ PATH_CHAR_CLASS_ANY
,
2195 /* 'H' */ PATH_CHAR_CLASS_ANY
, /* 'I' */ PATH_CHAR_CLASS_ANY
,
2196 /* 'J' */ PATH_CHAR_CLASS_ANY
, /* 'K' */ PATH_CHAR_CLASS_ANY
,
2197 /* 'L' */ PATH_CHAR_CLASS_ANY
, /* 'M' */ PATH_CHAR_CLASS_ANY
,
2198 /* 'N' */ PATH_CHAR_CLASS_ANY
, /* 'O' */ PATH_CHAR_CLASS_ANY
,
2199 /* 'P' */ PATH_CHAR_CLASS_ANY
, /* 'Q' */ PATH_CHAR_CLASS_ANY
,
2200 /* 'R' */ PATH_CHAR_CLASS_ANY
, /* 'S' */ PATH_CHAR_CLASS_ANY
,
2201 /* 'T' */ PATH_CHAR_CLASS_ANY
, /* 'U' */ PATH_CHAR_CLASS_ANY
,
2202 /* 'V' */ PATH_CHAR_CLASS_ANY
, /* 'W' */ PATH_CHAR_CLASS_ANY
,
2203 /* 'X' */ PATH_CHAR_CLASS_ANY
, /* 'Y' */ PATH_CHAR_CLASS_ANY
,
2204 /* 'Z' */ PATH_CHAR_CLASS_ANY
, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID
,
2205 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH
, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID
,
2206 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID
, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID
,
2207 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID
, /* 'a' */ PATH_CHAR_CLASS_ANY
,
2208 /* 'b' */ PATH_CHAR_CLASS_ANY
, /* 'c' */ PATH_CHAR_CLASS_ANY
,
2209 /* 'd' */ PATH_CHAR_CLASS_ANY
, /* 'e' */ PATH_CHAR_CLASS_ANY
,
2210 /* 'f' */ PATH_CHAR_CLASS_ANY
, /* 'g' */ PATH_CHAR_CLASS_ANY
,
2211 /* 'h' */ PATH_CHAR_CLASS_ANY
, /* 'i' */ PATH_CHAR_CLASS_ANY
,
2212 /* 'j' */ PATH_CHAR_CLASS_ANY
, /* 'k' */ PATH_CHAR_CLASS_ANY
,
2213 /* 'l' */ PATH_CHAR_CLASS_ANY
, /* 'm' */ PATH_CHAR_CLASS_ANY
,
2214 /* 'n' */ PATH_CHAR_CLASS_ANY
, /* 'o' */ PATH_CHAR_CLASS_ANY
,
2215 /* 'p' */ PATH_CHAR_CLASS_ANY
, /* 'q' */ PATH_CHAR_CLASS_ANY
,
2216 /* 'r' */ PATH_CHAR_CLASS_ANY
, /* 's' */ PATH_CHAR_CLASS_ANY
,
2217 /* 't' */ PATH_CHAR_CLASS_ANY
, /* 'u' */ PATH_CHAR_CLASS_ANY
,
2218 /* 'v' */ PATH_CHAR_CLASS_ANY
, /* 'w' */ PATH_CHAR_CLASS_ANY
,
2219 /* 'x' */ PATH_CHAR_CLASS_ANY
, /* 'y' */ PATH_CHAR_CLASS_ANY
,
2220 /* 'z' */ PATH_CHAR_CLASS_ANY
, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID
,
2221 /* '|' */ PATH_CHAR_CLASS_INVALID
, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID
,
2222 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2225 BOOL WINAPI
PathIsValidCharA(char c
, DWORD
class)
2227 if ((unsigned)c
> 0x7e)
2228 return class & PATH_CHAR_CLASS_OTHER_VALID
;
2230 return class & path_charclass
[(unsigned)c
];
2233 BOOL WINAPI
PathIsValidCharW(WCHAR c
, DWORD
class)
2236 return class & PATH_CHAR_CLASS_OTHER_VALID
;
2238 return class & path_charclass
[c
];
2241 char * WINAPI
PathFindNextComponentA(const char *path
)
2245 TRACE("%s\n", wine_dbgstr_a(path
));
2247 if (!path
|| !*path
)
2250 if ((slash
= StrChrA(path
, '\\')))
2252 if (slash
[1] == '\\')
2257 return (char *)path
+ strlen(path
);
2260 WCHAR
* WINAPI
PathFindNextComponentW(const WCHAR
*path
)
2264 TRACE("%s\n", wine_dbgstr_w(path
));
2266 if (!path
|| !*path
)
2269 if ((slash
= StrChrW(path
, '\\')))
2271 if (slash
[1] == '\\')
2276 return (WCHAR
*)path
+ lstrlenW(path
);
2279 char * WINAPI
PathSkipRootA(const char *path
)
2281 TRACE("%s\n", wine_dbgstr_a(path
));
2283 if (!path
|| !*path
)
2286 if (*path
== '\\' && path
[1] == '\\')
2288 /* Network share: skip share server and mount point */
2290 if ((path
= StrChrA(path
, '\\')) && (path
= StrChrA(path
+ 1, '\\')))
2292 return (char *)path
;
2295 if (IsDBCSLeadByte(*path
))
2299 if (path
[0] && path
[1] == ':' && path
[2] == '\\')
2300 return (char *)path
+ 3;
2305 WCHAR
* WINAPI
PathSkipRootW(const WCHAR
*path
)
2307 TRACE("%s\n", wine_dbgstr_w(path
));
2309 if (!path
|| !*path
)
2312 if (*path
== '\\' && path
[1] == '\\')
2314 /* Network share: skip share server and mount point */
2316 if ((path
= StrChrW(path
, '\\')) && (path
= StrChrW(path
+ 1, '\\')))
2318 return (WCHAR
*)path
;
2322 if (path
[0] && path
[1] == ':' && path
[2] == '\\')
2323 return (WCHAR
*)path
+ 3;
2328 void WINAPI
PathStripPathA(char *path
)
2330 TRACE("%s\n", wine_dbgstr_a(path
));
2334 char *filename
= PathFindFileNameA(path
);
2335 if (filename
!= path
)
2336 RtlMoveMemory(path
, filename
, strlen(filename
) + 1);
2340 void WINAPI
PathStripPathW(WCHAR
*path
)
2344 TRACE("%s\n", wine_dbgstr_w(path
));
2345 filename
= PathFindFileNameW(path
);
2346 if (filename
!= path
)
2347 RtlMoveMemory(path
, filename
, (lstrlenW(filename
) + 1) * sizeof(WCHAR
));
2350 BOOL WINAPI
PathSearchAndQualifyA(const char *path
, char *buffer
, UINT length
)
2352 TRACE("%s, %p, %u\n", wine_dbgstr_a(path
), buffer
, length
);
2354 if (SearchPathA(NULL
, path
, NULL
, length
, buffer
, NULL
))
2357 return !!GetFullPathNameA(path
, length
, buffer
, NULL
);
2360 BOOL WINAPI
PathSearchAndQualifyW(const WCHAR
*path
, WCHAR
*buffer
, UINT length
)
2362 TRACE("%s, %p, %u\n", wine_dbgstr_w(path
), buffer
, length
);
2364 if (SearchPathW(NULL
, path
, NULL
, length
, buffer
, NULL
))
2366 return !!GetFullPathNameW(path
, length
, buffer
, NULL
);
2369 BOOL WINAPI
PathRelativePathToA(char *path
, const char *from
, DWORD attributes_from
, const char *to
,
2370 DWORD attributes_to
)
2372 WCHAR pathW
[MAX_PATH
], fromW
[MAX_PATH
], toW
[MAX_PATH
];
2375 TRACE("%p, %s, %#x, %s, %#x\n", path
, wine_dbgstr_a(from
), attributes_from
, wine_dbgstr_a(to
), attributes_to
);
2377 if (!path
|| !from
|| !to
)
2380 MultiByteToWideChar(CP_ACP
, 0, from
, -1, fromW
, ARRAY_SIZE(fromW
));
2381 MultiByteToWideChar(CP_ACP
, 0, to
, -1, toW
, ARRAY_SIZE(toW
));
2382 ret
= PathRelativePathToW(pathW
, fromW
, attributes_from
, toW
, attributes_to
);
2383 WideCharToMultiByte(CP_ACP
, 0, pathW
, -1, path
, MAX_PATH
, 0, 0);
2388 BOOL WINAPI
PathRelativePathToW(WCHAR
*path
, const WCHAR
*from
, DWORD attributes_from
, const WCHAR
*to
,
2389 DWORD attributes_to
)
2391 static const WCHAR szPrevDirSlash
[] = { '.', '.', '\\', '\0' };
2392 static const WCHAR szPrevDir
[] = { '.', '.', '\0' };
2393 WCHAR fromW
[MAX_PATH
], toW
[MAX_PATH
];
2396 TRACE("%p, %s, %#x, %s, %#x\n", path
, wine_dbgstr_w(from
), attributes_from
, wine_dbgstr_w(to
), attributes_to
);
2398 if (!path
|| !from
|| !to
)
2402 lstrcpynW(fromW
, from
, ARRAY_SIZE(fromW
));
2403 lstrcpynW(toW
, to
, ARRAY_SIZE(toW
));
2405 if (!(attributes_from
& FILE_ATTRIBUTE_DIRECTORY
))
2406 PathRemoveFileSpecW(fromW
);
2407 if (!(attributes_to
& FILE_ATTRIBUTE_DIRECTORY
))
2408 PathRemoveFileSpecW(toW
);
2410 /* Paths can only be relative if they have a common root */
2411 if (!(len
= PathCommonPrefixW(fromW
, toW
, 0)))
2414 /* Strip off 'from' components to the root, by adding "..\" */
2426 from
= PathFindNextComponentW(from
);
2427 lstrcatW(path
, *from
? szPrevDirSlash
: szPrevDir
);
2430 /* From the root add the components of 'to' */
2432 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2437 len
= lstrlenW(path
);
2438 if (len
+ lstrlenW(to
) >= MAX_PATH
)
2443 lstrcpyW(path
+ len
, to
);
2449 BOOL WINAPI
PathMatchSpecA(const char *path
, const char *mask
)
2451 WCHAR
*pathW
, *maskW
;
2454 TRACE("%s, %s\n", wine_dbgstr_a(path
), wine_dbgstr_a(mask
));
2456 if (!lstrcmpA(mask
, "*.*"))
2457 return TRUE
; /* Matches every path */
2459 pathW
= heap_strdupAtoW( path
);
2460 maskW
= heap_strdupAtoW( mask
);
2461 ret
= PathMatchSpecW( pathW
, maskW
);
2467 static BOOL
path_match_maskW(const WCHAR
*name
, const WCHAR
*mask
)
2469 while (*name
&& *mask
&& *mask
!= ';')
2475 if (path_match_maskW(name
, mask
+ 1))
2476 return TRUE
; /* try substrings */
2481 if (towupper(*mask
) != towupper(*name
) && *mask
!= '?')
2490 while (*mask
== '*')
2492 if (!*mask
|| *mask
== ';')
2499 BOOL WINAPI
PathMatchSpecW(const WCHAR
*path
, const WCHAR
*mask
)
2501 static const WCHAR maskallW
[] = {'*','.','*',0};
2503 TRACE("%s, %s\n", wine_dbgstr_w(path
), wine_dbgstr_w(mask
));
2505 if (!lstrcmpW(mask
, maskallW
))
2506 return TRUE
; /* Matches every path */
2510 while (*mask
== ' ')
2511 mask
++; /* Eat leading spaces */
2513 if (path_match_maskW(path
, mask
))
2514 return TRUE
; /* Matches the current path */
2516 while (*mask
&& *mask
!= ';')
2517 mask
++; /* masks separated by ';' */
2526 void WINAPI
PathQuoteSpacesA(char *path
)
2528 TRACE("%s\n", wine_dbgstr_a(path
));
2530 if (path
&& StrChrA(path
, ' '))
2532 size_t len
= strlen(path
) + 1;
2534 if (len
+ 2 < MAX_PATH
)
2536 memmove(path
+ 1, path
, len
);
2539 path
[len
+ 1] = '\0';
2544 void WINAPI
PathQuoteSpacesW(WCHAR
*path
)
2546 TRACE("%s\n", wine_dbgstr_w(path
));
2548 if (path
&& StrChrW(path
, ' '))
2550 int len
= lstrlenW(path
) + 1;
2552 if (len
+ 2 < MAX_PATH
)
2554 memmove(path
+ 1, path
, len
* sizeof(WCHAR
));
2557 path
[len
+ 1] = '\0';
2562 BOOL WINAPI
PathIsSameRootA(const char *path1
, const char *path2
)
2567 TRACE("%s, %s\n", wine_dbgstr_a(path1
), wine_dbgstr_a(path2
));
2569 if (!path1
|| !path2
|| !(start
= PathSkipRootA(path1
)))
2572 len
= PathCommonPrefixA(path1
, path2
, NULL
) + 1;
2573 return start
- path1
<= len
;
2576 BOOL WINAPI
PathIsSameRootW(const WCHAR
*path1
, const WCHAR
*path2
)
2581 TRACE("%s, %s\n", wine_dbgstr_w(path1
), wine_dbgstr_w(path2
));
2583 if (!path1
|| !path2
|| !(start
= PathSkipRootW(path1
)))
2586 len
= PathCommonPrefixW(path1
, path2
, NULL
) + 1;
2587 return start
- path1
<= len
;
2590 BOOL WINAPI
PathFileExistsA(const char *path
)
2595 TRACE("%s\n", wine_dbgstr_a(path
));
2600 /* Prevent a dialog box if path is on a disk that has been ejected. */
2601 prev_mode
= SetErrorMode(SEM_FAILCRITICALERRORS
);
2602 attrs
= GetFileAttributesA(path
);
2603 SetErrorMode(prev_mode
);
2604 return attrs
!= INVALID_FILE_ATTRIBUTES
;
2607 BOOL WINAPI
PathFileExistsW(const WCHAR
*path
)
2612 TRACE("%s\n", wine_dbgstr_w(path
));
2617 prev_mode
= SetErrorMode(SEM_FAILCRITICALERRORS
);
2618 attrs
= GetFileAttributesW(path
);
2619 SetErrorMode(prev_mode
);
2620 return attrs
!= INVALID_FILE_ATTRIBUTES
;
2623 int WINAPI
PathParseIconLocationA(char *path
)
2628 TRACE("%s\n", debugstr_a(path
));
2633 if ((comma
= strchr(path
, ',')))
2636 ret
= StrToIntA(comma
);
2638 PathUnquoteSpacesA(path
);
2639 PathRemoveBlanksA(path
);
2644 int WINAPI
PathParseIconLocationW(WCHAR
*path
)
2649 TRACE("%s\n", debugstr_w(path
));
2654 if ((comma
= StrChrW(path
, ',')))
2657 ret
= StrToIntW(comma
);
2659 PathUnquoteSpacesW(path
);
2660 PathRemoveBlanksW(path
);
2665 BOOL WINAPI
PathUnExpandEnvStringsA(const char *path
, char *buffer
, UINT buf_len
)
2667 WCHAR bufferW
[MAX_PATH
], *pathW
;
2671 TRACE("%s, %p, %d\n", debugstr_a(path
), buffer
, buf_len
);
2673 pathW
= heap_strdupAtoW(path
);
2674 if (!pathW
) return FALSE
;
2676 ret
= PathUnExpandEnvStringsW(pathW
, bufferW
, MAX_PATH
);
2677 HeapFree(GetProcessHeap(), 0, pathW
);
2678 if (!ret
) return FALSE
;
2680 len
= WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, NULL
, 0, NULL
, NULL
);
2681 if (buf_len
< len
+ 1) return FALSE
;
2683 WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, buffer
, buf_len
, NULL
, NULL
);
2687 static const WCHAR allusersprofileW
[] = {'%','A','L','L','U','S','E','R','S','P','R','O','F','I','L','E','%',0};
2688 static const WCHAR appdataW
[] = {'%','A','P','P','D','A','T','A','%',0};
2689 static const WCHAR programfilesW
[] = {'%','P','r','o','g','r','a','m','F','i','l','e','s','%',0};
2690 static const WCHAR systemrootW
[] = {'%','S','y','s','t','e','m','R','o','o','t','%',0};
2691 static const WCHAR systemdriveW
[] = {'%','S','y','s','t','e','m','D','r','i','v','e','%',0};
2692 static const WCHAR userprofileW
[] = {'%','U','S','E','R','P','R','O','F','I','L','E','%',0};
2698 WCHAR path
[MAX_PATH
];
2702 static void init_envvars_map(struct envvars_map
*map
)
2706 map
->len
= ExpandEnvironmentStringsW(map
->var
, map
->path
, ARRAY_SIZE(map
->path
));
2707 /* exclude null from length */
2708 if (map
->len
) map
->len
--;
2713 BOOL WINAPI
PathUnExpandEnvStringsW(const WCHAR
*path
, WCHAR
*buffer
, UINT buf_len
)
2715 static struct envvars_map null_var
= {NULL
, 0, {0}, 0};
2716 struct envvars_map
*match
= &null_var
, *cur
;
2717 struct envvars_map envvars
[] =
2719 { allusersprofileW
, ARRAY_SIZE(allusersprofileW
) },
2720 { appdataW
, ARRAY_SIZE(appdataW
) },
2721 { programfilesW
, ARRAY_SIZE(programfilesW
) },
2722 { systemrootW
, ARRAY_SIZE(systemrootW
) },
2723 { systemdriveW
, ARRAY_SIZE(systemdriveW
) },
2724 { userprofileW
, ARRAY_SIZE(userprofileW
) },
2730 TRACE("%s, %p, %d\n", debugstr_w(path
), buffer
, buf_len
);
2732 pathlen
= lstrlenW(path
);
2733 init_envvars_map(envvars
);
2737 /* path can't contain expanded value or value wasn't retrieved */
2738 if (cur
->len
== 0 || cur
->len
> pathlen
|| wcsnicmp(cur
->path
, path
, cur
->len
))
2744 if (cur
->len
> match
->len
)
2749 /* 'varlen' includes NULL termination char */
2750 needed
= match
->varlen
+ pathlen
- match
->len
;
2751 if (match
->len
== 0 || needed
> buf_len
) return FALSE
;
2753 lstrcpyW(buffer
, match
->var
);
2754 lstrcatW(buffer
, &path
[match
->len
]);
2755 TRACE("ret %s\n", debugstr_w(buffer
));
2762 URL_SCHEME scheme_number
;
2763 WCHAR scheme_name
[12];
2767 { URL_SCHEME_FTP
, {'f','t','p',0}},
2768 { URL_SCHEME_HTTP
, {'h','t','t','p',0}},
2769 { URL_SCHEME_GOPHER
, {'g','o','p','h','e','r',0}},
2770 { URL_SCHEME_MAILTO
, {'m','a','i','l','t','o',0}},
2771 { URL_SCHEME_NEWS
, {'n','e','w','s',0}},
2772 { URL_SCHEME_NNTP
, {'n','n','t','p',0}},
2773 { URL_SCHEME_TELNET
, {'t','e','l','n','e','t',0}},
2774 { URL_SCHEME_WAIS
, {'w','a','i','s',0}},
2775 { URL_SCHEME_FILE
, {'f','i','l','e',0}},
2776 { URL_SCHEME_MK
, {'m','k',0}},
2777 { URL_SCHEME_HTTPS
, {'h','t','t','p','s',0}},
2778 { URL_SCHEME_SHELL
, {'s','h','e','l','l',0}},
2779 { URL_SCHEME_SNEWS
, {'s','n','e','w','s',0}},
2780 { URL_SCHEME_LOCAL
, {'l','o','c','a','l',0}},
2781 { URL_SCHEME_JAVASCRIPT
, {'j','a','v','a','s','c','r','i','p','t',0}},
2782 { URL_SCHEME_VBSCRIPT
, {'v','b','s','c','r','i','p','t',0}},
2783 { URL_SCHEME_ABOUT
, {'a','b','o','u','t',0}},
2784 { URL_SCHEME_RES
, {'r','e','s',0}},
2787 static DWORD
get_scheme_code(const WCHAR
*scheme
, DWORD scheme_len
)
2791 for (i
= 0; i
< ARRAY_SIZE(url_schemes
); ++i
)
2793 if (scheme_len
== lstrlenW(url_schemes
[i
].scheme_name
)
2794 && !wcsnicmp(scheme
, url_schemes
[i
].scheme_name
, scheme_len
))
2795 return url_schemes
[i
].scheme_number
;
2798 return URL_SCHEME_UNKNOWN
;
2801 HRESULT WINAPI
ParseURLA(const char *url
, PARSEDURLA
*result
)
2803 WCHAR scheme
[INTERNET_MAX_SCHEME_LENGTH
];
2804 const char *ptr
= url
;
2807 TRACE("%s, %p\n", wine_dbgstr_a(url
), result
);
2809 if (result
->cbSize
!= sizeof(*result
))
2810 return E_INVALIDARG
;
2812 while (*ptr
&& ((*ptr
>= 'a' && *ptr
<= 'z') || (*ptr
>= 'A' && *ptr
<= 'Z') ||
2813 (*ptr
>= '0' && *ptr
<= '9') || *ptr
== '-' || *ptr
== '+' || *ptr
== '.'))
2816 if (*ptr
!= ':' || ptr
<= url
+ 1)
2818 result
->pszProtocol
= NULL
;
2819 return URL_E_INVALID_SYNTAX
;
2822 result
->pszProtocol
= url
;
2823 result
->cchProtocol
= ptr
- url
;
2824 result
->pszSuffix
= ptr
+ 1;
2825 result
->cchSuffix
= strlen(result
->pszSuffix
);
2827 len
= MultiByteToWideChar(CP_ACP
, 0, url
, ptr
- url
, scheme
, ARRAY_SIZE(scheme
));
2828 result
->nScheme
= get_scheme_code(scheme
, len
);
2833 HRESULT WINAPI
ParseURLW(const WCHAR
*url
, PARSEDURLW
*result
)
2835 const WCHAR
*ptr
= url
;
2837 TRACE("%s, %p\n", wine_dbgstr_w(url
), result
);
2839 if (result
->cbSize
!= sizeof(*result
))
2840 return E_INVALIDARG
;
2842 while (*ptr
&& (iswalnum(*ptr
) || *ptr
== '-' || *ptr
== '+' || *ptr
== '.'))
2845 if (*ptr
!= ':' || ptr
<= url
+ 1)
2847 result
->pszProtocol
= NULL
;
2848 return URL_E_INVALID_SYNTAX
;
2851 result
->pszProtocol
= url
;
2852 result
->cchProtocol
= ptr
- url
;
2853 result
->pszSuffix
= ptr
+ 1;
2854 result
->cchSuffix
= lstrlenW(result
->pszSuffix
);
2855 result
->nScheme
= get_scheme_code(url
, ptr
- url
);
2860 HRESULT WINAPI
UrlUnescapeA(char *url
, char *unescaped
, DWORD
*unescaped_len
, DWORD flags
)
2862 BOOL stop_unescaping
= FALSE
;
2868 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(url
), unescaped
, unescaped_len
, flags
);
2871 return E_INVALIDARG
;
2873 if (flags
& URL_UNESCAPE_INPLACE
)
2877 if (!unescaped
|| !unescaped_len
) return E_INVALIDARG
;
2881 for (src
= url
, needed
= 0; *src
; src
++, needed
++)
2883 if (flags
& URL_DONT_UNESCAPE_EXTRA_INFO
&& (*src
== '#' || *src
== '?'))
2885 stop_unescaping
= TRUE
;
2888 else if (*src
== '%' && isxdigit(*(src
+ 1)) && isxdigit(*(src
+ 2)) && !stop_unescaping
)
2892 memcpy(buf
, src
+ 1, 2);
2894 ih
= strtol(buf
, NULL
, 16);
2896 src
+= 2; /* Advance to end of escape */
2901 if (flags
& URL_UNESCAPE_INPLACE
|| needed
< *unescaped_len
)
2905 if (flags
& URL_UNESCAPE_INPLACE
|| needed
< *unescaped_len
)
2912 needed
++; /* add one for the '\0' */
2916 if (!(flags
& URL_UNESCAPE_INPLACE
))
2917 *unescaped_len
= needed
;
2920 TRACE("result %s\n", flags
& URL_UNESCAPE_INPLACE
? wine_dbgstr_a(url
) : wine_dbgstr_a(unescaped
));
2925 HRESULT WINAPI
UrlUnescapeW(WCHAR
*url
, WCHAR
*unescaped
, DWORD
*unescaped_len
, DWORD flags
)
2927 BOOL stop_unescaping
= FALSE
;
2933 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url
), unescaped
, unescaped_len
, flags
);
2936 return E_INVALIDARG
;
2938 if (flags
& URL_UNESCAPE_INPLACE
)
2942 if (!unescaped
|| !unescaped_len
) return E_INVALIDARG
;
2946 for (src
= url
, needed
= 0; *src
; src
++, needed
++)
2948 if (flags
& URL_DONT_UNESCAPE_EXTRA_INFO
&& (*src
== '#' || *src
== '?'))
2950 stop_unescaping
= TRUE
;
2953 else if (*src
== '%' && iswxdigit(*(src
+ 1)) && iswxdigit(*(src
+ 2)) && !stop_unescaping
)
2956 WCHAR buf
[5] = {'0','x',0};
2957 memcpy(buf
+ 2, src
+ 1, 2*sizeof(WCHAR
));
2959 StrToIntExW(buf
, STIF_SUPPORT_HEX
, &ih
);
2961 src
+= 2; /* Advance to end of escape */
2966 if (flags
& URL_UNESCAPE_INPLACE
|| needed
< *unescaped_len
)
2970 if (flags
& URL_UNESCAPE_INPLACE
|| needed
< *unescaped_len
)
2977 needed
++; /* add one for the '\0' */
2981 if (!(flags
& URL_UNESCAPE_INPLACE
))
2982 *unescaped_len
= needed
;
2985 TRACE("result %s\n", flags
& URL_UNESCAPE_INPLACE
? wine_dbgstr_w(url
) : wine_dbgstr_w(unescaped
));
2990 HRESULT WINAPI
PathCreateFromUrlA(const char *pszUrl
, char *pszPath
, DWORD
*pcchPath
, DWORD dwReserved
)
2992 WCHAR bufW
[MAX_PATH
];
2993 WCHAR
*pathW
= bufW
;
2994 UNICODE_STRING urlW
;
2996 DWORD lenW
= ARRAY_SIZE(bufW
), lenA
;
2998 if (!pszUrl
|| !pszPath
|| !pcchPath
|| !*pcchPath
)
2999 return E_INVALIDARG
;
3001 if(!RtlCreateUnicodeStringFromAsciiz(&urlW
, pszUrl
))
3002 return E_INVALIDARG
;
3003 if((ret
= PathCreateFromUrlW(urlW
.Buffer
, pathW
, &lenW
, dwReserved
)) == E_POINTER
) {
3004 pathW
= HeapAlloc(GetProcessHeap(), 0, lenW
* sizeof(WCHAR
));
3005 ret
= PathCreateFromUrlW(urlW
.Buffer
, pathW
, &lenW
, dwReserved
);
3008 RtlUnicodeToMultiByteSize(&lenA
, pathW
, lenW
* sizeof(WCHAR
));
3009 if(*pcchPath
> lenA
) {
3010 RtlUnicodeToMultiByteN(pszPath
, *pcchPath
- 1, &lenA
, pathW
, lenW
* sizeof(WCHAR
));
3014 *pcchPath
= lenA
+ 1;
3018 if(pathW
!= bufW
) HeapFree(GetProcessHeap(), 0, pathW
);
3019 RtlFreeUnicodeString(&urlW
);
3023 HRESULT WINAPI
PathCreateFromUrlW(const WCHAR
*url
, WCHAR
*path
, DWORD
*pcchPath
, DWORD dwReserved
)
3025 static const WCHAR file_colon
[] = { 'f','i','l','e',':',0 };
3026 static const WCHAR localhost
[] = { 'l','o','c','a','l','h','o','s','t',0 };
3027 DWORD nslashes
, unescape
, len
;
3032 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url
), path
, pcchPath
, dwReserved
);
3034 if (!url
|| !path
|| !pcchPath
|| !*pcchPath
)
3035 return E_INVALIDARG
;
3037 if (lstrlenW(url
) < 5)
3038 return E_INVALIDARG
;
3040 if (CompareStringW(LOCALE_INVARIANT
, NORM_IGNORECASE
, url
, 5, file_colon
, 5) != CSTR_EQUAL
)
3041 return E_INVALIDARG
;
3047 while (*src
== '/' || *src
== '\\')
3053 /* We need a temporary buffer so we can compute what size to ask for.
3054 * We know that the final string won't be longer than the current pszUrl
3055 * plus at most two backslashes. All the other transformations make it
3058 len
= 2 + lstrlenW(url
) + 1;
3059 if (*pcchPath
< len
)
3060 tpath
= heap_alloc(len
* sizeof(WCHAR
));
3070 /* 'file:' + escaped DOS path */
3073 /* 'file:/' + escaped DOS path */
3076 /* 'file:///' (implied localhost) + escaped DOS path */
3077 if (!iswalpha(*src
) || (src
[1] != ':' && src
[1] != '|'))
3081 if (lstrlenW(src
) >= 10 && CompareStringW(LOCALE_INVARIANT
, NORM_IGNORECASE
,
3082 src
, 9, localhost
, 9) == CSTR_EQUAL
&& (src
[9] == '/' || src
[9] == '\\'))
3084 /* 'file://localhost/' + escaped DOS path */
3087 else if (iswalpha(*src
) && (src
[1] == ':' || src
[1] == '|'))
3089 /* 'file://' + unescaped DOS path */
3094 /* 'file://hostname:port/path' (where path is escaped)
3095 * or 'file:' + escaped UNC path (\\server\share\path)
3096 * The second form is clearly specific to Windows and it might
3097 * even be doing a network lookup to try to figure it out.
3099 while (*src
&& *src
!= '/' && *src
!= '\\')
3102 StrCpyNW(dst
, url
, len
+ 1);
3104 if (*src
&& iswalpha(src
[1]) && (src
[2] == ':' || src
[2] == '|'))
3106 /* 'Forget' to add a trailing '/', just like Windows */
3112 /* 'file://' + unescaped UNC path (\\server\share\path) */
3114 if (iswalpha(*src
) && (src
[1] == ':' || src
[1] == '|'))
3118 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3122 /* Copy the remainder of the path */
3123 len
+= lstrlenW(src
);
3126 /* First do the Windows-specific path conversions */
3127 for (dst
= tpath
; *dst
; dst
++)
3128 if (*dst
== '/') *dst
= '\\';
3129 if (iswalpha(*tpath
) && tpath
[1] == '|')
3130 tpath
[1] = ':'; /* c| -> c: */
3132 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3135 hr
= UrlUnescapeW(tpath
, NULL
, &len
, URL_UNESCAPE_INPLACE
);
3138 /* When working in-place UrlUnescapeW() does not set len */
3139 len
= lstrlenW(tpath
);
3143 if (*pcchPath
< len
+ 1)
3146 *pcchPath
= len
+ 1;
3152 lstrcpyW(path
, tpath
);
3157 TRACE("Returning (%u) %s\n", *pcchPath
, wine_dbgstr_w(path
));
3161 HRESULT WINAPI
PathCreateFromUrlAlloc(const WCHAR
*url
, WCHAR
**path
, DWORD reserved
)
3163 WCHAR pathW
[MAX_PATH
];
3168 hr
= PathCreateFromUrlW(url
, pathW
, &size
, reserved
);
3171 /* Yes, this is supposed to crash if 'path' is NULL */
3172 *path
= StrDupW(pathW
);
3178 BOOL WINAPI
PathIsURLA(const char *path
)
3183 TRACE("%s\n", wine_dbgstr_a(path
));
3185 if (!path
|| !*path
)
3189 base
.cbSize
= sizeof(base
);
3190 hr
= ParseURLA(path
, &base
);
3191 return hr
== S_OK
&& (base
.nScheme
!= URL_SCHEME_INVALID
);
3194 BOOL WINAPI
PathIsURLW(const WCHAR
*path
)
3199 TRACE("%s\n", wine_dbgstr_w(path
));
3201 if (!path
|| !*path
)
3205 base
.cbSize
= sizeof(base
);
3206 hr
= ParseURLW(path
, &base
);
3207 return hr
== S_OK
&& (base
.nScheme
!= URL_SCHEME_INVALID
);
3210 #define WINE_URL_BASH_AS_SLASH 0x01
3211 #define WINE_URL_COLLAPSE_SLASHES 0x02
3212 #define WINE_URL_ESCAPE_SLASH 0x04
3213 #define WINE_URL_ESCAPE_HASH 0x08
3214 #define WINE_URL_ESCAPE_QUESTION 0x10
3215 #define WINE_URL_STOP_ON_HASH 0x20
3216 #define WINE_URL_STOP_ON_QUESTION 0x40
3218 static BOOL
url_needs_escape(WCHAR ch
, DWORD flags
, DWORD int_flags
)
3220 if (flags
& URL_ESCAPE_SPACES_ONLY
)
3223 if ((flags
& URL_ESCAPE_PERCENT
) && (ch
== '%'))
3226 if ((flags
& URL_ESCAPE_AS_UTF8
) && (ch
>= 0x80))
3229 if (ch
<= 31 || (ch
>= 127 && ch
<= 255) )
3251 return !!(int_flags
& WINE_URL_ESCAPE_SLASH
);
3253 return !!(int_flags
& WINE_URL_ESCAPE_QUESTION
);
3255 return !!(int_flags
& WINE_URL_ESCAPE_HASH
);
3261 HRESULT WINAPI
UrlEscapeA(const char *url
, char *escaped
, DWORD
*escaped_len
, DWORD flags
)
3263 WCHAR bufW
[INTERNET_MAX_URL_LENGTH
];
3264 WCHAR
*escapedW
= bufW
;
3265 UNICODE_STRING urlW
;
3267 DWORD lenW
= ARRAY_SIZE(bufW
), lenA
;
3269 if (!escaped
|| !escaped_len
|| !*escaped_len
)
3270 return E_INVALIDARG
;
3272 if (!RtlCreateUnicodeStringFromAsciiz(&urlW
, url
))
3273 return E_INVALIDARG
;
3275 if (flags
& URL_ESCAPE_AS_UTF8
)
3277 RtlFreeUnicodeString(&urlW
);
3281 if ((hr
= UrlEscapeW(urlW
.Buffer
, escapedW
, &lenW
, flags
)) == E_POINTER
)
3283 escapedW
= heap_alloc(lenW
* sizeof(WCHAR
));
3284 hr
= UrlEscapeW(urlW
.Buffer
, escapedW
, &lenW
, flags
);
3289 RtlUnicodeToMultiByteSize(&lenA
, escapedW
, lenW
* sizeof(WCHAR
));
3290 if (*escaped_len
> lenA
)
3292 RtlUnicodeToMultiByteN(escaped
, *escaped_len
- 1, &lenA
, escapedW
, lenW
* sizeof(WCHAR
));
3294 *escaped_len
= lenA
;
3298 *escaped_len
= lenA
+ 1;
3302 if (escapedW
!= bufW
)
3303 heap_free(escapedW
);
3304 RtlFreeUnicodeString(&urlW
);
3308 HRESULT WINAPI
UrlEscapeW(const WCHAR
*url
, WCHAR
*escaped
, DWORD
*escaped_len
, DWORD flags
)
3310 static const WCHAR localhost
[] = {'l','o','c','a','l','h','o','s','t',0};
3311 DWORD needed
= 0, slashes
= 0, int_flags
;
3312 WCHAR next
[12], *dst
, *dst_ptr
;
3313 BOOL stop_escaping
= FALSE
;
3314 PARSEDURLW parsed_url
;
3319 TRACE("%p, %s, %p, %p, %#x\n", url
, wine_dbgstr_w(url
), escaped
, escaped_len
, flags
);
3321 if (!url
|| !escaped_len
|| !escaped
|| *escaped_len
== 0)
3322 return E_INVALIDARG
;
3324 if (flags
& ~(URL_ESCAPE_SPACES_ONLY
| URL_ESCAPE_SEGMENT_ONLY
| URL_DONT_ESCAPE_EXTRA_INFO
|
3325 URL_ESCAPE_PERCENT
| URL_ESCAPE_AS_UTF8
))
3327 FIXME("Unimplemented flags: %08x\n", flags
);
3330 dst_ptr
= dst
= heap_alloc(*escaped_len
* sizeof(WCHAR
));
3332 return E_OUTOFMEMORY
;
3335 if (flags
& URL_ESCAPE_SPACES_ONLY
)
3336 /* if SPACES_ONLY specified, reset the other controls */
3337 flags
&= ~(URL_DONT_ESCAPE_EXTRA_INFO
| URL_ESCAPE_PERCENT
| URL_ESCAPE_SEGMENT_ONLY
);
3339 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3340 flags
|= URL_DONT_ESCAPE_EXTRA_INFO
;
3343 if (flags
& URL_ESCAPE_SEGMENT_ONLY
)
3344 int_flags
= WINE_URL_ESCAPE_QUESTION
| WINE_URL_ESCAPE_HASH
| WINE_URL_ESCAPE_SLASH
;
3347 parsed_url
.cbSize
= sizeof(parsed_url
);
3348 if (ParseURLW(url
, &parsed_url
) != S_OK
)
3349 parsed_url
.nScheme
= URL_SCHEME_INVALID
;
3351 TRACE("scheme = %d (%s)\n", parsed_url
.nScheme
, debugstr_wn(parsed_url
.pszProtocol
, parsed_url
.cchProtocol
));
3353 if (flags
& URL_DONT_ESCAPE_EXTRA_INFO
)
3354 int_flags
= WINE_URL_STOP_ON_HASH
| WINE_URL_STOP_ON_QUESTION
;
3356 switch(parsed_url
.nScheme
) {
3357 case URL_SCHEME_FILE
:
3358 int_flags
|= WINE_URL_BASH_AS_SLASH
| WINE_URL_COLLAPSE_SLASHES
| WINE_URL_ESCAPE_HASH
;
3359 int_flags
&= ~WINE_URL_STOP_ON_HASH
;
3362 case URL_SCHEME_HTTP
:
3363 case URL_SCHEME_HTTPS
:
3364 int_flags
|= WINE_URL_BASH_AS_SLASH
;
3365 if(parsed_url
.pszSuffix
[0] != '/' && parsed_url
.pszSuffix
[0] != '\\')
3366 int_flags
|= WINE_URL_ESCAPE_SLASH
;
3369 case URL_SCHEME_MAILTO
:
3370 int_flags
|= WINE_URL_ESCAPE_SLASH
| WINE_URL_ESCAPE_QUESTION
| WINE_URL_ESCAPE_HASH
;
3371 int_flags
&= ~(WINE_URL_STOP_ON_QUESTION
| WINE_URL_STOP_ON_HASH
);
3374 case URL_SCHEME_INVALID
:
3377 case URL_SCHEME_FTP
:
3379 if(parsed_url
.pszSuffix
[0] != '/')
3380 int_flags
|= WINE_URL_ESCAPE_SLASH
;
3385 for (src
= url
; *src
; )
3390 if ((int_flags
& WINE_URL_COLLAPSE_SLASHES
) && src
== url
+ parsed_url
.cchProtocol
+ 1)
3392 int localhost_len
= ARRAY_SIZE(localhost
) - 1;
3393 while (cur
== '/' || cur
== '\\')
3398 if (slashes
== 2 && !wcsnicmp(src
, localhost
, localhost_len
)) { /* file://localhost/ -> file:/// */
3399 if(*(src
+ localhost_len
) == '/' || *(src
+ localhost_len
) == '\\')
3400 src
+= localhost_len
+ 1;
3408 next
[0] = next
[1] = next
[2] = '/';
3415 next
[0] = next
[1] = '/';
3422 if (cur
== '#' && (int_flags
& WINE_URL_STOP_ON_HASH
))
3423 stop_escaping
= TRUE
;
3425 if (cur
== '?' && (int_flags
& WINE_URL_STOP_ON_QUESTION
))
3426 stop_escaping
= TRUE
;
3428 if (cur
== '\\' && (int_flags
& WINE_URL_BASH_AS_SLASH
) && !stop_escaping
) cur
= '/';
3430 if (url_needs_escape(cur
, flags
, int_flags
) && !stop_escaping
)
3432 if (flags
& URL_ESCAPE_AS_UTF8
)
3436 if ((cur
>= 0xd800 && cur
<= 0xdfff) && (src
[1] >= 0xdc00 && src
[1] <= 0xdfff))
3438 len
= WideCharToMultiByte(CP_UTF8
, WC_ERR_INVALID_CHARS
, src
, 2, utf
, sizeof(utf
), NULL
, NULL
);
3442 len
= WideCharToMultiByte(CP_UTF8
, WC_ERR_INVALID_CHARS
, &cur
, 1, utf
, sizeof(utf
), NULL
, NULL
);
3452 for (i
= 0; i
< len
; ++i
)
3455 next
[i
*3+1] = hexDigits
[(utf
[i
] >> 4) & 0xf];
3456 next
[i
*3+2] = hexDigits
[utf
[i
] & 0xf];
3463 next
[1] = hexDigits
[(cur
>> 4) & 0xf];
3464 next
[2] = hexDigits
[cur
& 0xf];
3476 if (needed
+ len
<= *escaped_len
)
3478 memcpy(dst
, next
, len
*sizeof(WCHAR
));
3484 if (needed
< *escaped_len
)
3487 memcpy(escaped
, dst_ptr
, (needed
+1)*sizeof(WCHAR
));
3492 needed
++; /* add one for the '\0' */
3495 *escaped_len
= needed
;
3501 HRESULT WINAPI
UrlCanonicalizeA(const char *src_url
, char *canonicalized
, DWORD
*canonicalized_len
, DWORD flags
)
3503 LPWSTR url
, canonical
;
3506 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(src_url
), canonicalized
, canonicalized_len
, flags
);
3508 if (!src_url
|| !canonicalized
|| !canonicalized_len
|| !*canonicalized_len
)
3509 return E_INVALIDARG
;
3511 url
= heap_strdupAtoW(src_url
);
3512 canonical
= heap_alloc(*canonicalized_len
* sizeof(WCHAR
));
3513 if (!url
|| !canonical
)
3516 heap_free(canonical
);
3517 return E_OUTOFMEMORY
;
3520 hr
= UrlCanonicalizeW(url
, canonical
, canonicalized_len
, flags
);
3522 WideCharToMultiByte(CP_ACP
, 0, canonical
, -1, canonicalized
, *canonicalized_len
+ 1, NULL
, NULL
);
3525 heap_free(canonical
);
3529 HRESULT WINAPI
UrlCanonicalizeW(const WCHAR
*src_url
, WCHAR
*canonicalized
, DWORD
*canonicalized_len
, DWORD flags
)
3531 static const WCHAR wszFile
[] = {'f','i','l','e',':'};
3532 static const WCHAR wszRes
[] = {'r','e','s',':'};
3533 static const WCHAR wszHttp
[] = {'h','t','t','p',':'};
3534 static const WCHAR wszLocalhost
[] = {'l','o','c','a','l','h','o','s','t'};
3535 static const WCHAR wszFilePrefix
[] = {'f','i','l','e',':','/','/','/'};
3536 WCHAR
*url_copy
, *url
, *wk2
, *mp
, *mp2
;
3537 DWORD nByteLen
, nLen
, nWkLen
;
3538 const WCHAR
*wk1
, *root
;
3545 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(src_url
), canonicalized
, canonicalized_len
, flags
);
3547 if (!src_url
|| !canonicalized
|| !canonicalized
|| !*canonicalized_len
)
3548 return E_INVALIDARG
;
3556 /* Remove '\t' characters from URL */
3557 nByteLen
= (lstrlenW(src_url
) + 1) * sizeof(WCHAR
); /* length in bytes */
3558 url
= HeapAlloc(GetProcessHeap(), 0, nByteLen
);
3560 return E_OUTOFMEMORY
;
3571 /* Allocate memory for simplified URL (before escaping) */
3572 nByteLen
= (wk2
-url
)*sizeof(WCHAR
);
3573 url_copy
= heap_alloc(nByteLen
+ sizeof(wszFilePrefix
) + sizeof(WCHAR
));
3577 return E_OUTOFMEMORY
;
3580 is_file_url
= !wcsncmp(wszFile
, url
, ARRAY_SIZE(wszFile
));
3582 if ((nByteLen
>= sizeof(wszHttp
) && !memcmp(wszHttp
, url
, sizeof(wszHttp
))) || is_file_url
)
3585 if ((flags
& (URL_FILE_USE_PATHURL
| URL_WININET_COMPATIBILITY
)) && is_file_url
)
3588 if (nByteLen
>= sizeof(wszRes
) && !memcmp(wszRes
, url
, sizeof(wszRes
)))
3590 flags
&= ~URL_FILE_USE_PATHURL
;
3597 * 1 have 2[+] alnum 2,3
3598 * 2 have scheme (found :) 4,6,3
3599 * 3 failed (no location)
3601 * 5 have 1[+] alnum 6,3
3602 * 6 have location (found /) save root location
3612 memcpy(wk2
, wszFilePrefix
, sizeof(wszFilePrefix
));
3613 wk2
+= ARRAY_SIZE(wszFilePrefix
);
3614 if (flags
& (URL_FILE_USE_PATHURL
| URL_WININET_COMPATIBILITY
))
3620 flags
|= URL_ESCAPE_UNSAFE
;
3624 else if (url
[0] == '/')
3635 if (!iswalnum(*wk1
)) {state
= 3; break;}
3637 if (!iswalnum(*wk1
)) {state
= 3; break;}
3643 if (*wk1
++ == ':') state
= 2;
3647 if (*wk1
!= '/') {state
= 6; break;}
3649 if ((flags
& URL_FILE_USE_PATHURL
) && nByteLen
>= sizeof(wszLocalhost
) && is_file_url
3650 && !memcmp(wszLocalhost
, wk1
, sizeof(wszLocalhost
)))
3652 wk1
+= ARRAY_SIZE(wszLocalhost
);
3653 while (*wk1
== '\\' && (flags
& URL_FILE_USE_PATHURL
))
3657 if (*wk1
== '/' && (flags
& URL_FILE_USE_PATHURL
))
3659 else if (is_file_url
)
3661 const WCHAR
*body
= wk1
;
3663 while (*body
== '/')
3666 if (iswalnum(*body
) && *(body
+1) == ':')
3668 if (!(flags
& (URL_WININET_COMPATIBILITY
| URL_FILE_USE_PATHURL
)))
3678 if (flags
& URL_WININET_COMPATIBILITY
)
3680 if (*wk1
== '/' && *(wk1
+ 1) != '/')
3692 if (*wk1
== '/' && *(wk1
+1) != '/')
3706 nWkLen
= lstrlenW(wk1
);
3707 memcpy(wk2
, wk1
, (nWkLen
+ 1) * sizeof(WCHAR
));
3716 if (*mp
== '/' || *mp
== '\\')
3723 if (!iswalnum(*wk1
) && (*wk1
!= '-') && (*wk1
!= '.') && (*wk1
!= ':'))
3728 while (iswalnum(*wk1
) || (*wk1
== '-') || (*wk1
== '.') || (*wk1
== ':'))
3740 if (*wk1
!= '/' && *wk1
!= '\\')
3745 while (*wk1
== '/' || *wk1
== '\\')
3756 if (flags
& URL_DONT_SIMPLIFY
)
3762 /* Now at root location, cannot back up any more. */
3763 /* "root" will point at the '/' */
3768 mp
= wcschr(wk1
, '/');
3769 mp2
= wcschr(wk1
, '\\');
3770 if (mp2
&& (!mp
|| mp2
< mp
))
3774 nWkLen
= lstrlenW(wk1
);
3775 memcpy(wk2
, wk1
, (nWkLen
+ 1) * sizeof(WCHAR
));
3783 memcpy(wk2
, wk1
, nLen
* sizeof(WCHAR
));
3795 TRACE("found '/.'\n");
3796 if (wk1
[1] == '/' || wk1
[1] == '\\')
3798 /* case of /./ -> skip the ./ */
3801 else if (wk1
[1] == '.' && (wk1
[2] == '/' || wk1
[2] == '\\' || wk1
[2] == '?'
3802 || wk1
[2] == '#' || !wk1
[2]))
3804 /* case /../ -> need to backup wk2 */
3805 TRACE("found '/../'\n");
3806 *(wk2
-1) = '\0'; /* set end of string */
3807 mp
= wcsrchr(root
, '/');
3808 mp2
= wcsrchr(root
, '\\');
3809 if (mp2
&& (!mp
|| mp2
< mp
))
3811 if (mp
&& (mp
>= root
))
3813 /* found valid backup point */
3815 if(wk1
[2] != '/' && wk1
[2] != '\\')
3822 /* did not find point, restore '/' */
3834 FIXME("how did we get here - state=%d\n", state
);
3835 heap_free(url_copy
);
3837 return E_INVALIDARG
;
3840 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url
), wine_dbgstr_w(url_copy
));
3842 nLen
= lstrlenW(url_copy
);
3843 while ((nLen
> 0) && ((url_copy
[nLen
-1] <= ' ')))
3846 if ((flags
& URL_UNESCAPE
) || ((flags
& URL_FILE_USE_PATHURL
) && nByteLen
>= sizeof(wszFile
)
3847 && !memcmp(wszFile
, url
, sizeof(wszFile
))))
3849 UrlUnescapeW(url_copy
, NULL
, &nLen
, URL_UNESCAPE_INPLACE
);
3852 escape_flags
= flags
& (URL_ESCAPE_UNSAFE
| URL_ESCAPE_SPACES_ONLY
| URL_ESCAPE_PERCENT
|
3853 URL_DONT_ESCAPE_EXTRA_INFO
| URL_ESCAPE_SEGMENT_ONLY
);
3857 escape_flags
&= ~URL_ESCAPE_UNSAFE
;
3858 hr
= UrlEscapeW(url_copy
, canonicalized
, canonicalized_len
, escape_flags
);
3862 /* No escaping needed, just copy the string */
3863 nLen
= lstrlenW(url_copy
);
3864 if (nLen
< *canonicalized_len
)
3865 memcpy(canonicalized
, url_copy
, (nLen
+ 1)*sizeof(WCHAR
));
3871 *canonicalized_len
= nLen
;
3874 heap_free(url_copy
);
3878 TRACE("result %s\n", wine_dbgstr_w(canonicalized
));
3883 HRESULT WINAPI
UrlApplySchemeA(const char *url
, char *out
, DWORD
*out_len
, DWORD flags
)
3889 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_a(url
), out
, out_len
, out_len
? *out_len
: 0, flags
);
3891 if (!url
|| !out
|| !out_len
)
3892 return E_INVALIDARG
;
3894 inW
= heap_alloc(2 * INTERNET_MAX_URL_LENGTH
* sizeof(WCHAR
));
3895 outW
= inW
+ INTERNET_MAX_URL_LENGTH
;
3897 MultiByteToWideChar(CP_ACP
, 0, url
, -1, inW
, INTERNET_MAX_URL_LENGTH
);
3898 len
= INTERNET_MAX_URL_LENGTH
;
3900 hr
= UrlApplySchemeW(inW
, outW
, &len
, flags
);
3907 len
= WideCharToMultiByte(CP_ACP
, 0, outW
, -1, NULL
, 0, NULL
, NULL
);
3914 WideCharToMultiByte(CP_ACP
, 0, outW
, -1, out
, *out_len
, NULL
, NULL
);
3923 static HRESULT
url_guess_scheme(const WCHAR
*url
, WCHAR
*out
, DWORD
*out_len
)
3925 WCHAR reg_path
[MAX_PATH
], value
[MAX_PATH
], data
[MAX_PATH
];
3926 DWORD value_len
, data_len
, dwType
, i
;
3932 MultiByteToWideChar(CP_ACP
, 0,
3933 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path
, MAX_PATH
);
3934 RegOpenKeyExW(HKEY_LOCAL_MACHINE
, reg_path
, 0, 1, &newkey
);
3936 while (value_len
= data_len
= MAX_PATH
,
3937 RegEnumValueW(newkey
, index
, value
, &value_len
, 0, &dwType
, (LPVOID
)data
, &data_len
) == 0)
3939 TRACE("guess %d %s is %s\n", index
, wine_dbgstr_w(value
), wine_dbgstr_w(data
));
3942 for (i
= 0; i
< value_len
; ++i
)
3946 /* remember that TRUE is not-equal */
3947 j
= ChrCmpIW(Wxx
, Wyy
);
3950 if ((i
== value_len
) && !j
)
3952 if (lstrlenW(data
) + lstrlenW(url
) + 1 > *out_len
)
3954 *out_len
= lstrlenW(data
) + lstrlenW(url
) + 1;
3955 RegCloseKey(newkey
);
3958 lstrcpyW(out
, data
);
3960 *out_len
= lstrlenW(out
);
3961 TRACE("matched and set to %s\n", wine_dbgstr_w(out
));
3962 RegCloseKey(newkey
);
3967 RegCloseKey(newkey
);
3971 static HRESULT
url_create_from_path(const WCHAR
*path
, WCHAR
*url
, DWORD
*url_len
)
3973 static const WCHAR file_colonW
[] = {'f','i','l','e',':',0};
3974 static const WCHAR three_slashesW
[] = {'/','/','/',0};
3975 PARSEDURLW parsed_url
;
3980 parsed_url
.cbSize
= sizeof(parsed_url
);
3981 if (ParseURLW(path
, &parsed_url
) == S_OK
)
3983 if (parsed_url
.nScheme
!= URL_SCHEME_INVALID
&& parsed_url
.cchProtocol
> 1)
3985 needed
= lstrlenW(path
);
3986 if (needed
>= *url_len
)
3988 *url_len
= needed
+ 1;
3999 new_url
= heap_alloc((lstrlenW(path
) + 9) * sizeof(WCHAR
)); /* "file:///" + path length + 1 */
4000 lstrcpyW(new_url
, file_colonW
);
4001 if (iswalpha(path
[0]) && path
[1] == ':')
4002 lstrcatW(new_url
, three_slashesW
);
4003 lstrcatW(new_url
, path
);
4004 hr
= UrlEscapeW(new_url
, url
, url_len
, URL_ESCAPE_PERCENT
);
4009 static HRESULT
url_apply_default_scheme(const WCHAR
*url
, WCHAR
*out
, DWORD
*length
)
4011 static const WCHAR prefix_keyW
[] =
4012 {'S','o','f','t','w','a','r','e',
4013 '\\','M','i','c','r','o','s','o','f','t',
4014 '\\','W','i','n','d','o','w','s',
4015 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
4017 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
4018 DWORD data_len
, dwType
;
4019 WCHAR data
[MAX_PATH
];
4022 /* get and prepend default */
4023 RegOpenKeyExW(HKEY_LOCAL_MACHINE
, prefix_keyW
, 0, 1, &newkey
);
4024 data_len
= sizeof(data
);
4025 RegQueryValueExW(newkey
, NULL
, 0, &dwType
, (BYTE
*)data
, &data_len
);
4026 RegCloseKey(newkey
);
4027 if (lstrlenW(data
) + lstrlenW(url
) + 1 > *length
)
4029 *length
= lstrlenW(data
) + lstrlenW(url
) + 1;
4032 lstrcpyW(out
, data
);
4034 *length
= lstrlenW(out
);
4035 TRACE("used default %s\n", wine_dbgstr_w(out
));
4039 HRESULT WINAPI
UrlApplySchemeW(const WCHAR
*url
, WCHAR
*out
, DWORD
*length
, DWORD flags
)
4041 PARSEDURLW in_scheme
;
4045 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_w(url
), out
, length
, length
? *length
: 0, flags
);
4047 if (!url
|| !out
|| !length
)
4048 return E_INVALIDARG
;
4050 if (flags
& URL_APPLY_GUESSFILE
)
4052 if (*length
> 1 && ':' == url
[1])
4055 hr
= url_create_from_path(url
, out
, &res1
);
4056 if (hr
== S_OK
|| hr
== E_POINTER
)
4061 else if (hr
== S_FALSE
)
4068 in_scheme
.cbSize
= sizeof(in_scheme
);
4069 /* See if the base has a scheme */
4070 res1
= ParseURLW(url
, &in_scheme
);
4073 /* no scheme in input, need to see if we need to guess */
4074 if (flags
& URL_APPLY_GUESSSCHEME
)
4076 if ((hr
= url_guess_scheme(url
, out
, length
)) != E_FAIL
)
4081 /* If we are here, then either invalid scheme,
4082 * or no scheme and can't/failed guess.
4084 if ((((res1
== 0) && (flags
& URL_APPLY_FORCEAPPLY
)) || ((res1
!= 0)) ) && (flags
& URL_APPLY_DEFAULT
))
4085 return url_apply_default_scheme(url
, out
, length
);
4090 INT WINAPI
UrlCompareA(const char *url1
, const char *url2
, BOOL ignore_slash
)
4092 INT ret
, len
, len1
, len2
;
4095 return strcmp(url1
, url2
);
4096 len1
= strlen(url1
);
4097 if (url1
[len1
-1] == '/') len1
--;
4098 len2
= strlen(url2
);
4099 if (url2
[len2
-1] == '/') len2
--;
4101 return strncmp(url1
, url2
, len1
);
4102 len
= min(len1
, len2
);
4103 ret
= strncmp(url1
, url2
, len
);
4104 if (ret
) return ret
;
4105 if (len1
> len2
) return 1;
4109 INT WINAPI
UrlCompareW(const WCHAR
*url1
, const WCHAR
*url2
, BOOL ignore_slash
)
4111 size_t len
, len1
, len2
;
4115 return lstrcmpW(url1
, url2
);
4116 len1
= lstrlenW(url1
);
4117 if (url1
[len1
-1] == '/') len1
--;
4118 len2
= lstrlenW(url2
);
4119 if (url2
[len2
-1] == '/') len2
--;
4121 return wcsncmp(url1
, url2
, len1
);
4122 len
= min(len1
, len2
);
4123 ret
= wcsncmp(url1
, url2
, len
);
4124 if (ret
) return ret
;
4125 if (len1
> len2
) return 1;
4129 HRESULT WINAPI
UrlFixupW(const WCHAR
*url
, WCHAR
*translatedUrl
, DWORD maxChars
)
4133 FIXME("%s, %p, %d stub\n", wine_dbgstr_w(url
), translatedUrl
, maxChars
);
4138 srcLen
= lstrlenW(url
) + 1;
4140 /* For now just copy the URL directly */
4141 lstrcpynW(translatedUrl
, url
, (maxChars
< srcLen
) ? maxChars
: srcLen
);
4146 const char * WINAPI
UrlGetLocationA(const char *url
)
4150 base
.cbSize
= sizeof(base
);
4151 if (ParseURLA(url
, &base
) != S_OK
) return NULL
; /* invalid scheme */
4153 /* if scheme is file: then never return pointer */
4154 if (!strncmp(base
.pszProtocol
, "file", min(4, base
.cchProtocol
)))
4157 /* Look for '#' and return its addr */
4158 return strchr(base
.pszSuffix
, '#');
4161 const WCHAR
* WINAPI
UrlGetLocationW(const WCHAR
*url
)
4163 static const WCHAR fileW
[] = {'f','i','l','e','\0'};
4166 base
.cbSize
= sizeof(base
);
4167 if (ParseURLW(url
, &base
) != S_OK
) return NULL
; /* invalid scheme */
4169 /* if scheme is file: then never return pointer */
4170 if (!wcsncmp(base
.pszProtocol
, fileW
, min(4, base
.cchProtocol
)))
4173 /* Look for '#' and return its addr */
4174 return wcschr(base
.pszSuffix
, '#');
4177 HRESULT WINAPI
UrlGetPartA(const char *url
, char *out
, DWORD
*out_len
, DWORD part
, DWORD flags
)
4183 if (!url
|| !out
|| !out_len
|| !*out_len
)
4184 return E_INVALIDARG
;
4186 inW
= heap_alloc(2 * INTERNET_MAX_URL_LENGTH
* sizeof(WCHAR
));
4187 outW
= inW
+ INTERNET_MAX_URL_LENGTH
;
4189 MultiByteToWideChar(CP_ACP
, 0, url
, -1, inW
, INTERNET_MAX_URL_LENGTH
);
4191 len
= INTERNET_MAX_URL_LENGTH
;
4192 hr
= UrlGetPartW(inW
, outW
, &len
, part
, flags
);
4199 len2
= WideCharToMultiByte(CP_ACP
, 0, outW
, len
, NULL
, 0, NULL
, NULL
);
4200 if (len2
> *out_len
)
4202 *out_len
= len2
+ 1;
4206 len2
= WideCharToMultiByte(CP_ACP
, 0, outW
, len
+ 1, out
, *out_len
, NULL
, NULL
);
4207 *out_len
= len2
- 1;
4212 static const WCHAR
* scan_url(const WCHAR
*start
, DWORD
*size
, enum url_scan_type type
)
4214 static DWORD alwayszero
= 0;
4224 if ((iswlower(*start
) && iswalpha(*start
)) ||
4225 iswdigit(*start
) || *start
== '+' || *start
== '-' || *start
== '.')
4240 if (iswalpha(*start
) ||
4242 /* user/password only characters */
4247 /* *extra* characters */
4254 /* *safe* characters */
4265 else if (*start
== '%')
4267 if (iswxdigit(*(start
+ 1)) && iswxdigit(*(start
+ 2)))
4282 if (iswdigit(*start
))
4295 if (iswalnum(*start
) || *start
== '-' || *start
== '.' || *start
== ' ' || *start
== '*')
4306 FIXME("unknown type %d\n", type
);
4307 return (LPWSTR
)&alwayszero
;
4313 static LONG
parse_url(const WCHAR
*url
, struct parsed_url
*pl
)
4317 memset(pl
, 0, sizeof(*pl
));
4319 work
= scan_url(pl
->scheme
, &pl
->scheme_len
, SCHEME
);
4320 if (!*work
|| (*work
!= ':')) goto ErrorExit
;
4322 if ((*work
!= '/') || (*(work
+1) != '/')) goto SuccessExit
;
4324 pl
->username
= work
+ 2;
4325 work
= scan_url(pl
->username
, &pl
->username_len
, USERPASS
);
4328 /* parse password */
4330 pl
->password
= work
;
4331 work
= scan_url(pl
->password
, &pl
->password_len
, USERPASS
);
4334 /* what we just parsed must be the hostname and port
4335 * so reset pointers and clear then let it parse */
4336 pl
->username_len
= pl
->password_len
= 0;
4337 work
= pl
->username
- 1;
4338 pl
->username
= pl
->password
= 0;
4341 else if (*work
== '@')
4344 pl
->password_len
= 0;
4347 else if (!*work
|| *work
== '/' || *work
== '.')
4349 /* what was parsed was hostname, so reset pointers and let it parse */
4350 pl
->username_len
= pl
->password_len
= 0;
4351 work
= pl
->username
- 1;
4352 pl
->username
= pl
->password
= 0;
4354 else goto ErrorExit
;
4356 /* now start parsing hostname or hostnumber */
4358 pl
->hostname
= work
;
4359 work
= scan_url(pl
->hostname
, &pl
->hostname_len
, HOST
);
4365 work
= scan_url(pl
->port
, &pl
->port_len
, PORT
);
4369 /* see if query string */
4370 pl
->query
= wcschr(work
, '?');
4371 if (pl
->query
) pl
->query_len
= lstrlenW(pl
->query
);
4374 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
4375 pl
->scheme
, pl
->scheme_len
, pl
->username
, pl
->username_len
, pl
->password
, pl
->password_len
, pl
->hostname
,
4376 pl
->hostname_len
, pl
->port
, pl
->port_len
, pl
->query
, pl
->query_len
);
4381 FIXME("failed to parse %s\n", debugstr_w(url
));
4382 return E_INVALIDARG
;
4385 HRESULT WINAPI
UrlGetPartW(const WCHAR
*url
, WCHAR
*out
, DWORD
*out_len
, DWORD part
, DWORD flags
)
4387 DWORD scheme
, size
, schsize
;
4388 LPCWSTR addr
, schaddr
;
4389 struct parsed_url pl
;
4392 TRACE("%s, %p, %p(%d), %#x, %#x\n", wine_dbgstr_w(url
), out
, out_len
, *out_len
, part
, flags
);
4394 if (!url
|| !out
|| !out_len
|| !*out_len
)
4395 return E_INVALIDARG
;
4399 addr
= wcschr(url
, ':');
4401 scheme
= URL_SCHEME_UNKNOWN
;
4403 scheme
= get_scheme_code(url
, addr
- url
);
4405 hr
= parse_url(url
, &pl
);
4409 case URL_PART_SCHEME
:
4416 size
= pl
.scheme_len
;
4419 case URL_PART_HOSTNAME
:
4422 case URL_SCHEME_FTP
:
4423 case URL_SCHEME_HTTP
:
4424 case URL_SCHEME_GOPHER
:
4425 case URL_SCHEME_TELNET
:
4426 case URL_SCHEME_FILE
:
4427 case URL_SCHEME_HTTPS
:
4434 if (scheme
== URL_SCHEME_FILE
&& (!pl
.hostname_len
|| (pl
.hostname_len
== 1 && *(pl
.hostname
+ 1) == ':')))
4440 if (!pl
.hostname_len
)
4446 size
= pl
.hostname_len
;
4449 case URL_PART_USERNAME
:
4450 if (!pl
.username_len
)
4456 size
= pl
.username_len
;
4459 case URL_PART_PASSWORD
:
4460 if (!pl
.password_len
)
4466 size
= pl
.password_len
;
4479 case URL_PART_QUERY
:
4486 size
= pl
.query_len
;
4491 return E_INVALIDARG
;
4494 if (flags
== URL_PARTFLAG_KEEPSCHEME
)
4496 if (!pl
.scheme
|| !pl
.scheme_len
)
4501 schaddr
= pl
.scheme
;
4502 schsize
= pl
.scheme_len
;
4503 if (*out_len
< schsize
+ size
+ 2)
4505 *out_len
= schsize
+ size
+ 2;
4508 memcpy(out
, schaddr
, schsize
*sizeof(WCHAR
));
4510 memcpy(out
+ schsize
+1, addr
, size
*sizeof(WCHAR
));
4511 out
[schsize
+1+size
] = 0;
4512 *out_len
= schsize
+ 1 + size
;
4516 if (*out_len
< size
+ 1)
4518 *out_len
= size
+ 1;
4521 memcpy(out
, addr
, size
*sizeof(WCHAR
));
4525 TRACE("len=%d %s\n", *out_len
, wine_dbgstr_w(out
));
4530 BOOL WINAPI
UrlIsA(const char *url
, URLIS Urlis
)
4535 TRACE("%s, %d\n", debugstr_a(url
), Urlis
);
4543 base
.cbSize
= sizeof(base
);
4544 if (ParseURLA(url
, &base
) != S_OK
) return FALSE
; /* invalid scheme */
4545 switch (base
.nScheme
)
4547 case URL_SCHEME_MAILTO
:
4548 case URL_SCHEME_SHELL
:
4549 case URL_SCHEME_JAVASCRIPT
:
4550 case URL_SCHEME_VBSCRIPT
:
4551 case URL_SCHEME_ABOUT
:
4557 return (CompareStringA(LOCALE_INVARIANT
, NORM_IGNORECASE
, url
, 5, "file:", 5) == CSTR_EQUAL
);
4559 case URLIS_DIRECTORY
:
4560 last
= url
+ strlen(url
) - 1;
4561 return (last
>= url
&& (*last
== '/' || *last
== '\\' ));
4564 return PathIsURLA(url
);
4566 case URLIS_NOHISTORY
:
4567 case URLIS_APPLIABLE
:
4568 case URLIS_HASQUERY
:
4570 FIXME("(%s %d): stub\n", debugstr_a(url
), Urlis
);
4576 BOOL WINAPI
UrlIsW(const WCHAR
*url
, URLIS Urlis
)
4578 static const WCHAR file_colon
[] = {'f','i','l','e',':',0};
4582 TRACE("%s, %d\n", debugstr_w(url
), Urlis
);
4590 base
.cbSize
= sizeof(base
);
4591 if (ParseURLW(url
, &base
) != S_OK
) return FALSE
; /* invalid scheme */
4592 switch (base
.nScheme
)
4594 case URL_SCHEME_MAILTO
:
4595 case URL_SCHEME_SHELL
:
4596 case URL_SCHEME_JAVASCRIPT
:
4597 case URL_SCHEME_VBSCRIPT
:
4598 case URL_SCHEME_ABOUT
:
4604 return (CompareStringW(LOCALE_INVARIANT
, NORM_IGNORECASE
, url
, 5, file_colon
, 5) == CSTR_EQUAL
);
4606 case URLIS_DIRECTORY
:
4607 last
= url
+ lstrlenW(url
) - 1;
4608 return (last
>= url
&& (*last
== '/' || *last
== '\\'));
4611 return PathIsURLW(url
);
4613 case URLIS_NOHISTORY
:
4614 case URLIS_APPLIABLE
:
4615 case URLIS_HASQUERY
:
4617 FIXME("(%s %d): stub\n", debugstr_w(url
), Urlis
);
4623 BOOL WINAPI
UrlIsOpaqueA(const char *url
)
4625 return UrlIsA(url
, URLIS_OPAQUE
);
4628 BOOL WINAPI
UrlIsOpaqueW(const WCHAR
*url
)
4630 return UrlIsW(url
, URLIS_OPAQUE
);
4633 BOOL WINAPI
UrlIsNoHistoryA(const char *url
)
4635 return UrlIsA(url
, URLIS_NOHISTORY
);
4638 BOOL WINAPI
UrlIsNoHistoryW(const WCHAR
*url
)
4640 return UrlIsW(url
, URLIS_NOHISTORY
);
4643 HRESULT WINAPI
UrlCreateFromPathA(const char *path
, char *url
, DWORD
*url_len
, DWORD reserved
)
4645 WCHAR bufW
[INTERNET_MAX_URL_LENGTH
];
4646 DWORD lenW
= ARRAY_SIZE(bufW
), lenA
;
4647 UNICODE_STRING pathW
;
4651 if (!RtlCreateUnicodeStringFromAsciiz(&pathW
, path
))
4652 return E_INVALIDARG
;
4654 if ((hr
= UrlCreateFromPathW(pathW
.Buffer
, urlW
, &lenW
, reserved
)) == E_POINTER
)
4656 urlW
= heap_alloc(lenW
* sizeof(WCHAR
));
4657 hr
= UrlCreateFromPathW(pathW
.Buffer
, urlW
, &lenW
, reserved
);
4662 RtlUnicodeToMultiByteSize(&lenA
, urlW
, lenW
* sizeof(WCHAR
));
4663 if (*url_len
> lenA
)
4665 RtlUnicodeToMultiByteN(url
, *url_len
- 1, &lenA
, urlW
, lenW
* sizeof(WCHAR
));
4671 *url_len
= lenA
+ 1;
4677 RtlFreeUnicodeString(&pathW
);
4681 HRESULT WINAPI
UrlCreateFromPathW(const WCHAR
*path
, WCHAR
*url
, DWORD
*url_len
, DWORD reserved
)
4685 TRACE("%s, %p, %p, %#x\n", debugstr_w(path
), url
, url_len
, reserved
);
4687 if (reserved
|| !url
|| !url_len
)
4688 return E_INVALIDARG
;
4690 hr
= url_create_from_path(path
, url
, url_len
);
4692 lstrcpyW(url
, path
);
4697 HRESULT WINAPI
UrlCombineA(const char *base
, const char *relative
, char *combined
, DWORD
*combined_len
, DWORD flags
)
4699 WCHAR
*baseW
, *relativeW
, *combinedW
;
4703 TRACE("%s, %s, %d, %#x\n", debugstr_a(base
), debugstr_a(relative
), combined_len
? *combined_len
: 0, flags
);
4705 if (!base
|| !relative
|| !combined_len
)
4706 return E_INVALIDARG
;
4708 baseW
= heap_alloc(3 * INTERNET_MAX_URL_LENGTH
* sizeof(WCHAR
));
4709 relativeW
= baseW
+ INTERNET_MAX_URL_LENGTH
;
4710 combinedW
= relativeW
+ INTERNET_MAX_URL_LENGTH
;
4712 MultiByteToWideChar(CP_ACP
, 0, base
, -1, baseW
, INTERNET_MAX_URL_LENGTH
);
4713 MultiByteToWideChar(CP_ACP
, 0, relative
, -1, relativeW
, INTERNET_MAX_URL_LENGTH
);
4714 len
= *combined_len
;
4716 hr
= UrlCombineW(baseW
, relativeW
, combined
? combinedW
: NULL
, &len
, flags
);
4719 *combined_len
= len
;
4724 len2
= WideCharToMultiByte(CP_ACP
, 0, combinedW
, len
, NULL
, 0, NULL
, NULL
);
4725 if (len2
> *combined_len
)
4727 *combined_len
= len2
;
4731 WideCharToMultiByte(CP_ACP
, 0, combinedW
, len
+1, combined
, *combined_len
+ 1, NULL
, NULL
);
4732 *combined_len
= len2
;
4737 HRESULT WINAPI
UrlCombineW(const WCHAR
*baseW
, const WCHAR
*relativeW
, WCHAR
*combined
, DWORD
*combined_len
, DWORD flags
)
4739 static const WCHAR myfilestr
[] = {'f','i','l','e',':','/','/','/','\0'};
4740 static const WCHAR fragquerystr
[] = {'#','?',0};
4741 DWORD i
, len
, process_case
= 0, myflags
, sizeloc
= 0;
4742 LPWSTR work
, preliminary
, mbase
, mrelative
;
4743 PARSEDURLW base
, relative
;
4746 TRACE("%s, %s, %d, %#x\n", debugstr_w(baseW
), debugstr_w(relativeW
), combined_len
? *combined_len
: 0, flags
);
4748 if (!baseW
|| !relativeW
|| !combined_len
)
4749 return E_INVALIDARG
;
4751 base
.cbSize
= sizeof(base
);
4752 relative
.cbSize
= sizeof(relative
);
4754 /* Get space for duplicates of the input and the output */
4755 preliminary
= heap_alloc(3 * INTERNET_MAX_URL_LENGTH
* sizeof(WCHAR
));
4756 mbase
= preliminary
+ INTERNET_MAX_URL_LENGTH
;
4757 mrelative
= mbase
+ INTERNET_MAX_URL_LENGTH
;
4758 *preliminary
= '\0';
4760 /* Canonicalize the base input prior to looking for the scheme */
4761 myflags
= flags
& (URL_DONT_SIMPLIFY
| URL_UNESCAPE
);
4762 len
= INTERNET_MAX_URL_LENGTH
;
4763 UrlCanonicalizeW(baseW
, mbase
, &len
, myflags
);
4765 /* Canonicalize the relative input prior to looking for the scheme */
4766 len
= INTERNET_MAX_URL_LENGTH
;
4767 UrlCanonicalizeW(relativeW
, mrelative
, &len
, myflags
);
4769 /* See if the base has a scheme */
4770 if (ParseURLW(mbase
, &base
) != S_OK
)
4772 /* If base has no scheme return relative. */
4773 TRACE("no scheme detected in Base\n");
4778 BOOL manual_search
= FALSE
;
4780 work
= (LPWSTR
)base
.pszProtocol
;
4781 for (i
= 0; i
< base
.cchProtocol
; ++i
)
4782 work
[i
] = towlower(work
[i
]);
4784 /* mk is a special case */
4785 if (base
.nScheme
== URL_SCHEME_MK
)
4787 static const WCHAR wsz
[] = {':',':',0};
4788 WCHAR
*ptr
= wcsstr(base
.pszSuffix
, wsz
);
4794 delta
= ptr
-base
.pszSuffix
;
4795 base
.cchProtocol
+= delta
;
4796 base
.pszSuffix
+= delta
;
4797 base
.cchSuffix
-= delta
;
4802 /* get size of location field (if it exists) */
4803 work
= (LPWSTR
)base
.pszSuffix
;
4809 /* At this point have start of location and
4810 * it ends at next '/' or end of string.
4812 while (*work
&& (*work
!= '/')) work
++;
4813 sizeloc
= (DWORD
)(work
- base
.pszSuffix
);
4818 /* If there is a '?', then the remaining part can only contain a
4819 * query string or fragment, so start looking for the last leaf
4820 * from the '?'. Otherwise, if there is a '#' and the characters
4821 * immediately preceding it are ".htm[l]", then begin looking for
4822 * the last leaf starting from the '#'. Otherwise the '#' is not
4823 * meaningful and just start looking from the end. */
4824 if ((work
= wcspbrk(base
.pszSuffix
+ sizeloc
, fragquerystr
)))
4826 static const WCHAR htmlW
[] = {'.','h','t','m','l'};
4827 static const WCHAR htmW
[] = {'.','h','t','m'};
4829 if (*work
== '?' || base
.nScheme
== URL_SCHEME_HTTP
|| base
.nScheme
== URL_SCHEME_HTTPS
)
4830 manual_search
= TRUE
;
4831 else if (work
- base
.pszSuffix
> ARRAY_SIZE(htmW
))
4833 work
-= ARRAY_SIZE(htmW
);
4834 if (!wcsnicmp(work
, htmW
, ARRAY_SIZE(htmW
)))
4835 manual_search
= TRUE
;
4836 work
+= ARRAY_SIZE(htmW
);
4839 if (!manual_search
&& work
- base
.pszSuffix
> ARRAY_SIZE(htmlW
))
4841 work
-= ARRAY_SIZE(htmlW
);
4842 if (!wcsnicmp(work
, htmlW
, ARRAY_SIZE(htmlW
)))
4843 manual_search
= TRUE
;
4844 work
+= ARRAY_SIZE(htmlW
);
4850 /* search backwards starting from the current position */
4851 while (*work
!= '/' && work
> base
.pszSuffix
+ sizeloc
)
4853 base
.cchSuffix
= work
- base
.pszSuffix
+ 1;
4857 /* search backwards starting from the end of the string */
4858 work
= wcsrchr((base
.pszSuffix
+sizeloc
), '/');
4861 len
= (DWORD
)(work
- base
.pszSuffix
+ 1);
4862 base
.cchSuffix
= len
;
4865 base
.cchSuffix
= sizeloc
;
4870 * .pszSuffix points to location (starting with '//')
4871 * .cchSuffix length of location (above) and rest less the last
4873 * sizeloc length of location (above) up to but not including
4877 if (ParseURLW(mrelative
, &relative
) != S_OK
)
4879 /* No scheme in relative */
4880 TRACE("no scheme detected in Relative\n");
4881 relative
.pszSuffix
= mrelative
; /* case 3,4,5 depends on this */
4882 relative
.cchSuffix
= lstrlenW(mrelative
);
4883 if (*relativeW
== ':')
4885 /* Case that is either left alone or uses base. */
4886 if (flags
& URL_PLUGGABLE_PROTOCOL
)
4894 if (iswalnum(*mrelative
) && *(mrelative
+ 1) == ':')
4896 /* case that becomes "file:///" */
4897 lstrcpyW(preliminary
, myfilestr
);
4901 if (*mrelative
== '/' && *(mrelative
+1) == '/')
4903 /* Relative has location and the rest. */
4907 if (*mrelative
== '/')
4909 /* Relative is root to location. */
4913 if (*mrelative
== '#')
4915 if (!(work
= wcschr(base
.pszSuffix
+base
.cchSuffix
, '#')))
4916 work
= (LPWSTR
)base
.pszSuffix
+ lstrlenW(base
.pszSuffix
);
4918 memcpy(preliminary
, base
.pszProtocol
, (work
-base
.pszProtocol
)*sizeof(WCHAR
));
4919 preliminary
[work
-base
.pszProtocol
] = '\0';
4923 process_case
= (*base
.pszSuffix
== '/' || base
.nScheme
== URL_SCHEME_MK
) ? 5 : 3;
4928 work
= (LPWSTR
)relative
.pszProtocol
;
4929 for (i
= 0; i
< relative
.cchProtocol
; ++i
)
4930 work
[i
] = towlower(work
[i
]);
4933 /* Handle cases where relative has scheme. */
4934 if ((base
.cchProtocol
== relative
.cchProtocol
) && !wcsncmp(base
.pszProtocol
, relative
.pszProtocol
, base
.cchProtocol
))
4936 /* since the schemes are the same */
4937 if (*relative
.pszSuffix
== '/' && *(relative
.pszSuffix
+1) == '/')
4939 /* Relative replaces location and what follows. */
4943 if (*relative
.pszSuffix
== '/')
4945 /* Relative is root to location */
4949 /* replace either just location if base's location starts with a
4950 * slash or otherwise everything */
4951 process_case
= (*base
.pszSuffix
== '/') ? 5 : 1;
4955 if (*relative
.pszSuffix
== '/' && *(relative
.pszSuffix
+1) == '/')
4957 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4963 } while (FALSE
); /* a little trick to allow easy exit from nested if's */
4966 switch (process_case
)
4969 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4970 lstrcatW(preliminary
, mrelative
);
4974 /* Relative replaces scheme and location */
4975 lstrcpyW(preliminary
, mrelative
);
4979 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4980 memcpy(preliminary
, base
.pszProtocol
, (base
.cchProtocol
+ 1)*sizeof(WCHAR
));
4981 work
= preliminary
+ base
.cchProtocol
+ 1;
4982 lstrcpyW(work
, relative
.pszSuffix
);
4986 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4987 memcpy(preliminary
, base
.pszProtocol
, (base
.cchProtocol
+1+sizeloc
)*sizeof(WCHAR
));
4988 work
= preliminary
+ base
.cchProtocol
+ 1 + sizeloc
;
4989 if (flags
& URL_PLUGGABLE_PROTOCOL
)
4991 lstrcpyW(work
, relative
.pszSuffix
);
4995 /* Return the base without its document (if any) and append relative after its scheme. */
4996 memcpy(preliminary
, base
.pszProtocol
, (base
.cchProtocol
+ 1 + base
.cchSuffix
)*sizeof(WCHAR
));
4997 work
= preliminary
+ base
.cchProtocol
+ 1 + base
.cchSuffix
- 1;
5000 lstrcpyW(work
, relative
.pszSuffix
);
5004 FIXME("Unexpected case %d.\n", process_case
);
5010 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
5011 if (*combined_len
== 0)
5013 hr
= UrlCanonicalizeW(preliminary
, mrelative
, combined_len
, flags
& ~URL_FILE_USE_PATHURL
);
5014 if (SUCCEEDED(hr
) && combined
)
5015 lstrcpyW(combined
, mrelative
);
5017 TRACE("return-%d len=%d, %s\n", process_case
, *combined_len
, debugstr_w(combined
));
5020 heap_free(preliminary
);
5024 HRESULT WINAPI
HashData(const unsigned char *src
, DWORD src_len
, unsigned char *dest
, DWORD dest_len
)
5026 INT src_count
= src_len
- 1, dest_count
= dest_len
- 1;
5029 return E_INVALIDARG
;
5031 while (dest_count
>= 0)
5033 dest
[dest_count
] = (dest_count
& 0xff);
5037 while (src_count
>= 0)
5039 dest_count
= dest_len
- 1;
5040 while (dest_count
>= 0)
5042 dest
[dest_count
] = hashdata_lookup
[src
[src_count
] ^ dest
[dest_count
]];
5051 HRESULT WINAPI
UrlHashA(const char *url
, unsigned char *dest
, DWORD dest_len
)
5055 HashData((const BYTE
*)url
, (int)strlen(url
), dest
, dest_len
);
5059 return E_INVALIDARG
;
5065 HRESULT WINAPI
UrlHashW(const WCHAR
*url
, unsigned char *dest
, DWORD dest_len
)
5067 char urlA
[MAX_PATH
];
5069 TRACE("%s, %p, %d\n", debugstr_w(url
), dest
, dest_len
);
5073 WideCharToMultiByte(CP_ACP
, 0, url
, -1, urlA
, MAX_PATH
, NULL
, NULL
);
5074 HashData((const BYTE
*)urlA
, (int)strlen(urlA
), dest
, dest_len
);
5078 return E_INVALIDARG
;
5084 BOOL WINAPI
IsInternetESCEnabled(void)