kernelbase: Do not parse the hostname of internet URLs if they are missing a double...
[wine.git] / dlls / kernelbase / path.c
blob7eda9bd483c82397de6fffd3d33e2c2cd995506f
1 /*
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
20 #include <stdarg.h>
21 #include <string.h>
23 #include "windef.h"
24 #include "winbase.h"
25 #include "pathcch.h"
26 #include "strsafe.h"
27 #include "shlwapi.h"
28 #include "wininet.h"
29 #include "intshcut.h"
30 #include "winternl.h"
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 static const char hexDigits[] = "0123456789ABCDEF";
41 static const unsigned char hashdata_lookup[256] =
43 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33,
44 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41,
45 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c,
46 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90,
47 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe,
48 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd,
49 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d,
50 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd,
51 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2,
52 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b,
53 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70,
54 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b,
55 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47,
56 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d,
57 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8,
58 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1,
61 struct parsed_url
63 const WCHAR *scheme; /* [out] start of scheme */
64 DWORD scheme_len; /* [out] size of scheme (until colon) */
65 const WCHAR *username; /* [out] start of Username */
66 DWORD username_len; /* [out] size of Username (until ":" or "@") */
67 const WCHAR *password; /* [out] start of Password */
68 DWORD password_len; /* [out] size of Password (until "@") */
69 const WCHAR *hostname; /* [out] start of Hostname */
70 DWORD hostname_len; /* [out] size of Hostname (until ":" or "/") */
71 const WCHAR *port; /* [out] start of Port */
72 DWORD port_len; /* [out] size of Port (until "/" or eos) */
73 const WCHAR *query; /* [out] start of Query */
74 DWORD query_len; /* [out] size of Query (until eos) */
75 DWORD scheme_number;
78 static WCHAR *heap_strdupAtoW(const char *str)
80 WCHAR *ret = NULL;
82 if (str)
84 DWORD len;
86 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
87 ret = heap_alloc(len * sizeof(WCHAR));
88 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
91 return ret;
94 static BOOL is_drive_spec( const WCHAR *str )
96 return isalpha( str[0] ) && str[1] == ':';
99 static BOOL is_escaped_drive_spec( const WCHAR *str )
101 return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|');
104 static BOOL is_prefixed_unc(const WCHAR *string)
106 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 );
109 static BOOL is_prefixed_disk(const WCHAR *string)
111 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 );
114 static BOOL is_prefixed_volume(const WCHAR *string)
116 const WCHAR *guid;
117 INT i = 0;
119 if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE;
121 guid = string + 10;
123 while (i <= 37)
125 switch (i)
127 case 0:
128 if (guid[i] != '{') return FALSE;
129 break;
130 case 9:
131 case 14:
132 case 19:
133 case 24:
134 if (guid[i] != '-') return FALSE;
135 break;
136 case 37:
137 if (guid[i] != '}') return FALSE;
138 break;
139 default:
140 if (!isxdigit(guid[i])) return FALSE;
141 break;
143 i++;
146 return TRUE;
149 /* Get the next character beyond end of the segment.
150 Return TRUE if the last segment ends with a backslash */
151 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment)
153 while (*next && *next != '\\') next++;
154 if (*next == '\\')
156 *next_segment = next + 1;
157 return TRUE;
159 else
161 *next_segment = next;
162 return FALSE;
166 /* Find the last character of the root in a path, if there is one, without any segments */
167 static const WCHAR *get_root_end(const WCHAR *path)
169 /* Find path root */
170 if (is_prefixed_volume(path))
171 return path[48] == '\\' ? path + 48 : path + 47;
172 else if (is_prefixed_unc(path))
173 return path + 7;
174 else if (is_prefixed_disk(path))
175 return path[6] == '\\' ? path + 6 : path + 5;
176 /* \\ */
177 else if (path[0] == '\\' && path[1] == '\\')
178 return path + 1;
179 /* \ */
180 else if (path[0] == '\\')
181 return path;
182 /* X:\ */
183 else if (is_drive_spec( path ))
184 return path[2] == '\\' ? path + 2 : path + 1;
185 else
186 return NULL;
189 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
191 WCHAR *buffer, *dst;
192 const WCHAR *src;
193 const WCHAR *root_end;
194 SIZE_T buffer_size, length;
196 TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out);
198 if (!path_in || !path_out
199 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS))
200 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)
201 && !(flags & PATHCCH_ALLOW_LONG_PATHS))
202 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS)))
204 if (path_out) *path_out = NULL;
205 return E_INVALIDARG;
208 length = lstrlenW(path_in);
209 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)))
210 || (length + 1 > PATHCCH_MAX_CCH))
212 *path_out = NULL;
213 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
216 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
217 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
219 /* path length + possible \\?\ addition + possible \ addition + NUL */
220 buffer_size = (length + 6) * sizeof(WCHAR);
221 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size);
222 if (!buffer)
224 *path_out = NULL;
225 return E_OUTOFMEMORY;
228 src = path_in;
229 dst = buffer;
231 root_end = get_root_end(path_in);
232 if (root_end) root_end = buffer + (root_end - path_in);
234 /* Copy path root */
235 if (root_end)
237 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR));
238 src += root_end - buffer + 1;
239 if(PathCchStripPrefix(dst, length + 6) == S_OK)
241 /* Fill in \ in X:\ if the \ is missing */
242 if (is_drive_spec( dst ) && dst[2]!= '\\')
244 dst[2] = '\\';
245 dst[3] = 0;
247 dst = buffer + lstrlenW(buffer);
248 root_end = dst;
250 else
251 dst += root_end - buffer + 1;
254 while (*src)
256 if (src[0] == '.')
258 if (src[1] == '.')
260 /* Keep one . after * */
261 if (dst > buffer && dst[-1] == '*')
263 *dst++ = *src++;
264 continue;
267 /* Keep the .. if not surrounded by \ */
268 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\'))
270 *dst++ = *src++;
271 *dst++ = *src++;
272 continue;
275 /* Remove the \ before .. if the \ is not part of root */
276 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
278 *--dst = '\0';
279 /* Remove characters until a \ is encountered */
280 while (dst > buffer)
282 if (dst[-1] == '\\')
284 *--dst = 0;
285 break;
287 else
288 *--dst = 0;
291 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
292 else if (src[2] == '\\')
293 src++;
295 src += 2;
297 else
299 /* Keep the . if not surrounded by \ */
300 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\'))
302 *dst++ = *src++;
303 continue;
306 /* Remove the \ before . if the \ is not part of root */
307 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--;
308 /* Remove the extra \ after . if the \ before . wasn't deleted */
309 else if (src[1] == '\\')
310 src++;
312 src++;
315 /* If X:\ is not complete, then complete it */
316 if (is_drive_spec( buffer ) && buffer[2] != '\\')
318 root_end = buffer + 2;
319 dst = buffer + 3;
320 buffer[2] = '\\';
321 /* If next character is \, use the \ to fill in */
322 if (src[0] == '\\') src++;
325 /* Copy over */
326 else
327 *dst++ = *src++;
329 /* End the path */
330 *dst = 0;
332 /* Strip multiple trailing . */
333 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
335 while (dst > buffer && dst[-1] == '.')
337 /* Keep a . after * */
338 if (dst - 1 > buffer && dst[-2] == '*')
339 break;
340 /* If . follow a : at the second character, remove the . and add a \ */
341 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1)
342 *--dst = '\\';
343 else
344 *--dst = 0;
348 /* If result path is empty, fill in \ */
349 if (!*buffer)
351 buffer[0] = '\\';
352 buffer[1] = 0;
355 /* Extend the path if needed */
356 length = lstrlenW(buffer);
357 if (((length + 1 > MAX_PATH && is_drive_spec( buffer ))
358 || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))
359 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS))
361 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR));
362 buffer[0] = '\\';
363 buffer[1] = '\\';
364 buffer[2] = '?';
365 buffer[3] = '\\';
368 /* Add a trailing backslash to the path if needed */
369 if (flags & PATHCCH_ENSURE_TRAILING_SLASH)
370 PathCchAddBackslash(buffer, buffer_size);
372 *path_out = buffer;
373 return S_OK;
376 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out)
378 SIZE_T combined_length, length2;
379 WCHAR *combined_path;
380 BOOL add_backslash = FALSE;
381 HRESULT hr;
383 TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out);
385 if ((!path1 && !path2) || !out)
387 if (out) *out = NULL;
388 return E_INVALIDARG;
391 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out);
393 /* If path2 is fully qualified, use path2 only */
394 if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\'))
396 path1 = path2;
397 path2 = NULL;
398 add_backslash = (is_drive_spec(path1) && !path1[2])
399 || (is_prefixed_disk(path1) && !path1[6]);
402 length2 = path2 ? lstrlenW(path2) : 0;
403 /* path1 length + path2 length + possible backslash + NULL */
404 combined_length = lstrlenW(path1) + length2 + 2;
406 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR));
407 if (!combined_path)
409 *out = NULL;
410 return E_OUTOFMEMORY;
413 lstrcpyW(combined_path, path1);
414 PathCchStripPrefix(combined_path, combined_length);
415 if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
417 if (path2 && path2[0])
419 if (path2[0] == '\\' && path2[1] != '\\')
421 PathCchStripToRoot(combined_path, combined_length);
422 path2++;
425 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
426 lstrcatW(combined_path, path2);
429 hr = PathAllocCanonicalize(combined_path, flags, out);
430 HeapFree(GetProcessHeap(), 0, combined_path);
431 return hr;
434 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size)
436 return PathCchAddBackslashEx(path, size, NULL, NULL);
439 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining)
441 BOOL needs_termination;
442 SIZE_T length;
444 TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining);
446 length = lstrlenW(path);
447 needs_termination = size && length && path[length - 1] != '\\';
449 if (length >= (needs_termination ? size - 1 : size))
451 if (endptr) *endptr = NULL;
452 if (remaining) *remaining = 0;
453 return STRSAFE_E_INSUFFICIENT_BUFFER;
456 if (!needs_termination)
458 if (endptr) *endptr = path + length;
459 if (remaining) *remaining = size - length;
460 return S_FALSE;
463 path[length++] = '\\';
464 path[length] = 0;
466 if (endptr) *endptr = path + length;
467 if (remaining) *remaining = size - length;
469 return S_OK;
472 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
474 const WCHAR *existing_extension, *next;
475 SIZE_T path_length, extension_length, dot_length;
476 BOOL has_dot;
477 HRESULT hr;
479 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
481 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG;
483 next = extension;
484 while (*next)
486 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG;
487 next++;
490 has_dot = extension[0] == '.';
492 hr = PathCchFindExtension(path, size, &existing_extension);
493 if (FAILED(hr)) return hr;
494 if (*existing_extension) return S_FALSE;
496 path_length = wcsnlen(path, size);
497 dot_length = has_dot ? 0 : 1;
498 extension_length = lstrlenW(extension);
500 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER;
502 /* If extension is empty or only dot, return S_OK with path unchanged */
503 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK;
505 if (!has_dot)
507 path[path_length] = '.';
508 path_length++;
511 lstrcpyW(path + path_length, extension);
512 return S_OK;
515 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2)
517 TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2));
519 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE);
522 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags)
524 HRESULT hr;
525 WCHAR *result;
527 TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags);
529 if (!path1 || !size) return E_INVALIDARG;
531 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
532 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
533 * buffer for PathCchCombineEx */
534 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
535 if (!result) return E_OUTOFMEMORY;
537 /* Avoid the single backslash behavior with PathCchCombineEx when appending */
538 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++;
540 hr = PathCchCombineEx(result, size, path1, path2, flags);
541 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR));
543 HeapFree(GetProcessHeap(), 0, result);
544 return hr;
547 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in)
549 TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in));
551 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
552 if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\'))
553 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
555 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE);
558 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags)
560 WCHAR *buffer;
561 SIZE_T length;
562 HRESULT hr;
564 TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags);
566 if (!size) return E_INVALIDARG;
568 hr = PathAllocCanonicalize(in, flags, &buffer);
569 if (FAILED(hr)) return hr;
571 length = lstrlenW(buffer);
572 if (size < length + 1)
574 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
575 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\')))
576 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
577 else
578 hr = STRSAFE_E_INSUFFICIENT_BUFFER;
581 if (SUCCEEDED(hr))
583 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
585 /* Fill a backslash at the end of X: */
586 if (is_drive_spec( out ) && !out[2] && size > 3)
588 out[2] = '\\';
589 out[3] = 0;
593 LocalFree(buffer);
594 return hr;
597 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2)
599 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2));
601 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE);
604 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags)
606 HRESULT hr;
607 WCHAR *buffer;
608 SIZE_T length;
610 TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags);
612 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
614 hr = PathAllocCombine(path1, path2, flags, &buffer);
615 if (FAILED(hr))
617 out[0] = 0;
618 return hr;
621 length = lstrlenW(buffer);
622 if (length + 1 > size)
624 out[0] = 0;
625 LocalFree(buffer);
626 return STRSAFE_E_INSUFFICIENT_BUFFER;
628 else
630 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
631 LocalFree(buffer);
632 return S_OK;
636 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension)
638 const WCHAR *lastpoint = NULL;
639 SIZE_T counter = 0;
641 TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension);
643 if (!path || !size || size > PATHCCH_MAX_CCH)
645 *extension = NULL;
646 return E_INVALIDARG;
649 while (*path)
651 if (*path == '\\' || *path == ' ')
652 lastpoint = NULL;
653 else if (*path == '.')
654 lastpoint = path;
656 path++;
657 counter++;
658 if (counter == size || counter == PATHCCH_MAX_CCH)
660 *extension = NULL;
661 return E_INVALIDARG;
665 *extension = lastpoint ? lastpoint : path;
666 return S_OK;
669 BOOL WINAPI PathCchIsRoot(const WCHAR *path)
671 const WCHAR *root_end;
672 const WCHAR *next;
673 BOOL is_unc;
675 TRACE("%s\n", wine_dbgstr_w(path));
677 if (!path || !*path) return FALSE;
679 root_end = get_root_end(path);
680 if (!root_end) return FALSE;
682 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
684 next = root_end + 1;
685 /* No extra segments */
686 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE;
688 /* Has first segment with an ending backslash but no remaining characters */
689 if (get_next_segment(next, &next) && !*next) return FALSE;
690 /* Has first segment with no ending backslash */
691 else if (!*next)
692 return TRUE;
693 /* Has first segment with an ending backslash and has remaining characters*/
694 else
696 next++;
697 /* Second segment must have no backslash and no remaining characters */
698 return !get_next_segment(next, &next) && !*next;
701 else if (*root_end == '\\' && !root_end[1])
702 return TRUE;
703 else
704 return FALSE;
707 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size)
709 WCHAR *path_end;
710 SIZE_T free_size;
712 TRACE("%s %Iu\n", debugstr_w(path), path_size);
714 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size);
717 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size)
719 const WCHAR *root_end;
720 SIZE_T path_length;
722 TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size);
724 if (!path_size || !path_end || !free_size)
726 if (path_end) *path_end = NULL;
727 if (free_size) *free_size = 0;
728 return E_INVALIDARG;
731 path_length = wcsnlen(path, path_size);
732 if (path_length == path_size && !path[path_length]) return E_INVALIDARG;
734 root_end = get_root_end(path);
735 if (path_length > 0 && path[path_length - 1] == '\\')
737 *path_end = path + path_length - 1;
738 *free_size = path_size - path_length + 1;
739 /* If the last character is beyond end of root */
740 if (!root_end || path + path_length - 1 > root_end)
742 path[path_length - 1] = 0;
743 return S_OK;
745 else
746 return S_FALSE;
748 else
750 *path_end = path + path_length;
751 *free_size = path_size - path_length;
752 return S_FALSE;
756 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size)
758 const WCHAR *extension;
759 WCHAR *next;
760 HRESULT hr;
762 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
764 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
766 hr = PathCchFindExtension(path, size, &extension);
767 if (FAILED(hr)) return hr;
769 next = path + (extension - path);
770 while (next - path < size && *next) *next++ = 0;
772 return next == extension ? S_FALSE : S_OK;
775 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size)
777 const WCHAR *root_end = NULL;
778 SIZE_T length;
779 WCHAR *last;
781 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
783 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
785 if (PathCchIsRoot(path)) return S_FALSE;
787 PathCchSkipRoot(path, &root_end);
789 /* The backslash at the end of UNC and \\* are not considered part of root in this case */
790 if (root_end && root_end > path && root_end[-1] == '\\'
791 && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')))
792 root_end--;
794 length = lstrlenW(path);
795 last = path + length - 1;
796 while (last >= path && (!root_end || last >= root_end))
798 if (last - path >= size) return E_INVALIDARG;
800 if (*last == '\\')
802 *last-- = 0;
803 break;
806 *last-- = 0;
809 return last != path + length - 1 ? S_OK : S_FALSE;
812 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
814 HRESULT hr;
816 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
818 hr = PathCchRemoveExtension(path, size);
819 if (FAILED(hr)) return hr;
821 hr = PathCchAddExtension(path, size, extension);
822 return FAILED(hr) ? hr : S_OK;
825 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end)
827 TRACE("%s %p\n", debugstr_w(path), root_end);
829 if (!path || !path[0] || !root_end
830 || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path)
831 && !is_prefixed_disk(path)))
832 return E_INVALIDARG;
834 *root_end = get_root_end(path);
835 if (*root_end)
837 (*root_end)++;
838 if (is_prefixed_unc(path))
840 get_next_segment(*root_end, root_end);
841 get_next_segment(*root_end, root_end);
843 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
845 /* Skip share server */
846 get_next_segment(*root_end, root_end);
847 /* If mount point is empty, don't skip over mount point */
848 if (**root_end != '\\') get_next_segment(*root_end, root_end);
852 return *root_end ? S_OK : E_INVALIDARG;
855 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size)
857 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
859 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
861 if (is_prefixed_unc(path))
863 /* \\?\UNC\a -> \\a */
864 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG;
865 lstrcpyW(path + 2, path + 8);
866 return S_OK;
868 else if (is_prefixed_disk(path))
870 /* \\?\C:\ -> C:\ */
871 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG;
872 lstrcpyW(path, path + 4);
873 return S_OK;
875 else
876 return S_FALSE;
879 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size)
881 const WCHAR *root_end;
882 WCHAR *segment_end;
883 BOOL is_unc;
885 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
887 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
889 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
890 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
891 * \\\\a\\b\\c -> \\\\a\\b */
892 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
894 root_end = is_unc ? path + 8 : path + 3;
895 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
896 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
898 if (root_end - path >= size) return E_INVALIDARG;
900 segment_end = path + (root_end - path) - 1;
901 *segment_end = 0;
902 return S_OK;
904 else if (PathCchSkipRoot(path, &root_end) == S_OK)
906 if (root_end - path >= size) return E_INVALIDARG;
908 segment_end = path + (root_end - path);
909 if (!*segment_end) return S_FALSE;
911 *segment_end = 0;
912 return S_OK;
914 else
915 return E_INVALIDARG;
918 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server)
920 const WCHAR *result = NULL;
922 TRACE("%s %p\n", wine_dbgstr_w(path), server);
924 if (is_prefixed_unc(path))
925 result = path + 8;
926 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
927 result = path + 2;
929 if (server) *server = result;
930 return !!result;
933 BOOL WINAPI PathIsUNCA(const char *path)
935 TRACE("%s\n", wine_dbgstr_a(path));
937 return path && (path[0] == '\\') && (path[1] == '\\');
940 BOOL WINAPI PathIsUNCW(const WCHAR *path)
942 TRACE("%s\n", wine_dbgstr_w(path));
944 return path && (path[0] == '\\') && (path[1] == '\\');
947 BOOL WINAPI PathIsRelativeA(const char *path)
949 TRACE("%s\n", wine_dbgstr_a(path));
951 if (!path || !*path || IsDBCSLeadByte(*path))
952 return TRUE;
954 return !(*path == '\\' || (*path && path[1] == ':'));
957 BOOL WINAPI PathIsRelativeW(const WCHAR *path)
959 TRACE("%s\n", wine_dbgstr_w(path));
961 if (!path || !*path)
962 return TRUE;
964 return !(*path == '\\' || (*path && path[1] == ':'));
967 BOOL WINAPI PathIsUNCServerShareA(const char *path)
969 BOOL seen_slash = FALSE;
971 TRACE("%s\n", wine_dbgstr_a(path));
973 if (path && *path++ == '\\' && *path++ == '\\')
975 while (*path)
977 if (*path == '\\')
979 if (seen_slash)
980 return FALSE;
981 seen_slash = TRUE;
984 path = CharNextA(path);
988 return seen_slash;
991 BOOL WINAPI PathIsUNCServerShareW(const WCHAR *path)
993 BOOL seen_slash = FALSE;
995 TRACE("%s\n", wine_dbgstr_w(path));
997 if (path && *path++ == '\\' && *path++ == '\\')
999 while (*path)
1001 if (*path == '\\')
1003 if (seen_slash)
1004 return FALSE;
1005 seen_slash = TRUE;
1008 path++;
1012 return seen_slash;
1015 BOOL WINAPI PathIsRootA(const char *path)
1017 TRACE("%s\n", wine_dbgstr_a(path));
1019 if (!path || !*path)
1020 return FALSE;
1022 if (*path == '\\')
1024 if (!path[1])
1025 return TRUE; /* \ */
1026 else if (path[1] == '\\')
1028 BOOL seen_slash = FALSE;
1029 path += 2;
1031 /* Check for UNC root path */
1032 while (*path)
1034 if (*path == '\\')
1036 if (seen_slash)
1037 return FALSE;
1038 seen_slash = TRUE;
1041 path = CharNextA(path);
1044 return TRUE;
1047 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1048 return TRUE; /* X:\ */
1050 return FALSE;
1053 BOOL WINAPI PathIsRootW(const WCHAR *path)
1055 TRACE("%s\n", wine_dbgstr_w(path));
1057 if (!path || !*path)
1058 return FALSE;
1060 if (*path == '\\')
1062 if (!path[1])
1063 return TRUE; /* \ */
1064 else if (path[1] == '\\')
1066 BOOL seen_slash = FALSE;
1068 path += 2;
1069 /* Check for UNC root path */
1070 while (*path)
1072 if (*path == '\\')
1074 if (seen_slash)
1075 return FALSE;
1076 seen_slash = TRUE;
1078 path++;
1081 return TRUE;
1084 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1085 return TRUE; /* X:\ */
1087 return FALSE;
1090 BOOL WINAPI PathRemoveFileSpecA(char *path)
1092 char *filespec = path;
1093 BOOL modified = FALSE;
1095 TRACE("%s\n", wine_dbgstr_a(path));
1097 if (!path)
1098 return FALSE;
1100 /* Skip directory or UNC path */
1101 if (*path == '\\')
1102 filespec = ++path;
1103 if (*path == '\\')
1104 filespec = ++path;
1106 while (*path)
1108 if (*path == '\\')
1109 filespec = path; /* Skip dir */
1110 else if (*path == ':')
1112 filespec = ++path; /* Skip drive */
1113 if (*path == '\\')
1114 filespec++;
1116 if (!(path = CharNextA(path)))
1117 break;
1120 if (*filespec)
1122 *filespec = '\0';
1123 modified = TRUE;
1126 return modified;
1129 BOOL WINAPI PathRemoveFileSpecW(WCHAR *path)
1131 WCHAR *filespec = path;
1132 BOOL modified = FALSE;
1134 TRACE("%s\n", wine_dbgstr_w(path));
1136 if (!path)
1137 return FALSE;
1139 /* Skip directory or UNC path */
1140 if (*path == '\\')
1141 filespec = ++path;
1142 if (*path == '\\')
1143 filespec = ++path;
1145 while (*path)
1147 if (*path == '\\')
1148 filespec = path; /* Skip dir */
1149 else if (*path == ':')
1151 filespec = ++path; /* Skip drive */
1152 if (*path == '\\')
1153 filespec++;
1156 path++;
1159 if (*filespec)
1161 *filespec = '\0';
1162 modified = TRUE;
1165 return modified;
1168 BOOL WINAPI PathStripToRootA(char *path)
1170 TRACE("%s\n", wine_dbgstr_a(path));
1172 if (!path)
1173 return FALSE;
1175 while (!PathIsRootA(path))
1176 if (!PathRemoveFileSpecA(path))
1177 return FALSE;
1179 return TRUE;
1182 BOOL WINAPI PathStripToRootW(WCHAR *path)
1184 TRACE("%s\n", wine_dbgstr_w(path));
1186 if (!path)
1187 return FALSE;
1189 while (!PathIsRootW(path))
1190 if (!PathRemoveFileSpecW(path))
1191 return FALSE;
1193 return TRUE;
1196 LPSTR WINAPI PathAddBackslashA(char *path)
1198 unsigned int len;
1199 char *prev = path;
1201 TRACE("%s\n", wine_dbgstr_a(path));
1203 if (!path || (len = strlen(path)) >= MAX_PATH)
1204 return NULL;
1206 if (len)
1210 path = CharNextA(prev);
1211 if (*path)
1212 prev = path;
1213 } while (*path);
1215 if (*prev != '\\')
1217 *path++ = '\\';
1218 *path = '\0';
1222 return path;
1225 LPWSTR WINAPI PathAddBackslashW(WCHAR *path)
1227 unsigned int len;
1229 TRACE("%s\n", wine_dbgstr_w(path));
1231 if (!path || (len = lstrlenW(path)) >= MAX_PATH)
1232 return NULL;
1234 if (len)
1236 path += len;
1237 if (path[-1] != '\\')
1239 *path++ = '\\';
1240 *path = '\0';
1244 return path;
1247 LPSTR WINAPI PathFindExtensionA(const char *path)
1249 const char *lastpoint = NULL;
1251 TRACE("%s\n", wine_dbgstr_a(path));
1253 if (path)
1255 while (*path)
1257 if (*path == '\\' || *path == ' ')
1258 lastpoint = NULL;
1259 else if (*path == '.')
1260 lastpoint = path;
1261 path = CharNextA(path);
1265 return (LPSTR)(lastpoint ? lastpoint : path);
1268 LPWSTR WINAPI PathFindExtensionW(const WCHAR *path)
1270 const WCHAR *lastpoint = NULL;
1272 TRACE("%s\n", wine_dbgstr_w(path));
1274 if (path)
1276 while (*path)
1278 if (*path == '\\' || *path == ' ')
1279 lastpoint = NULL;
1280 else if (*path == '.')
1281 lastpoint = path;
1282 path++;
1286 return (LPWSTR)(lastpoint ? lastpoint : path);
1289 BOOL WINAPI PathAddExtensionA(char *path, const char *ext)
1291 unsigned int len;
1293 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1295 if (!path || !ext || *(PathFindExtensionA(path)))
1296 return FALSE;
1298 len = strlen(path);
1299 if (len + strlen(ext) >= MAX_PATH)
1300 return FALSE;
1302 strcpy(path + len, ext);
1303 return TRUE;
1306 BOOL WINAPI PathAddExtensionW(WCHAR *path, const WCHAR *ext)
1308 unsigned int len;
1310 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1312 if (!path || !ext || *(PathFindExtensionW(path)))
1313 return FALSE;
1315 len = lstrlenW(path);
1316 if (len + lstrlenW(ext) >= MAX_PATH)
1317 return FALSE;
1319 lstrcpyW(path + len, ext);
1320 return TRUE;
1323 BOOL WINAPI PathCanonicalizeW(WCHAR *buffer, const WCHAR *path)
1325 const WCHAR *src = path;
1326 WCHAR *dst = buffer;
1328 TRACE("%p, %s\n", buffer, wine_dbgstr_w(path));
1330 if (dst)
1331 *dst = '\0';
1333 if (!dst || !path)
1335 SetLastError(ERROR_INVALID_PARAMETER);
1336 return FALSE;
1339 if (!*path)
1341 *buffer++ = '\\';
1342 *buffer = '\0';
1343 return TRUE;
1346 /* Copy path root */
1347 if (*src == '\\')
1349 *dst++ = *src++;
1351 else if (*src && src[1] == ':')
1353 /* X:\ */
1354 *dst++ = *src++;
1355 *dst++ = *src++;
1356 if (*src == '\\')
1357 *dst++ = *src++;
1360 /* Canonicalize the rest of the path */
1361 while (*src)
1363 if (*src == '.')
1365 if (src[1] == '\\' && (src == path || src[-1] == '\\' || src[-1] == ':'))
1367 src += 2; /* Skip .\ */
1369 else if (src[1] == '.' && dst != buffer && dst[-1] == '\\')
1371 /* \.. backs up a directory, over the root if it has no \ following X:.
1372 * .. is ignored if it would remove a UNC server name or initial \\
1374 if (dst != buffer)
1376 *dst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
1377 if (dst > buffer + 1 && dst[-1] == '\\' && (dst[-2] != '\\' || dst > buffer + 2))
1379 if (dst[-2] == ':' && (dst > buffer + 3 || dst[-3] == ':'))
1381 dst -= 2;
1382 while (dst > buffer && *dst != '\\')
1383 dst--;
1384 if (*dst == '\\')
1385 dst++; /* Reset to last '\' */
1386 else
1387 dst = buffer; /* Start path again from new root */
1389 else if (dst[-2] != ':' && !PathIsUNCServerShareW(buffer))
1390 dst -= 2;
1392 while (dst > buffer && *dst != '\\')
1393 dst--;
1394 if (dst == buffer)
1396 *dst++ = '\\';
1397 src++;
1400 src += 2; /* Skip .. in src path */
1402 else
1403 *dst++ = *src++;
1405 else
1406 *dst++ = *src++;
1409 /* Append \ to naked drive specs */
1410 if (dst - buffer == 2 && dst[-1] == ':')
1411 *dst++ = '\\';
1412 *dst++ = '\0';
1413 return TRUE;
1416 BOOL WINAPI PathCanonicalizeA(char *buffer, const char *path)
1418 WCHAR pathW[MAX_PATH], bufferW[MAX_PATH];
1419 BOOL ret;
1420 int len;
1422 TRACE("%p, %s\n", buffer, wine_dbgstr_a(path));
1424 if (buffer)
1425 *buffer = '\0';
1427 if (!buffer || !path)
1429 SetLastError(ERROR_INVALID_PARAMETER);
1430 return FALSE;
1433 len = MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, ARRAY_SIZE(pathW));
1434 if (!len)
1435 return FALSE;
1437 ret = PathCanonicalizeW(bufferW, pathW);
1438 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, 0, 0);
1440 return ret;
1443 WCHAR * WINAPI PathCombineW(WCHAR *dst, const WCHAR *dir, const WCHAR *file)
1445 BOOL use_both = FALSE, strip = FALSE;
1446 WCHAR tmp[MAX_PATH];
1448 TRACE("%p, %s, %s\n", dst, wine_dbgstr_w(dir), wine_dbgstr_w(file));
1450 /* Invalid parameters */
1451 if (!dst)
1452 return NULL;
1454 if (!dir && !file)
1456 dst[0] = 0;
1457 return NULL;
1460 if ((!file || !*file) && dir)
1462 /* Use dir only */
1463 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1465 else if (!dir || !*dir || !PathIsRelativeW(file))
1467 if (!dir || !*dir || *file != '\\' || PathIsUNCW(file))
1469 /* Use file only */
1470 lstrcpynW(tmp, file, ARRAY_SIZE(tmp));
1472 else
1474 use_both = TRUE;
1475 strip = TRUE;
1478 else
1479 use_both = TRUE;
1481 if (use_both)
1483 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1484 if (strip)
1486 PathStripToRootW(tmp);
1487 file++; /* Skip '\' */
1490 if (!PathAddBackslashW(tmp) || lstrlenW(tmp) + lstrlenW(file) >= MAX_PATH)
1492 dst[0] = 0;
1493 return NULL;
1496 lstrcatW(tmp, file);
1499 PathCanonicalizeW(dst, tmp);
1500 return dst;
1503 LPSTR WINAPI PathCombineA(char *dst, const char *dir, const char *file)
1505 WCHAR dstW[MAX_PATH], dirW[MAX_PATH], fileW[MAX_PATH];
1507 TRACE("%p, %s, %s\n", dst, wine_dbgstr_a(dir), wine_dbgstr_a(file));
1509 /* Invalid parameters */
1510 if (!dst)
1511 return NULL;
1513 if (!dir && !file)
1514 goto fail;
1516 if (dir && !MultiByteToWideChar(CP_ACP, 0, dir, -1, dirW, ARRAY_SIZE(dirW)))
1517 goto fail;
1519 if (file && !MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW)))
1520 goto fail;
1522 if (PathCombineW(dstW, dir ? dirW : NULL, file ? fileW : NULL))
1523 if (WideCharToMultiByte(CP_ACP, 0, dstW, -1, dst, MAX_PATH, 0, 0))
1524 return dst;
1525 fail:
1526 dst[0] = 0;
1527 return NULL;
1530 BOOL WINAPI PathAppendA(char *path, const char *append)
1532 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(append));
1534 if (path && append)
1536 if (!PathIsUNCA(append))
1537 while (*append == '\\')
1538 append++;
1540 if (PathCombineA(path, path, append))
1541 return TRUE;
1544 return FALSE;
1547 BOOL WINAPI PathAppendW(WCHAR *path, const WCHAR *append)
1549 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(append));
1551 if (path && append)
1553 if (!PathIsUNCW(append))
1554 while (*append == '\\')
1555 append++;
1557 if (PathCombineW(path, path, append))
1558 return TRUE;
1561 return FALSE;
1564 int WINAPI PathCommonPrefixA(const char *file1, const char *file2, char *path)
1566 const char *iter1 = file1;
1567 const char *iter2 = file2;
1568 unsigned int len = 0;
1570 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1), wine_dbgstr_a(file2), path);
1572 if (path)
1573 *path = '\0';
1575 if (!file1 || !file2)
1576 return 0;
1578 /* Handle roots first */
1579 if (PathIsUNCA(file1))
1581 if (!PathIsUNCA(file2))
1582 return 0;
1583 iter1 += 2;
1584 iter2 += 2;
1586 else if (PathIsUNCA(file2))
1587 return 0;
1589 for (;;)
1591 /* Update len */
1592 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1593 len = iter1 - file1; /* Common to this point */
1595 if (!*iter1 || (tolower(*iter1) != tolower(*iter2)))
1596 break; /* Strings differ at this point */
1598 iter1++;
1599 iter2++;
1602 if (len == 2)
1603 len++; /* Feature/Bug compatible with Win32 */
1605 if (len && path)
1607 memcpy(path, file1, len);
1608 path[len] = '\0';
1611 return len;
1614 int WINAPI PathCommonPrefixW(const WCHAR *file1, const WCHAR *file2, WCHAR *path)
1616 const WCHAR *iter1 = file1;
1617 const WCHAR *iter2 = file2;
1618 unsigned int len = 0;
1620 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1), wine_dbgstr_w(file2), path);
1622 if (path)
1623 *path = '\0';
1625 if (!file1 || !file2)
1626 return 0;
1628 /* Handle roots first */
1629 if (PathIsUNCW(file1))
1631 if (!PathIsUNCW(file2))
1632 return 0;
1633 iter1 += 2;
1634 iter2 += 2;
1636 else if (PathIsUNCW(file2))
1637 return 0;
1639 for (;;)
1641 /* Update len */
1642 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1643 len = iter1 - file1; /* Common to this point */
1645 if (!*iter1 || (towupper(*iter1) != towupper(*iter2)))
1646 break; /* Strings differ at this point */
1648 iter1++;
1649 iter2++;
1652 if (len == 2)
1653 len++; /* Feature/Bug compatible with Win32 */
1655 if (len && path)
1657 memcpy(path, file1, len * sizeof(WCHAR));
1658 path[len] = '\0';
1661 return len;
1664 BOOL WINAPI PathIsPrefixA(const char *prefix, const char *path)
1666 TRACE("%s, %s\n", wine_dbgstr_a(prefix), wine_dbgstr_a(path));
1668 return prefix && path && PathCommonPrefixA(path, prefix, NULL) == (int)strlen(prefix);
1671 BOOL WINAPI PathIsPrefixW(const WCHAR *prefix, const WCHAR *path)
1673 TRACE("%s, %s\n", wine_dbgstr_w(prefix), wine_dbgstr_w(path));
1675 return prefix && path && PathCommonPrefixW(path, prefix, NULL) == (int)lstrlenW(prefix);
1678 char * WINAPI PathFindFileNameA(const char *path)
1680 const char *last_slash = path;
1682 TRACE("%s\n", wine_dbgstr_a(path));
1684 while (path && *path)
1686 if ((*path == '\\' || *path == '/' || *path == ':') &&
1687 path[1] && path[1] != '\\' && path[1] != '/')
1688 last_slash = path + 1;
1689 path = CharNextA(path);
1692 return (char *)last_slash;
1695 WCHAR * WINAPI PathFindFileNameW(const WCHAR *path)
1697 const WCHAR *last_slash = path;
1699 TRACE("%s\n", wine_dbgstr_w(path));
1701 while (path && *path)
1703 if ((*path == '\\' || *path == '/' || *path == ':') &&
1704 path[1] && path[1] != '\\' && path[1] != '/')
1705 last_slash = path + 1;
1706 path++;
1709 return (WCHAR *)last_slash;
1712 char * WINAPI PathGetArgsA(const char *path)
1714 BOOL seen_quote = FALSE;
1716 TRACE("%s\n", wine_dbgstr_a(path));
1718 if (!path)
1719 return NULL;
1721 while (*path)
1723 if (*path == ' ' && !seen_quote)
1724 return (char *)path + 1;
1726 if (*path == '"')
1727 seen_quote = !seen_quote;
1728 path = CharNextA(path);
1731 return (char *)path;
1734 WCHAR * WINAPI PathGetArgsW(const WCHAR *path)
1736 BOOL seen_quote = FALSE;
1738 TRACE("%s\n", wine_dbgstr_w(path));
1740 if (!path)
1741 return NULL;
1743 while (*path)
1745 if (*path == ' ' && !seen_quote)
1746 return (WCHAR *)path + 1;
1748 if (*path == '"')
1749 seen_quote = !seen_quote;
1750 path++;
1753 return (WCHAR *)path;
1756 UINT WINAPI PathGetCharTypeW(WCHAR ch)
1758 UINT flags = 0;
1760 TRACE("%#x\n", ch);
1762 if (!ch || ch < ' ' || ch == '<' || ch == '>' || ch == '"' || ch == '|' || ch == '/')
1763 flags = GCT_INVALID; /* Invalid */
1764 else if (ch == '*' || ch == '?')
1765 flags = GCT_WILD; /* Wildchars */
1766 else if (ch == '\\' || ch == ':')
1767 return GCT_SEPARATOR; /* Path separators */
1768 else
1770 if (ch < 126)
1772 if (((ch & 0x1) && ch != ';') || !ch || isalnum(ch) || ch == '$' || ch == '&' || ch == '(' ||
1773 ch == '.' || ch == '@' || ch == '^' || ch == '\'' || ch == '`')
1775 flags |= GCT_SHORTCHAR; /* All these are valid for DOS */
1778 else
1779 flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */
1781 flags |= GCT_LFNCHAR; /* Valid for long file names */
1784 return flags;
1787 UINT WINAPI PathGetCharTypeA(UCHAR ch)
1789 return PathGetCharTypeW(ch);
1792 int WINAPI PathGetDriveNumberA(const char *path)
1794 TRACE("%s\n", wine_dbgstr_a(path));
1796 if (path && *path && path[1] == ':')
1798 if (*path >= 'a' && *path <= 'z') return *path - 'a';
1799 if (*path >= 'A' && *path <= 'Z') return *path - 'A';
1801 return -1;
1804 int WINAPI PathGetDriveNumberW(const WCHAR *path)
1806 TRACE("%s\n", wine_dbgstr_w(path));
1808 if (!path)
1809 return -1;
1811 if (!wcsncmp(path, L"\\\\?\\", 4)) path += 4;
1813 if (!path[0] || path[1] != ':') return -1;
1814 if (path[0] >= 'A' && path[0] <= 'Z') return path[0] - 'A';
1815 if (path[0] >= 'a' && path[0] <= 'z') return path[0] - 'a';
1816 return -1;
1819 BOOL WINAPI PathIsFileSpecA(const char *path)
1821 TRACE("%s\n", wine_dbgstr_a(path));
1823 if (!path)
1824 return FALSE;
1826 while (*path)
1828 if (*path == '\\' || *path == ':')
1829 return FALSE;
1830 path = CharNextA(path);
1833 return TRUE;
1836 BOOL WINAPI PathIsFileSpecW(const WCHAR *path)
1838 TRACE("%s\n", wine_dbgstr_w(path));
1840 if (!path)
1841 return FALSE;
1843 while (*path)
1845 if (*path == '\\' || *path == ':')
1846 return FALSE;
1847 path++;
1850 return TRUE;
1853 BOOL WINAPI PathIsUNCServerA(const char *path)
1855 TRACE("%s\n", wine_dbgstr_a(path));
1857 if (!(path && path[0] == '\\' && path[1] == '\\'))
1858 return FALSE;
1860 while (*path)
1862 if (*path == '\\')
1863 return FALSE;
1864 path = CharNextA(path);
1867 return TRUE;
1870 BOOL WINAPI PathIsUNCServerW(const WCHAR *path)
1872 TRACE("%s\n", wine_dbgstr_w(path));
1874 if (!(path && path[0] == '\\' && path[1] == '\\'))
1875 return FALSE;
1877 return !wcschr(path + 2, '\\');
1880 void WINAPI PathRemoveBlanksA(char *path)
1882 char *start, *first;
1884 TRACE("%s\n", wine_dbgstr_a(path));
1886 if (!path || !*path)
1887 return;
1889 start = first = path;
1891 while (*path == ' ')
1892 path = CharNextA(path);
1894 while (*path)
1895 *start++ = *path++;
1897 if (start != first)
1898 while (start[-1] == ' ')
1899 start--;
1901 *start = '\0';
1904 void WINAPI PathRemoveBlanksW(WCHAR *path)
1906 WCHAR *start, *first;
1908 TRACE("%s\n", wine_dbgstr_w(path));
1910 if (!path || !*path)
1911 return;
1913 start = first = path;
1915 while (*path == ' ')
1916 path++;
1918 while (*path)
1919 *start++ = *path++;
1921 if (start != first)
1922 while (start[-1] == ' ')
1923 start--;
1925 *start = '\0';
1928 void WINAPI PathRemoveExtensionA(char *path)
1930 TRACE("%s\n", wine_dbgstr_a(path));
1932 if (!path)
1933 return;
1935 path = PathFindExtensionA(path);
1936 if (path && *path)
1937 *path = '\0';
1940 void WINAPI PathRemoveExtensionW(WCHAR *path)
1942 TRACE("%s\n", wine_dbgstr_w(path));
1944 if (!path)
1945 return;
1947 path = PathFindExtensionW(path);
1948 if (path && *path)
1949 *path = '\0';
1952 BOOL WINAPI PathRenameExtensionA(char *path, const char *ext)
1954 char *extension;
1956 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1958 extension = PathFindExtensionA(path);
1960 if (!extension || (extension - path + strlen(ext) >= MAX_PATH))
1961 return FALSE;
1963 strcpy(extension, ext);
1964 return TRUE;
1967 BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext)
1969 WCHAR *extension;
1971 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1973 extension = PathFindExtensionW(path);
1975 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH))
1976 return FALSE;
1978 lstrcpyW(extension, ext);
1979 return TRUE;
1982 void WINAPI PathUnquoteSpacesA(char *path)
1984 unsigned int len;
1986 TRACE("%s\n", wine_dbgstr_a(path));
1988 if (!path || *path != '"')
1989 return;
1991 len = strlen(path) - 1;
1992 if (path[len] == '"')
1994 path[len] = '\0';
1995 for (; *path; path++)
1996 *path = path[1];
2000 void WINAPI PathUnquoteSpacesW(WCHAR *path)
2002 unsigned int len;
2004 TRACE("%s\n", wine_dbgstr_w(path));
2006 if (!path || *path != '"')
2007 return;
2009 len = lstrlenW(path) - 1;
2010 if (path[len] == '"')
2012 path[len] = '\0';
2013 for (; *path; path++)
2014 *path = path[1];
2018 char * WINAPI PathRemoveBackslashA(char *path)
2020 char *ptr;
2022 TRACE("%s\n", wine_dbgstr_a(path));
2024 if (!path)
2025 return NULL;
2027 ptr = CharPrevA(path, path + strlen(path));
2028 if (!PathIsRootA(path) && *ptr == '\\')
2029 *ptr = '\0';
2031 return ptr;
2034 WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path)
2036 WCHAR *ptr;
2038 TRACE("%s\n", wine_dbgstr_w(path));
2040 if (!path)
2041 return NULL;
2043 ptr = path + lstrlenW(path);
2044 if (ptr > path) ptr--;
2045 if (!PathIsRootW(path) && *ptr == '\\')
2046 *ptr = '\0';
2048 return ptr;
2051 BOOL WINAPI PathIsLFNFileSpecA(const char *path)
2053 unsigned int name_len = 0, ext_len = 0;
2055 TRACE("%s\n", wine_dbgstr_a(path));
2057 if (!path)
2058 return FALSE;
2060 while (*path)
2062 if (*path == ' ')
2063 return TRUE; /* DOS names cannot have spaces */
2064 if (*path == '.')
2066 if (ext_len)
2067 return TRUE; /* DOS names have only one dot */
2068 ext_len = 1;
2070 else if (ext_len)
2072 ext_len++;
2073 if (ext_len > 4)
2074 return TRUE; /* DOS extensions are <= 3 chars*/
2076 else
2078 name_len++;
2079 if (name_len > 8)
2080 return TRUE; /* DOS names are <= 8 chars */
2082 path = CharNextA(path);
2085 return FALSE; /* Valid DOS path */
2088 BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path)
2090 unsigned int name_len = 0, ext_len = 0;
2092 TRACE("%s\n", wine_dbgstr_w(path));
2094 if (!path)
2095 return FALSE;
2097 while (*path)
2099 if (*path == ' ')
2100 return TRUE; /* DOS names cannot have spaces */
2101 if (*path == '.')
2103 if (ext_len)
2104 return TRUE; /* DOS names have only one dot */
2105 ext_len = 1;
2107 else if (ext_len)
2109 ext_len++;
2110 if (ext_len > 4)
2111 return TRUE; /* DOS extensions are <= 3 chars*/
2113 else
2115 name_len++;
2116 if (name_len > 8)
2117 return TRUE; /* DOS names are <= 8 chars */
2119 path++;
2122 return FALSE; /* Valid DOS path */
2125 #define PATH_CHAR_CLASS_LETTER 0x00000001
2126 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2127 #define PATH_CHAR_CLASS_DOT 0x00000004
2128 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2129 #define PATH_CHAR_CLASS_COLON 0x00000010
2130 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2131 #define PATH_CHAR_CLASS_COMMA 0x00000040
2132 #define PATH_CHAR_CLASS_SPACE 0x00000080
2133 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2134 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2136 #define PATH_CHAR_CLASS_INVALID 0x00000000
2137 #define PATH_CHAR_CLASS_ANY 0xffffffff
2139 static const DWORD path_charclass[] =
2141 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID,
2142 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID,
2143 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID,
2144 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID,
2145 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID,
2146 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID,
2147 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID,
2148 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID,
2149 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID,
2150 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID,
2151 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID,
2152 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID,
2153 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID,
2154 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID,
2155 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID,
2156 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID,
2157 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID,
2158 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID,
2159 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID,
2160 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID,
2161 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID,
2162 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID,
2163 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID,
2164 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID,
2165 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID,
2166 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID,
2167 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID,
2168 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID,
2169 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID,
2170 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON,
2171 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID,
2172 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER,
2173 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY,
2174 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY,
2175 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY,
2176 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY,
2177 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY,
2178 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY,
2179 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY,
2180 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY,
2181 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY,
2182 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY,
2183 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY,
2184 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY,
2185 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY,
2186 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID,
2187 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID,
2188 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID,
2189 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY,
2190 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY,
2191 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY,
2192 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY,
2193 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY,
2194 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY,
2195 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY,
2196 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY,
2197 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY,
2198 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY,
2199 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY,
2200 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY,
2201 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY,
2202 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID,
2203 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID,
2204 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2207 BOOL WINAPI PathIsValidCharA(char c, DWORD class)
2209 if ((unsigned)c > 0x7e)
2210 return class & PATH_CHAR_CLASS_OTHER_VALID;
2212 return class & path_charclass[(unsigned)c];
2215 BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class)
2217 if (c > 0x7e)
2218 return class & PATH_CHAR_CLASS_OTHER_VALID;
2220 return class & path_charclass[c];
2223 char * WINAPI PathFindNextComponentA(const char *path)
2225 char *slash;
2227 TRACE("%s\n", wine_dbgstr_a(path));
2229 if (!path || !*path)
2230 return NULL;
2232 if ((slash = StrChrA(path, '\\')))
2234 if (slash[1] == '\\')
2235 slash++;
2236 return slash + 1;
2239 return (char *)path + strlen(path);
2242 WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path)
2244 WCHAR *slash;
2246 TRACE("%s\n", wine_dbgstr_w(path));
2248 if (!path || !*path)
2249 return NULL;
2251 if ((slash = StrChrW(path, '\\')))
2253 if (slash[1] == '\\')
2254 slash++;
2255 return slash + 1;
2258 return (WCHAR *)path + lstrlenW(path);
2261 char * WINAPI PathSkipRootA(const char *path)
2263 TRACE("%s\n", wine_dbgstr_a(path));
2265 if (!path || !*path)
2266 return NULL;
2268 if (*path == '\\' && path[1] == '\\')
2270 /* Network share: skip share server and mount point */
2271 path += 2;
2272 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\')))
2273 path++;
2274 return (char *)path;
2277 if (IsDBCSLeadByte(*path))
2278 return NULL;
2280 /* Check x:\ */
2281 if (path[0] && path[1] == ':' && path[2] == '\\')
2282 return (char *)path + 3;
2284 return NULL;
2287 WCHAR * WINAPI PathSkipRootW(const WCHAR *path)
2289 TRACE("%s\n", wine_dbgstr_w(path));
2291 if (!path || !*path)
2292 return NULL;
2294 if (*path == '\\' && path[1] == '\\')
2296 /* Network share: skip share server and mount point */
2297 path += 2;
2298 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\')))
2299 path++;
2300 return (WCHAR *)path;
2303 /* Check x:\ */
2304 if (path[0] && path[1] == ':' && path[2] == '\\')
2305 return (WCHAR *)path + 3;
2307 return NULL;
2310 void WINAPI PathStripPathA(char *path)
2312 TRACE("%s\n", wine_dbgstr_a(path));
2314 if (path)
2316 char *filename = PathFindFileNameA(path);
2317 if (filename != path)
2318 RtlMoveMemory(path, filename, strlen(filename) + 1);
2322 void WINAPI PathStripPathW(WCHAR *path)
2324 WCHAR *filename;
2326 TRACE("%s\n", wine_dbgstr_w(path));
2327 filename = PathFindFileNameW(path);
2328 if (filename != path)
2329 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR));
2332 BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length)
2334 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length);
2336 if (SearchPathA(NULL, path, NULL, length, buffer, NULL))
2337 return TRUE;
2339 return !!GetFullPathNameA(path, length, buffer, NULL);
2342 BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length)
2344 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length);
2346 if (SearchPathW(NULL, path, NULL, length, buffer, NULL))
2347 return TRUE;
2348 return !!GetFullPathNameW(path, length, buffer, NULL);
2351 BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to,
2352 DWORD attributes_to)
2354 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH];
2355 BOOL ret;
2357 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to);
2359 if (!path || !from || !to)
2360 return FALSE;
2362 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW));
2363 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW));
2364 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to);
2365 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0);
2367 return ret;
2370 BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to,
2371 DWORD attributes_to)
2373 WCHAR fromW[MAX_PATH], toW[MAX_PATH];
2374 DWORD len;
2376 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to);
2378 if (!path || !from || !to)
2379 return FALSE;
2381 *path = '\0';
2382 lstrcpynW(fromW, from, ARRAY_SIZE(fromW));
2383 lstrcpynW(toW, to, ARRAY_SIZE(toW));
2385 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY))
2386 PathRemoveFileSpecW(fromW);
2387 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY))
2388 PathRemoveFileSpecW(toW);
2390 /* Paths can only be relative if they have a common root */
2391 if (!(len = PathCommonPrefixW(fromW, toW, 0)))
2392 return FALSE;
2394 /* Strip off 'from' components to the root, by adding "..\" */
2395 from = fromW + len;
2396 if (!*from)
2398 path[0] = '.';
2399 path[1] = '\0';
2401 if (*from == '\\')
2402 from++;
2404 while (*from)
2406 from = PathFindNextComponentW(from);
2407 lstrcatW(path, *from ? L"..\\" : L"..");
2410 /* From the root add the components of 'to' */
2411 to += len;
2412 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2413 if (*to && to[-1])
2415 if (*to != '\\')
2416 to--;
2417 len = lstrlenW(path);
2418 if (len + lstrlenW(to) >= MAX_PATH)
2420 *path = '\0';
2421 return FALSE;
2423 lstrcpyW(path + len, to);
2426 return TRUE;
2429 HRESULT WINAPI PathMatchSpecExA(const char *path, const char *mask, DWORD flags)
2431 WCHAR *pathW, *maskW;
2432 HRESULT ret;
2434 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask));
2436 if (flags)
2437 FIXME("Ignoring flags %#lx.\n", flags);
2439 if (!lstrcmpA(mask, "*.*"))
2440 return S_OK; /* Matches every path */
2442 pathW = heap_strdupAtoW( path );
2443 maskW = heap_strdupAtoW( mask );
2444 ret = PathMatchSpecExW( pathW, maskW, flags );
2445 heap_free( pathW );
2446 heap_free( maskW );
2447 return ret;
2450 BOOL WINAPI PathMatchSpecA(const char *path, const char *mask)
2452 return PathMatchSpecExA(path, mask, 0) == S_OK;
2455 static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask)
2457 while (*name && *mask && *mask != ';')
2459 if (*mask == '*')
2463 if (path_match_maskW(name, mask + 1))
2464 return TRUE; /* try substrings */
2465 } while (*name++);
2466 return FALSE;
2469 if (towupper(*mask) != towupper(*name) && *mask != '?')
2470 return FALSE;
2472 name++;
2473 mask++;
2476 if (!*name)
2478 while (*mask == '*')
2479 mask++;
2480 if (!*mask || *mask == ';')
2481 return TRUE;
2484 return FALSE;
2487 HRESULT WINAPI PathMatchSpecExW(const WCHAR *path, const WCHAR *mask, DWORD flags)
2489 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask));
2491 if (flags)
2492 FIXME("Ignoring flags %#lx.\n", flags);
2494 if (!lstrcmpW(mask, L"*.*"))
2495 return S_OK; /* Matches every path */
2497 while (*mask)
2499 while (*mask == ' ')
2500 mask++; /* Eat leading spaces */
2502 if (path_match_maskW(path, mask))
2503 return S_OK; /* Matches the current path */
2505 while (*mask && *mask != ';')
2506 mask++; /* masks separated by ';' */
2508 if (*mask == ';')
2509 mask++;
2512 return S_FALSE;
2515 BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask)
2517 return PathMatchSpecExW(path, mask, 0) == S_OK;
2520 void WINAPI PathQuoteSpacesA(char *path)
2522 TRACE("%s\n", wine_dbgstr_a(path));
2524 if (path && StrChrA(path, ' '))
2526 size_t len = strlen(path) + 1;
2528 if (len + 2 < MAX_PATH)
2530 memmove(path + 1, path, len);
2531 path[0] = '"';
2532 path[len] = '"';
2533 path[len + 1] = '\0';
2538 void WINAPI PathQuoteSpacesW(WCHAR *path)
2540 TRACE("%s\n", wine_dbgstr_w(path));
2542 if (path && StrChrW(path, ' '))
2544 int len = lstrlenW(path) + 1;
2546 if (len + 2 < MAX_PATH)
2548 memmove(path + 1, path, len * sizeof(WCHAR));
2549 path[0] = '"';
2550 path[len] = '"';
2551 path[len + 1] = '\0';
2556 BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2)
2558 const char *start;
2559 int len;
2561 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2));
2563 if (!path1 || !path2 || !(start = PathSkipRootA(path1)))
2564 return FALSE;
2566 len = PathCommonPrefixA(path1, path2, NULL) + 1;
2567 return start - path1 <= len;
2570 BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2)
2572 const WCHAR *start;
2573 int len;
2575 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2));
2577 if (!path1 || !path2 || !(start = PathSkipRootW(path1)))
2578 return FALSE;
2580 len = PathCommonPrefixW(path1, path2, NULL) + 1;
2581 return start - path1 <= len;
2584 BOOL WINAPI PathFileExistsA(const char *path)
2586 UINT prev_mode;
2587 DWORD attrs;
2589 TRACE("%s\n", wine_dbgstr_a(path));
2591 if (!path)
2592 return FALSE;
2594 /* Prevent a dialog box if path is on a disk that has been ejected. */
2595 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2596 attrs = GetFileAttributesA(path);
2597 SetErrorMode(prev_mode);
2598 return attrs != INVALID_FILE_ATTRIBUTES;
2601 BOOL WINAPI PathFileExistsW(const WCHAR *path)
2603 UINT prev_mode;
2604 DWORD attrs;
2606 TRACE("%s\n", wine_dbgstr_w(path));
2608 if (!path)
2609 return FALSE;
2611 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2612 attrs = GetFileAttributesW(path);
2613 SetErrorMode(prev_mode);
2614 return attrs != INVALID_FILE_ATTRIBUTES;
2617 int WINAPI PathParseIconLocationA(char *path)
2619 int ret = 0;
2620 char *comma;
2622 TRACE("%s\n", debugstr_a(path));
2624 if (!path)
2625 return 0;
2627 if ((comma = strchr(path, ',')))
2629 *comma++ = '\0';
2630 ret = StrToIntA(comma);
2632 PathUnquoteSpacesA(path);
2633 PathRemoveBlanksA(path);
2635 return ret;
2638 int WINAPI PathParseIconLocationW(WCHAR *path)
2640 WCHAR *comma;
2641 int ret = 0;
2643 TRACE("%s\n", debugstr_w(path));
2645 if (!path)
2646 return 0;
2648 if ((comma = StrChrW(path, ',')))
2650 *comma++ = '\0';
2651 ret = StrToIntW(comma);
2653 PathUnquoteSpacesW(path);
2654 PathRemoveBlanksW(path);
2656 return ret;
2659 BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len)
2661 WCHAR bufferW[MAX_PATH], *pathW;
2662 DWORD len;
2663 BOOL ret;
2665 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len);
2667 pathW = heap_strdupAtoW(path);
2668 if (!pathW) return FALSE;
2670 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
2671 HeapFree(GetProcessHeap(), 0, pathW);
2672 if (!ret) return FALSE;
2674 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
2675 if (buf_len < len + 1) return FALSE;
2677 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
2678 return TRUE;
2681 struct envvars_map
2683 const WCHAR *var;
2684 WCHAR path[MAX_PATH];
2685 DWORD len;
2688 static void init_envvars_map(struct envvars_map *map)
2690 while (map->var)
2692 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path));
2693 /* exclude null from length */
2694 if (map->len) map->len--;
2695 map++;
2699 BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len)
2701 static struct envvars_map null_var = {L"", {0}, 0};
2702 struct envvars_map *match = &null_var, *cur;
2703 struct envvars_map envvars[] =
2705 { L"%ALLUSERSPROFILE%" },
2706 { L"%APPDATA%" },
2707 { L"%ProgramFiles%" },
2708 { L"%SystemRoot%" },
2709 { L"%SystemDrive%" },
2710 { L"%USERPROFILE%" },
2711 { NULL }
2713 DWORD pathlen;
2714 UINT needed;
2716 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len);
2718 pathlen = lstrlenW(path);
2719 init_envvars_map(envvars);
2720 cur = envvars;
2721 while (cur->var)
2723 /* path can't contain expanded value or value wasn't retrieved */
2724 if (cur->len == 0 || cur->len > pathlen ||
2725 CompareStringOrdinal( cur->path, cur->len, path, cur->len, TRUE ) != CSTR_EQUAL)
2727 cur++;
2728 continue;
2731 if (cur->len > match->len)
2732 match = cur;
2733 cur++;
2736 needed = lstrlenW(match->var) + 1 + pathlen - match->len;
2737 if (match->len == 0 || needed > buf_len) return FALSE;
2739 lstrcpyW(buffer, match->var);
2740 lstrcatW(buffer, &path[match->len]);
2741 TRACE("ret %s\n", debugstr_w(buffer));
2743 return TRUE;
2746 static const struct
2748 URL_SCHEME scheme_number;
2749 const WCHAR *scheme_name;
2751 url_schemes[] =
2753 { URL_SCHEME_FTP, L"ftp"},
2754 { URL_SCHEME_HTTP, L"http"},
2755 { URL_SCHEME_GOPHER, L"gopher"},
2756 { URL_SCHEME_MAILTO, L"mailto"},
2757 { URL_SCHEME_NEWS, L"news"},
2758 { URL_SCHEME_NNTP, L"nntp"},
2759 { URL_SCHEME_TELNET, L"telnet"},
2760 { URL_SCHEME_WAIS, L"wais"},
2761 { URL_SCHEME_FILE, L"file"},
2762 { URL_SCHEME_MK, L"mk"},
2763 { URL_SCHEME_HTTPS, L"https"},
2764 { URL_SCHEME_SHELL, L"shell"},
2765 { URL_SCHEME_SNEWS, L"snews"},
2766 { URL_SCHEME_LOCAL, L"local"},
2767 { URL_SCHEME_JAVASCRIPT, L"javascript"},
2768 { URL_SCHEME_VBSCRIPT, L"vbscript"},
2769 { URL_SCHEME_ABOUT, L"about"},
2770 { URL_SCHEME_RES, L"res"},
2773 static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len)
2775 unsigned int i;
2777 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i)
2779 if (scheme_len == lstrlenW(url_schemes[i].scheme_name)
2780 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len))
2781 return url_schemes[i].scheme_number;
2784 return URL_SCHEME_UNKNOWN;
2787 HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result)
2789 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
2790 const char *ptr = url;
2791 int len;
2793 TRACE("%s, %p\n", wine_dbgstr_a(url), result);
2795 if (result->cbSize != sizeof(*result))
2796 return E_INVALIDARG;
2798 while (*ptr && (isalnum( *ptr ) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2799 ptr++;
2801 if (*ptr != ':' || ptr <= url + 1)
2803 result->pszProtocol = NULL;
2804 return URL_E_INVALID_SYNTAX;
2807 result->pszProtocol = url;
2808 result->cchProtocol = ptr - url;
2809 result->pszSuffix = ptr + 1;
2810 result->cchSuffix = strlen(result->pszSuffix);
2812 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme));
2813 result->nScheme = get_scheme_code(scheme, len);
2815 return S_OK;
2818 HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result)
2820 const WCHAR *ptr = url;
2822 TRACE("%s, %p\n", wine_dbgstr_w(url), result);
2824 if (result->cbSize != sizeof(*result))
2825 return E_INVALIDARG;
2827 while (*ptr && (isalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2828 ptr++;
2830 if (*ptr != ':' || ptr <= url + 1)
2832 result->pszProtocol = NULL;
2833 return URL_E_INVALID_SYNTAX;
2836 result->pszProtocol = url;
2837 result->cchProtocol = ptr - url;
2838 result->pszSuffix = ptr + 1;
2839 result->cchSuffix = lstrlenW(result->pszSuffix);
2840 result->nScheme = get_scheme_code(url, ptr - url);
2842 return S_OK;
2845 HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags)
2847 BOOL stop_unescaping = FALSE;
2848 const char *src;
2849 char *dst, next;
2850 DWORD needed;
2851 HRESULT hr;
2853 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags);
2855 if (!url)
2856 return E_INVALIDARG;
2858 if (flags & URL_UNESCAPE_INPLACE)
2859 dst = url;
2860 else
2862 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2863 dst = unescaped;
2866 for (src = url, needed = 0; *src; src++, needed++)
2868 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2870 stop_unescaping = TRUE;
2871 next = *src;
2873 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2875 INT ih;
2876 char buf[3];
2877 memcpy(buf, src + 1, 2);
2878 buf[2] = '\0';
2879 ih = strtol(buf, NULL, 16);
2880 next = (CHAR) ih;
2881 src += 2; /* Advance to end of escape */
2883 else
2884 next = *src;
2886 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2887 *dst++ = next;
2890 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2892 *dst = '\0';
2893 hr = S_OK;
2895 else
2897 needed++; /* add one for the '\0' */
2898 hr = E_POINTER;
2901 if (!(flags & URL_UNESCAPE_INPLACE))
2902 *unescaped_len = needed;
2904 if (hr == S_OK)
2905 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped));
2907 return hr;
2910 HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags)
2912 BOOL stop_unescaping = FALSE;
2913 const WCHAR *src;
2914 WCHAR *dst, next;
2915 DWORD needed;
2916 HRESULT hr;
2918 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags);
2920 if (!url)
2921 return E_INVALIDARG;
2923 if (flags & URL_UNESCAPE_INPLACE)
2924 dst = url;
2925 else
2927 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2928 dst = unescaped;
2931 for (src = url, needed = 0; *src; src++, needed++)
2933 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2935 stop_unescaping = TRUE;
2936 next = *src;
2938 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2940 INT ih;
2941 WCHAR buf[5] = L"0x";
2942 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
2943 buf[4] = 0;
2944 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
2945 next = (WCHAR) ih;
2946 src += 2; /* Advance to end of escape */
2948 else
2949 next = *src;
2951 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2952 *dst++ = next;
2955 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2957 *dst = '\0';
2958 hr = S_OK;
2960 else
2962 needed++; /* add one for the '\0' */
2963 hr = E_POINTER;
2966 if (!(flags & URL_UNESCAPE_INPLACE))
2967 *unescaped_len = needed;
2969 if (hr == S_OK)
2970 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped));
2972 return hr;
2975 HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved)
2977 WCHAR bufW[MAX_PATH];
2978 WCHAR *pathW = bufW;
2979 UNICODE_STRING urlW;
2980 HRESULT ret;
2981 DWORD lenW = ARRAY_SIZE(bufW), lenA;
2983 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
2984 return E_INVALIDARG;
2986 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
2987 return E_INVALIDARG;
2988 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
2989 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2990 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
2992 if(ret == S_OK) {
2993 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
2994 if(*pcchPath > lenA) {
2995 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
2996 pszPath[lenA] = 0;
2997 *pcchPath = lenA;
2998 } else {
2999 *pcchPath = lenA + 1;
3000 ret = E_POINTER;
3003 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
3004 RtlFreeUnicodeString(&urlW);
3005 return ret;
3008 HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved)
3010 DWORD nslashes, unescape, len;
3011 const WCHAR *src;
3012 WCHAR *tpath, *dst;
3013 HRESULT hr = S_OK;
3015 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), path, pcchPath, dwReserved);
3017 if (!url || !path || !pcchPath || !*pcchPath)
3018 return E_INVALIDARG;
3020 if (wcsnicmp( url, L"file:", 5))
3021 return E_INVALIDARG;
3023 url += 5;
3025 src = url;
3026 nslashes = 0;
3027 while (*src == '/' || *src == '\\')
3029 nslashes++;
3030 src++;
3033 /* We need a temporary buffer so we can compute what size to ask for.
3034 * We know that the final string won't be longer than the current pszUrl
3035 * plus at most two backslashes. All the other transformations make it
3036 * shorter.
3038 len = 2 + lstrlenW(url) + 1;
3039 if (*pcchPath < len)
3040 tpath = heap_alloc(len * sizeof(WCHAR));
3041 else
3042 tpath = path;
3044 len = 0;
3045 dst = tpath;
3046 unescape = 1;
3047 switch (nslashes)
3049 case 0:
3050 /* 'file:' + escaped DOS path */
3051 break;
3052 case 1:
3053 /* 'file:/' + escaped DOS path */
3054 /* fall through */
3055 case 3:
3056 /* 'file:///' (implied localhost) + escaped DOS path */
3057 if (!is_escaped_drive_spec( src ))
3058 src -= 1;
3059 break;
3060 case 2:
3061 if (lstrlenW(src) >= 10 && !wcsnicmp( src, L"localhost", 9) && (src[9] == '/' || src[9] == '\\'))
3063 /* 'file://localhost/' + escaped DOS path */
3064 src += 10;
3066 else if (is_escaped_drive_spec( src ))
3068 /* 'file://' + unescaped DOS path */
3069 unescape = 0;
3071 else
3073 /* 'file://hostname:port/path' (where path is escaped)
3074 * or 'file:' + escaped UNC path (\\server\share\path)
3075 * The second form is clearly specific to Windows and it might
3076 * even be doing a network lookup to try to figure it out.
3078 while (*src && *src != '/' && *src != '\\')
3079 src++;
3080 len = src - url;
3081 StrCpyNW(dst, url, len + 1);
3082 dst += len;
3083 if (*src && is_escaped_drive_spec( src + 1 ))
3085 /* 'Forget' to add a trailing '/', just like Windows */
3086 src++;
3089 break;
3090 case 4:
3091 /* 'file://' + unescaped UNC path (\\server\share\path) */
3092 unescape = 0;
3093 if (is_escaped_drive_spec( src ))
3094 break;
3095 /* fall through */
3096 default:
3097 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3098 src -= 2;
3101 /* Copy the remainder of the path */
3102 len += lstrlenW(src);
3103 lstrcpyW(dst, src);
3105 /* First do the Windows-specific path conversions */
3106 for (dst = tpath; *dst; dst++)
3107 if (*dst == '/') *dst = '\\';
3108 if (is_escaped_drive_spec( tpath ))
3109 tpath[1] = ':'; /* c| -> c: */
3111 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3112 if (unescape)
3114 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3115 if (hr == S_OK)
3117 /* When working in-place UrlUnescapeW() does not set len */
3118 len = lstrlenW(tpath);
3122 if (*pcchPath < len + 1)
3124 hr = E_POINTER;
3125 *pcchPath = len + 1;
3127 else
3129 *pcchPath = len;
3130 if (tpath != path)
3131 lstrcpyW(path, tpath);
3133 if (tpath != path)
3134 heap_free(tpath);
3136 TRACE("Returning (%lu) %s\n", *pcchPath, wine_dbgstr_w(path));
3137 return hr;
3140 HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved)
3142 WCHAR pathW[MAX_PATH];
3143 DWORD size;
3144 HRESULT hr;
3146 size = MAX_PATH;
3147 hr = PathCreateFromUrlW(url, pathW, &size, reserved);
3148 if (SUCCEEDED(hr))
3150 /* Yes, this is supposed to crash if 'path' is NULL */
3151 *path = StrDupW(pathW);
3154 return hr;
3157 BOOL WINAPI PathIsURLA(const char *path)
3159 PARSEDURLA base;
3160 HRESULT hr;
3162 TRACE("%s\n", wine_dbgstr_a(path));
3164 if (!path || !*path)
3165 return FALSE;
3167 /* get protocol */
3168 base.cbSize = sizeof(base);
3169 hr = ParseURLA(path, &base);
3170 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3173 BOOL WINAPI PathIsURLW(const WCHAR *path)
3175 PARSEDURLW base;
3176 HRESULT hr;
3178 TRACE("%s\n", wine_dbgstr_w(path));
3180 if (!path || !*path)
3181 return FALSE;
3183 /* get protocol */
3184 base.cbSize = sizeof(base);
3185 hr = ParseURLW(path, &base);
3186 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3189 #define WINE_URL_BASH_AS_SLASH 0x01
3190 #define WINE_URL_COLLAPSE_SLASHES 0x02
3191 #define WINE_URL_ESCAPE_SLASH 0x04
3192 #define WINE_URL_ESCAPE_HASH 0x08
3193 #define WINE_URL_ESCAPE_QUESTION 0x10
3194 #define WINE_URL_STOP_ON_HASH 0x20
3195 #define WINE_URL_STOP_ON_QUESTION 0x40
3197 static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags)
3199 if (flags & URL_ESCAPE_SPACES_ONLY)
3200 return ch == ' ';
3202 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%'))
3203 return TRUE;
3205 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80))
3206 return TRUE;
3208 if (ch <= 31 || (ch >= 127 && ch <= 255) )
3209 return TRUE;
3211 if (isalnum(ch))
3212 return FALSE;
3214 switch (ch) {
3215 case ' ':
3216 case '<':
3217 case '>':
3218 case '\"':
3219 case '{':
3220 case '}':
3221 case '|':
3222 case '\\':
3223 case '^':
3224 case ']':
3225 case '[':
3226 case '`':
3227 case '&':
3228 return TRUE;
3229 case '/':
3230 return !!(int_flags & WINE_URL_ESCAPE_SLASH);
3231 case '?':
3232 return !!(int_flags & WINE_URL_ESCAPE_QUESTION);
3233 case '#':
3234 return !!(int_flags & WINE_URL_ESCAPE_HASH);
3235 default:
3236 return FALSE;
3240 HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags)
3242 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
3243 WCHAR *escapedW = bufW;
3244 UNICODE_STRING urlW;
3245 HRESULT hr;
3246 DWORD lenW = ARRAY_SIZE(bufW), lenA;
3248 if (!escaped || !escaped_len || !*escaped_len)
3249 return E_INVALIDARG;
3251 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url))
3252 return E_INVALIDARG;
3254 if (flags & URL_ESCAPE_AS_UTF8)
3256 RtlFreeUnicodeString(&urlW);
3257 return E_NOTIMPL;
3260 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER)
3262 escapedW = heap_alloc(lenW * sizeof(WCHAR));
3263 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags);
3266 if (hr == S_OK)
3268 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
3269 if (*escaped_len > lenA)
3271 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
3272 escaped[lenA] = 0;
3273 *escaped_len = lenA;
3275 else
3277 *escaped_len = lenA + 1;
3278 hr = E_POINTER;
3281 if (escapedW != bufW)
3282 heap_free(escapedW);
3283 RtlFreeUnicodeString(&urlW);
3284 return hr;
3287 HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags)
3289 DWORD needed = 0, slashes = 0, int_flags;
3290 WCHAR next[12], *dst, *dst_ptr;
3291 BOOL stop_escaping = FALSE;
3292 PARSEDURLW parsed_url;
3293 const WCHAR *src;
3294 INT i, len;
3295 HRESULT hr;
3297 TRACE("%p, %s, %p, %p, %#lx\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags);
3299 if (!url || !escaped_len || !escaped || *escaped_len == 0)
3300 return E_INVALIDARG;
3302 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO |
3303 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8))
3305 FIXME("Unimplemented flags: %08lx\n", flags);
3308 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR));
3309 if (!dst_ptr)
3310 return E_OUTOFMEMORY;
3312 /* fix up flags */
3313 if (flags & URL_ESCAPE_SPACES_ONLY)
3314 /* if SPACES_ONLY specified, reset the other controls */
3315 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY);
3316 else
3317 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3318 flags |= URL_DONT_ESCAPE_EXTRA_INFO;
3320 int_flags = 0;
3321 if (flags & URL_ESCAPE_SEGMENT_ONLY)
3322 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
3323 else
3325 parsed_url.cbSize = sizeof(parsed_url);
3326 if (ParseURLW(url, &parsed_url) != S_OK)
3327 parsed_url.nScheme = URL_SCHEME_INVALID;
3329 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
3331 if (flags & URL_DONT_ESCAPE_EXTRA_INFO)
3332 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
3334 switch(parsed_url.nScheme) {
3335 case URL_SCHEME_FILE:
3336 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
3337 int_flags &= ~WINE_URL_STOP_ON_HASH;
3338 break;
3340 case URL_SCHEME_HTTP:
3341 case URL_SCHEME_HTTPS:
3342 int_flags |= WINE_URL_BASH_AS_SLASH;
3343 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
3344 int_flags |= WINE_URL_ESCAPE_SLASH;
3345 break;
3347 case URL_SCHEME_MAILTO:
3348 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
3349 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
3350 break;
3352 case URL_SCHEME_INVALID:
3353 break;
3355 case URL_SCHEME_FTP:
3356 default:
3357 if(parsed_url.pszSuffix[0] != '/')
3358 int_flags |= WINE_URL_ESCAPE_SLASH;
3359 break;
3363 for (src = url; *src; )
3365 WCHAR cur = *src;
3366 len = 0;
3368 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1)
3370 while (cur == '/' || cur == '\\')
3372 slashes++;
3373 cur = *++src;
3375 if (slashes == 2 && !wcsnicmp(src, L"localhost", 9)) { /* file://localhost/ -> file:/// */
3376 if(src[9] == '/' || src[9] == '\\') src += 10;
3377 slashes = 3;
3380 switch (slashes)
3382 case 1:
3383 case 3:
3384 next[0] = next[1] = next[2] = '/';
3385 len = 3;
3386 break;
3387 case 0:
3388 len = 0;
3389 break;
3390 default:
3391 next[0] = next[1] = '/';
3392 len = 2;
3393 break;
3396 if (len == 0)
3398 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
3399 stop_escaping = TRUE;
3401 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
3402 stop_escaping = TRUE;
3404 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
3406 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping)
3408 if (flags & URL_ESCAPE_AS_UTF8)
3410 char utf[16];
3412 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff))
3414 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL);
3415 src++;
3417 else
3418 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL);
3420 if (!len)
3422 utf[0] = 0xef;
3423 utf[1] = 0xbf;
3424 utf[2] = 0xbd;
3425 len = 3;
3428 for (i = 0; i < len; ++i)
3430 next[i*3+0] = '%';
3431 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf];
3432 next[i*3+2] = hexDigits[utf[i] & 0xf];
3434 len *= 3;
3436 else
3438 next[0] = '%';
3439 next[1] = hexDigits[(cur >> 4) & 0xf];
3440 next[2] = hexDigits[cur & 0xf];
3441 len = 3;
3444 else
3446 next[0] = cur;
3447 len = 1;
3449 src++;
3452 if (needed + len <= *escaped_len)
3454 memcpy(dst, next, len*sizeof(WCHAR));
3455 dst += len;
3457 needed += len;
3460 if (needed < *escaped_len)
3462 *dst = '\0';
3463 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR));
3464 hr = S_OK;
3466 else
3468 needed++; /* add one for the '\0' */
3469 hr = E_POINTER;
3471 *escaped_len = needed;
3473 heap_free(dst_ptr);
3474 return hr;
3477 HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags)
3479 LPWSTR url, canonical;
3480 HRESULT hr;
3482 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags);
3484 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3485 return E_INVALIDARG;
3487 url = heap_strdupAtoW(src_url);
3488 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR));
3489 if (!url || !canonical)
3491 heap_free(url);
3492 heap_free(canonical);
3493 return E_OUTOFMEMORY;
3496 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags);
3497 if (hr == S_OK)
3498 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL);
3500 heap_free(url);
3501 heap_free(canonical);
3502 return hr;
3505 HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags)
3507 WCHAR *url_copy, *url, *wk2, *mp, *mp2;
3508 DWORD nByteLen, nLen, nWkLen;
3509 const WCHAR *wk1, *root;
3510 DWORD escape_flags;
3511 WCHAR slash = '\0';
3512 HRESULT hr = S_OK;
3513 BOOL is_file_url;
3514 INT state;
3516 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags);
3518 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3519 return E_INVALIDARG;
3521 if (!*src_url)
3523 *canonicalized = 0;
3524 return S_OK;
3527 /* Remove '\t' characters from URL */
3528 nByteLen = (lstrlenW(src_url) + 1) * sizeof(WCHAR); /* length in bytes */
3529 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
3530 if(!url)
3531 return E_OUTOFMEMORY;
3533 wk1 = src_url;
3534 wk2 = url;
3537 while(*wk1 == '\t')
3538 wk1++;
3539 *wk2++ = *wk1;
3540 } while (*wk1++);
3542 /* Allocate memory for simplified URL (before escaping) */
3543 nByteLen = (wk2-url)*sizeof(WCHAR);
3544 url_copy = heap_alloc(nByteLen + sizeof(L"file:///"));
3545 if (!url_copy)
3547 heap_free(url);
3548 return E_OUTOFMEMORY;
3551 is_file_url = !wcsncmp(url, L"file:", 5);
3553 if ((nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"http:", 5)) || is_file_url)
3554 slash = '/';
3556 if ((flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
3557 slash = '\\';
3559 if (nByteLen >= 4*sizeof(WCHAR) && !wcsncmp(url, L"res:", 4))
3561 flags &= ~URL_FILE_USE_PATHURL;
3562 slash = '\0';
3566 * state =
3567 * 0 initial 1,3
3568 * 1 have 2[+] alnum 2,3
3569 * 2 have scheme (found :) 4,6,3
3570 * 3 failed (no location)
3571 * 4 have // 5,3
3572 * 5 have 1[+] alnum 6,3
3573 * 6 have location (found /) save root location
3576 wk1 = url;
3577 wk2 = url_copy;
3578 state = 0;
3580 /* Assume path */
3581 if (url[1] == ':')
3583 lstrcpyW(wk2, L"file:///");
3584 wk2 += lstrlenW(wk2);
3585 if (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
3587 slash = '\\';
3588 --wk2;
3590 else
3591 flags |= URL_ESCAPE_UNSAFE;
3592 state = 5;
3593 is_file_url = TRUE;
3595 else if (url[0] == '/')
3597 state = 5;
3598 is_file_url = TRUE;
3601 while (*wk1)
3603 switch (state)
3605 case 0:
3606 if (!isalnum(*wk1)) {state = 3; break;}
3607 *wk2++ = *wk1++;
3608 if (!isalnum(*wk1)) {state = 3; break;}
3609 *wk2++ = *wk1++;
3610 state = 1;
3611 break;
3612 case 1:
3613 *wk2++ = *wk1;
3614 if (*wk1++ == ':') state = 2;
3615 break;
3616 case 2:
3617 *wk2++ = *wk1++;
3618 if (*wk1 != '/') {state = 6; break;}
3619 *wk2++ = *wk1++;
3620 if ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 9*sizeof(WCHAR) && is_file_url
3621 && !wcsncmp(wk1, L"localhost", 9))
3623 wk1 += 9;
3624 while (*wk1 == '\\' && (flags & URL_FILE_USE_PATHURL))
3625 wk1++;
3628 if (*wk1 == '/' && (flags & URL_FILE_USE_PATHURL))
3629 wk1++;
3630 else if (is_file_url)
3632 const WCHAR *body = wk1;
3634 while (*body == '/')
3635 ++body;
3637 if (is_drive_spec( body ))
3639 if (!(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)))
3641 if (slash)
3642 *wk2++ = slash;
3643 else
3644 *wk2++ = '/';
3647 else
3649 if (flags & URL_WININET_COMPATIBILITY)
3651 if (*wk1 == '/' && *(wk1 + 1) != '/')
3653 *wk2++ = '\\';
3655 else
3657 *wk2++ = '\\';
3658 *wk2++ = '\\';
3661 else
3663 if (*wk1 == '/' && *(wk1+1) != '/')
3665 if (slash)
3666 *wk2++ = slash;
3667 else
3668 *wk2++ = '/';
3672 wk1 = body;
3674 state = 4;
3675 break;
3676 case 3:
3677 nWkLen = lstrlenW(wk1);
3678 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3679 mp = wk2;
3680 wk1 += nWkLen;
3681 wk2 += nWkLen;
3683 if (slash)
3685 while (mp < wk2)
3687 if (*mp == '/' || *mp == '\\')
3688 *mp = slash;
3689 mp++;
3692 break;
3693 case 4:
3694 if (!isalnum(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
3696 state = 3;
3697 break;
3699 while (isalnum(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
3700 *wk2++ = *wk1++;
3701 state = 5;
3702 if (!*wk1)
3704 if (slash)
3705 *wk2++ = slash;
3706 else
3707 *wk2++ = '/';
3709 break;
3710 case 5:
3711 if (*wk1 != '/' && *wk1 != '\\')
3713 state = 3;
3714 break;
3716 while (*wk1 == '/' || *wk1 == '\\')
3718 if (slash)
3719 *wk2++ = slash;
3720 else
3721 *wk2++ = *wk1;
3722 wk1++;
3724 state = 6;
3725 break;
3726 case 6:
3727 if (flags & URL_DONT_SIMPLIFY)
3729 state = 3;
3730 break;
3733 /* Now at root location, cannot back up any more. */
3734 /* "root" will point at the '/' */
3736 root = wk2-1;
3737 while (*wk1)
3739 mp = wcschr(wk1, '/');
3740 mp2 = wcschr(wk1, '\\');
3741 if (mp2 && (!mp || mp2 < mp))
3742 mp = mp2;
3743 if (!mp)
3745 nWkLen = lstrlenW(wk1);
3746 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3747 wk1 += nWkLen;
3748 wk2 += nWkLen;
3749 continue;
3751 nLen = mp - wk1;
3752 if (nLen)
3754 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
3755 wk2 += nLen;
3756 wk1 += nLen;
3758 if (slash)
3759 *wk2++ = slash;
3760 else
3761 *wk2++ = *wk1;
3762 wk1++;
3764 while (*wk1 == '.')
3766 TRACE("found '/.'\n");
3767 if (wk1[1] == '/' || wk1[1] == '\\')
3769 /* case of /./ -> skip the ./ */
3770 wk1 += 2;
3772 else if (wk1[1] == '.' && (wk1[2] == '/' || wk1[2] == '\\' || wk1[2] == '?'
3773 || wk1[2] == '#' || !wk1[2]))
3775 /* case /../ -> need to backup wk2 */
3776 TRACE("found '/../'\n");
3777 *(wk2-1) = '\0'; /* set end of string */
3778 mp = wcsrchr(root, '/');
3779 mp2 = wcsrchr(root, '\\');
3780 if (mp2 && (!mp || mp2 < mp))
3781 mp = mp2;
3782 if (mp && (mp >= root))
3784 /* found valid backup point */
3785 wk2 = mp + 1;
3786 if(wk1[2] != '/' && wk1[2] != '\\')
3787 wk1 += 2;
3788 else
3789 wk1 += 3;
3791 else
3793 /* did not find point, restore '/' */
3794 *(wk2-1) = slash;
3795 break;
3798 else
3799 break;
3802 *wk2 = '\0';
3803 break;
3804 default:
3805 FIXME("how did we get here - state=%d\n", state);
3806 heap_free(url_copy);
3807 heap_free(url);
3808 return E_INVALIDARG;
3810 *wk2 = '\0';
3811 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url), wine_dbgstr_w(url_copy));
3813 nLen = lstrlenW(url_copy);
3814 while ((nLen > 0) && ((url_copy[nLen-1] <= ' ')))
3815 url_copy[--nLen]=0;
3817 if ((flags & URL_UNESCAPE) ||
3818 ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"file:", 5)))
3820 UrlUnescapeW(url_copy, NULL, &nLen, URL_UNESCAPE_INPLACE);
3823 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT |
3824 URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_SEGMENT_ONLY);
3826 if (escape_flags)
3828 escape_flags &= ~URL_ESCAPE_UNSAFE;
3829 hr = UrlEscapeW(url_copy, canonicalized, canonicalized_len, escape_flags);
3831 else
3833 /* No escaping needed, just copy the string */
3834 nLen = lstrlenW(url_copy);
3835 if (nLen < *canonicalized_len)
3836 memcpy(canonicalized, url_copy, (nLen + 1)*sizeof(WCHAR));
3837 else
3839 hr = E_POINTER;
3840 nLen++;
3842 *canonicalized_len = nLen;
3845 heap_free(url_copy);
3846 heap_free(url);
3848 if (hr == S_OK)
3849 TRACE("result %s\n", wine_dbgstr_w(canonicalized));
3851 return hr;
3854 HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags)
3856 LPWSTR inW, outW;
3857 HRESULT hr;
3858 DWORD len;
3860 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags);
3862 if (!url || !out || !out_len)
3863 return E_INVALIDARG;
3865 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
3866 outW = inW + INTERNET_MAX_URL_LENGTH;
3868 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
3869 len = INTERNET_MAX_URL_LENGTH;
3871 hr = UrlApplySchemeW(inW, outW, &len, flags);
3872 if (hr != S_OK)
3874 heap_free(inW);
3875 return hr;
3878 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL);
3879 if (len > *out_len)
3881 hr = E_POINTER;
3882 goto cleanup;
3885 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL);
3886 len--;
3888 cleanup:
3889 *out_len = len;
3890 heap_free(inW);
3891 return hr;
3894 static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len)
3896 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH];
3897 DWORD value_len, data_len, dwType, i;
3898 WCHAR Wxx, Wyy;
3899 HKEY newkey;
3900 INT index;
3901 BOOL j;
3903 MultiByteToWideChar(CP_ACP, 0,
3904 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH);
3905 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
3906 index = 0;
3907 while (value_len = data_len = MAX_PATH,
3908 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0)
3910 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data));
3912 j = FALSE;
3913 for (i = 0; i < value_len; ++i)
3915 Wxx = url[i];
3916 Wyy = value[i];
3917 /* remember that TRUE is not-equal */
3918 j = ChrCmpIW(Wxx, Wyy);
3919 if (j) break;
3921 if ((i == value_len) && !j)
3923 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len)
3925 *out_len = lstrlenW(data) + lstrlenW(url) + 1;
3926 RegCloseKey(newkey);
3927 return E_POINTER;
3929 lstrcpyW(out, data);
3930 lstrcatW(out, url);
3931 *out_len = lstrlenW(out);
3932 TRACE("matched and set to %s\n", wine_dbgstr_w(out));
3933 RegCloseKey(newkey);
3934 return S_OK;
3936 index++;
3938 RegCloseKey(newkey);
3939 return E_FAIL;
3942 static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len)
3944 PARSEDURLW parsed_url;
3945 WCHAR *new_url;
3946 DWORD needed;
3947 HRESULT hr;
3949 parsed_url.cbSize = sizeof(parsed_url);
3950 if (ParseURLW(path, &parsed_url) == S_OK)
3952 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1)
3954 needed = lstrlenW(path);
3955 if (needed >= *url_len)
3957 *url_len = needed + 1;
3958 return E_POINTER;
3960 else
3962 *url_len = needed;
3963 return S_FALSE;
3968 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */
3969 lstrcpyW(new_url, L"file:");
3970 if (is_drive_spec( path )) lstrcatW(new_url, L"///");
3971 lstrcatW(new_url, path);
3972 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT);
3973 heap_free(new_url);
3974 return hr;
3977 static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length)
3979 DWORD data_len, dwType;
3980 WCHAR data[MAX_PATH];
3981 HKEY newkey;
3983 /* get and prepend default */
3984 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
3985 0, 1, &newkey);
3986 data_len = sizeof(data);
3987 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len);
3988 RegCloseKey(newkey);
3989 if (lstrlenW(data) + lstrlenW(url) + 1 > *length)
3991 *length = lstrlenW(data) + lstrlenW(url) + 1;
3992 return E_POINTER;
3994 lstrcpyW(out, data);
3995 lstrcatW(out, url);
3996 *length = lstrlenW(out);
3997 TRACE("used default %s\n", wine_dbgstr_w(out));
3998 return S_OK;
4001 HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags)
4003 PARSEDURLW in_scheme;
4004 DWORD res1;
4005 HRESULT hr;
4007 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags);
4009 if (!url || !out || !length)
4010 return E_INVALIDARG;
4012 if (flags & URL_APPLY_GUESSFILE)
4014 if ((*length > 1 && ':' == url[1]) || PathIsUNCW(url))
4016 res1 = *length;
4017 hr = url_create_from_path(url, out, &res1);
4018 if (hr == S_OK || hr == E_POINTER)
4020 *length = res1;
4021 return hr;
4023 else if (hr == S_FALSE)
4025 return hr;
4030 in_scheme.cbSize = sizeof(in_scheme);
4031 /* See if the base has a scheme */
4032 res1 = ParseURLW(url, &in_scheme);
4033 if (res1)
4035 /* no scheme in input, need to see if we need to guess */
4036 if (flags & URL_APPLY_GUESSSCHEME)
4038 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL)
4039 return hr;
4043 /* If we are here, then either invalid scheme,
4044 * or no scheme and can't/failed guess.
4046 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT))
4047 return url_apply_default_scheme(url, out, length);
4049 return S_FALSE;
4052 INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash)
4054 INT ret, len, len1, len2;
4056 if (!ignore_slash)
4057 return strcmp(url1, url2);
4058 len1 = strlen(url1);
4059 if (url1[len1-1] == '/') len1--;
4060 len2 = strlen(url2);
4061 if (url2[len2-1] == '/') len2--;
4062 if (len1 == len2)
4063 return strncmp(url1, url2, len1);
4064 len = min(len1, len2);
4065 ret = strncmp(url1, url2, len);
4066 if (ret) return ret;
4067 if (len1 > len2) return 1;
4068 return -1;
4071 INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash)
4073 size_t len, len1, len2;
4074 INT ret;
4076 if (!ignore_slash)
4077 return lstrcmpW(url1, url2);
4078 len1 = lstrlenW(url1);
4079 if (url1[len1-1] == '/') len1--;
4080 len2 = lstrlenW(url2);
4081 if (url2[len2-1] == '/') len2--;
4082 if (len1 == len2)
4083 return wcsncmp(url1, url2, len1);
4084 len = min(len1, len2);
4085 ret = wcsncmp(url1, url2, len);
4086 if (ret) return ret;
4087 if (len1 > len2) return 1;
4088 return -1;
4091 HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars)
4093 DWORD srcLen;
4095 FIXME("%s, %p, %ld stub\n", wine_dbgstr_w(url), translatedUrl, maxChars);
4097 if (!url)
4098 return E_FAIL;
4100 srcLen = lstrlenW(url) + 1;
4102 /* For now just copy the URL directly */
4103 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
4105 return S_OK;
4108 const char * WINAPI UrlGetLocationA(const char *url)
4110 PARSEDURLA base;
4112 base.cbSize = sizeof(base);
4113 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */
4115 /* if scheme is file: then never return pointer */
4116 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol)))
4117 return NULL;
4119 /* Look for '#' and return its addr */
4120 return strchr(base.pszSuffix, '#');
4123 const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url)
4125 PARSEDURLW base;
4127 base.cbSize = sizeof(base);
4128 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */
4130 /* if scheme is file: then never return pointer */
4131 if (!wcsncmp(base.pszProtocol, L"file", min(4, base.cchProtocol)))
4132 return NULL;
4134 /* Look for '#' and return its addr */
4135 return wcschr(base.pszSuffix, '#');
4138 HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags)
4140 LPWSTR inW, outW;
4141 DWORD len, len2;
4142 HRESULT hr;
4144 if (!url || !out || !out_len || !*out_len)
4145 return E_INVALIDARG;
4147 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4148 outW = inW + INTERNET_MAX_URL_LENGTH;
4150 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
4152 len = INTERNET_MAX_URL_LENGTH;
4153 hr = UrlGetPartW(inW, outW, &len, part, flags);
4154 if (FAILED(hr))
4156 heap_free(inW);
4157 return hr;
4160 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, NULL, 0, NULL, NULL);
4161 if (len2 > *out_len)
4163 *out_len = len2;
4164 heap_free(inW);
4165 return E_POINTER;
4167 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL);
4168 *out_len = len2 - 1;
4169 heap_free(inW);
4170 if (hr == S_OK && !*out_len) hr = S_FALSE;
4171 return hr;
4174 static const WCHAR *parse_scheme( const WCHAR *p )
4176 while (isalnum( *p ) || *p == '+' || *p == '-' || *p == '.')
4177 ++p;
4178 return p;
4181 static const WCHAR *parse_url_element( const WCHAR *url, const WCHAR *separators )
4183 const WCHAR *p;
4185 if ((p = wcspbrk( url, separators )))
4186 return p;
4187 return url + wcslen( url );
4190 static BOOL is_slash( char c )
4192 return c == '/' || c == '\\';
4195 static void parse_url( const WCHAR *url, struct parsed_url *pl )
4197 const WCHAR *work;
4199 memset(pl, 0, sizeof(*pl));
4200 pl->scheme = url;
4201 work = parse_scheme( pl->scheme );
4202 if (work < url + 2 || *work != ':') return;
4203 pl->scheme_len = work - pl->scheme;
4204 work++;
4205 pl->scheme_number = get_scheme_code(pl->scheme, pl->scheme_len);
4206 if (!is_slash( work[0] ) || !is_slash( work[1] ))
4208 if (pl->scheme_number != URL_SCHEME_FILE)
4209 pl->scheme_number = URL_SCHEME_UNKNOWN;
4210 return;
4212 work += 2;
4214 if (pl->scheme_number != URL_SCHEME_FILE)
4216 pl->username = work;
4217 work = parse_url_element( pl->username, L":@/\\?#" );
4218 pl->username_len = work - pl->username;
4219 if (*work == ':')
4221 pl->password = work + 1;
4222 work = parse_url_element( pl->password, L"@/\\?#" );
4223 pl->password_len = work - pl->password;
4224 if (*work == '@')
4226 work++;
4228 else
4230 /* what we just parsed must be the hostname and port
4231 * so reset pointers and clear then let it parse */
4232 pl->username_len = pl->password_len = 0;
4233 work = pl->username;
4234 pl->username = pl->password = 0;
4237 else if (*work == '@')
4239 /* no password */
4240 pl->password_len = 0;
4241 pl->password = 0;
4242 work++;
4244 else
4246 /* what was parsed was hostname, so reset pointers and let it parse */
4247 pl->username_len = pl->password_len = 0;
4248 work = pl->username;
4249 pl->username = pl->password = 0;
4253 pl->hostname = work;
4254 if (pl->scheme_number == URL_SCHEME_FILE)
4256 work = parse_url_element( pl->hostname, L"/\\?#" );
4257 pl->hostname_len = work - pl->hostname;
4258 if (pl->hostname_len >= 2 && pl->hostname[1] == ':')
4259 pl->hostname_len = 0;
4261 else
4263 work = parse_url_element( pl->hostname, L":/\\?#" );
4264 pl->hostname_len = work - pl->hostname;
4266 if (*work == ':')
4268 pl->port = work + 1;
4269 work = parse_url_element( pl->port, L"/\\?#" );
4270 pl->port_len = work - pl->port;
4274 if ((pl->query = wcschr( work, '?' )))
4276 ++pl->query;
4277 pl->query_len = lstrlenW(pl->query);
4281 HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags)
4283 LPCWSTR addr, schaddr;
4284 struct parsed_url pl;
4285 DWORD size, schsize;
4287 TRACE("%s, %p, %p(%ld), %#lx, %#lx\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags);
4289 if (!url || !out || !out_len || !*out_len)
4290 return E_INVALIDARG;
4292 parse_url(url, &pl);
4294 switch (pl.scheme_number)
4296 case URL_SCHEME_FTP:
4297 case URL_SCHEME_GOPHER:
4298 case URL_SCHEME_HTTP:
4299 case URL_SCHEME_HTTPS:
4300 case URL_SCHEME_TELNET:
4301 case URL_SCHEME_NEWS:
4302 case URL_SCHEME_NNTP:
4303 case URL_SCHEME_SNEWS:
4304 break;
4306 case URL_SCHEME_FILE:
4307 if (part != URL_PART_SCHEME && part != URL_PART_QUERY && part != URL_PART_HOSTNAME)
4308 return E_FAIL;
4309 break;
4311 default:
4312 if (part != URL_PART_SCHEME && part != URL_PART_QUERY)
4313 return E_FAIL;
4316 switch (part)
4318 case URL_PART_SCHEME:
4319 flags &= ~URL_PARTFLAG_KEEPSCHEME;
4320 addr = pl.scheme;
4321 size = pl.scheme_len;
4322 break;
4324 case URL_PART_HOSTNAME:
4325 addr = pl.hostname;
4326 size = pl.hostname_len;
4327 break;
4329 case URL_PART_USERNAME:
4330 if (!pl.username)
4331 return E_INVALIDARG;
4332 addr = pl.username;
4333 size = pl.username_len;
4334 break;
4336 case URL_PART_PASSWORD:
4337 if (!pl.password)
4338 return E_INVALIDARG;
4339 addr = pl.password;
4340 size = pl.password_len;
4341 break;
4343 case URL_PART_PORT:
4344 if (!pl.port)
4345 return E_INVALIDARG;
4346 addr = pl.port;
4347 size = pl.port_len;
4348 break;
4350 case URL_PART_QUERY:
4351 flags &= ~URL_PARTFLAG_KEEPSCHEME;
4352 addr = pl.query;
4353 size = pl.query_len;
4354 break;
4356 default:
4357 return E_INVALIDARG;
4360 if (flags == URL_PARTFLAG_KEEPSCHEME && pl.scheme_number != URL_SCHEME_FILE)
4362 if (!pl.scheme || !pl.scheme_len)
4363 return E_FAIL;
4364 schaddr = pl.scheme;
4365 schsize = pl.scheme_len;
4366 if (*out_len < schsize + size + 2)
4368 *out_len = schsize + size + 2;
4369 return E_POINTER;
4371 memcpy(out, schaddr, schsize*sizeof(WCHAR));
4372 out[schsize] = ':';
4373 memcpy(out + schsize+1, addr, size*sizeof(WCHAR));
4374 out[schsize+1+size] = 0;
4375 *out_len = schsize + 1 + size;
4377 else
4379 if (*out_len < size + 1)
4381 *out_len = size + 1;
4382 return E_POINTER;
4385 if (part == URL_PART_SCHEME)
4387 unsigned int i;
4389 for (i = 0; i < size; ++i)
4390 out[i] = tolower( addr[i] );
4392 else
4394 memcpy( out, addr, size * sizeof(WCHAR) );
4396 out[size] = 0;
4397 *out_len = size;
4399 TRACE("len=%ld %s\n", *out_len, wine_dbgstr_w(out));
4401 return S_OK;
4404 BOOL WINAPI UrlIsA(const char *url, URLIS Urlis)
4406 const char *last;
4407 PARSEDURLA base;
4409 TRACE("%s, %d\n", debugstr_a(url), Urlis);
4411 if (!url)
4412 return FALSE;
4414 switch (Urlis) {
4416 case URLIS_OPAQUE:
4417 base.cbSize = sizeof(base);
4418 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */
4419 switch (base.nScheme)
4421 case URL_SCHEME_MAILTO:
4422 case URL_SCHEME_SHELL:
4423 case URL_SCHEME_JAVASCRIPT:
4424 case URL_SCHEME_VBSCRIPT:
4425 case URL_SCHEME_ABOUT:
4426 return TRUE;
4428 return FALSE;
4430 case URLIS_FILEURL:
4431 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL);
4433 case URLIS_DIRECTORY:
4434 last = url + strlen(url) - 1;
4435 return (last >= url && (*last == '/' || *last == '\\' ));
4437 case URLIS_URL:
4438 return PathIsURLA(url);
4440 case URLIS_NOHISTORY:
4441 case URLIS_APPLIABLE:
4442 case URLIS_HASQUERY:
4443 default:
4444 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis);
4447 return FALSE;
4450 BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis)
4452 const WCHAR *last;
4453 PARSEDURLW base;
4455 TRACE("%s, %d\n", debugstr_w(url), Urlis);
4457 if (!url)
4458 return FALSE;
4460 switch (Urlis)
4462 case URLIS_OPAQUE:
4463 base.cbSize = sizeof(base);
4464 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */
4465 switch (base.nScheme)
4467 case URL_SCHEME_MAILTO:
4468 case URL_SCHEME_SHELL:
4469 case URL_SCHEME_JAVASCRIPT:
4470 case URL_SCHEME_VBSCRIPT:
4471 case URL_SCHEME_ABOUT:
4472 return TRUE;
4474 return FALSE;
4476 case URLIS_FILEURL:
4477 return !wcsnicmp( url, L"file:", 5 );
4479 case URLIS_DIRECTORY:
4480 last = url + lstrlenW(url) - 1;
4481 return (last >= url && (*last == '/' || *last == '\\'));
4483 case URLIS_URL:
4484 return PathIsURLW(url);
4486 case URLIS_NOHISTORY:
4487 case URLIS_APPLIABLE:
4488 case URLIS_HASQUERY:
4489 default:
4490 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis);
4493 return FALSE;
4496 BOOL WINAPI UrlIsOpaqueA(const char *url)
4498 return UrlIsA(url, URLIS_OPAQUE);
4501 BOOL WINAPI UrlIsOpaqueW(const WCHAR *url)
4503 return UrlIsW(url, URLIS_OPAQUE);
4506 BOOL WINAPI UrlIsNoHistoryA(const char *url)
4508 return UrlIsA(url, URLIS_NOHISTORY);
4511 BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url)
4513 return UrlIsW(url, URLIS_NOHISTORY);
4516 HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved)
4518 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
4519 DWORD lenW = ARRAY_SIZE(bufW), lenA;
4520 UNICODE_STRING pathW;
4521 WCHAR *urlW = bufW;
4522 HRESULT hr;
4524 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path))
4525 return E_INVALIDARG;
4527 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER)
4529 urlW = heap_alloc(lenW * sizeof(WCHAR));
4530 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved);
4533 if (SUCCEEDED(hr))
4535 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
4536 if (*url_len > lenA)
4538 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR));
4539 url[lenA] = 0;
4540 *url_len = lenA;
4542 else
4544 *url_len = lenA + 1;
4545 hr = E_POINTER;
4548 if (urlW != bufW)
4549 heap_free(urlW);
4550 RtlFreeUnicodeString(&pathW);
4551 return hr;
4554 HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved)
4556 HRESULT hr;
4558 TRACE("%s, %p, %p, %#lx\n", debugstr_w(path), url, url_len, reserved);
4560 if (reserved || !url || !url_len)
4561 return E_INVALIDARG;
4563 hr = url_create_from_path(path, url, url_len);
4564 if (hr == S_FALSE)
4565 lstrcpyW(url, path);
4567 return hr;
4570 HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags)
4572 WCHAR *baseW, *relativeW, *combinedW;
4573 DWORD len, len2;
4574 HRESULT hr;
4576 TRACE("%s, %s, %ld, %#lx\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags);
4578 if (!base || !relative || !combined_len)
4579 return E_INVALIDARG;
4581 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4582 relativeW = baseW + INTERNET_MAX_URL_LENGTH;
4583 combinedW = relativeW + INTERNET_MAX_URL_LENGTH;
4585 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH);
4586 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH);
4587 len = *combined_len;
4589 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags);
4590 if (hr != S_OK)
4592 *combined_len = len;
4593 heap_free(baseW);
4594 return hr;
4597 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL);
4598 if (len2 > *combined_len)
4600 *combined_len = len2;
4601 heap_free(baseW);
4602 return E_POINTER;
4604 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL);
4605 *combined_len = len2;
4606 heap_free(baseW);
4607 return S_OK;
4610 HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags)
4612 DWORD i, len, process_case = 0, myflags, sizeloc = 0;
4613 LPWSTR work, preliminary, mbase, mrelative;
4614 PARSEDURLW base, relative;
4615 HRESULT hr;
4617 TRACE("%s, %s, %ld, %#lx\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags);
4619 if (!baseW || !relativeW || !combined_len)
4620 return E_INVALIDARG;
4622 base.cbSize = sizeof(base);
4623 relative.cbSize = sizeof(relative);
4625 /* Get space for duplicates of the input and the output */
4626 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4627 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
4628 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
4629 *preliminary = '\0';
4631 /* Canonicalize the base input prior to looking for the scheme */
4632 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
4633 len = INTERNET_MAX_URL_LENGTH;
4634 UrlCanonicalizeW(baseW, mbase, &len, myflags);
4636 /* Canonicalize the relative input prior to looking for the scheme */
4637 len = INTERNET_MAX_URL_LENGTH;
4638 UrlCanonicalizeW(relativeW, mrelative, &len, myflags);
4640 /* See if the base has a scheme */
4641 if (ParseURLW(mbase, &base) != S_OK)
4643 /* If base has no scheme return relative. */
4644 TRACE("no scheme detected in Base\n");
4645 process_case = 1;
4647 else do
4649 BOOL manual_search = FALSE;
4651 work = (LPWSTR)base.pszProtocol;
4652 for (i = 0; i < base.cchProtocol; ++i)
4653 work[i] = RtlDowncaseUnicodeChar(work[i]);
4655 /* mk is a special case */
4656 if (base.nScheme == URL_SCHEME_MK)
4658 WCHAR *ptr = wcsstr(base.pszSuffix, L"::");
4659 if (ptr)
4661 int delta;
4663 ptr += 2;
4664 delta = ptr-base.pszSuffix;
4665 base.cchProtocol += delta;
4666 base.pszSuffix += delta;
4667 base.cchSuffix -= delta;
4670 else
4672 /* get size of location field (if it exists) */
4673 work = (LPWSTR)base.pszSuffix;
4674 sizeloc = 0;
4675 if (*work++ == '/')
4677 if (*work++ == '/')
4679 /* At this point have start of location and
4680 * it ends at next '/' or end of string.
4682 while (*work && (*work != '/')) work++;
4683 sizeloc = (DWORD)(work - base.pszSuffix);
4688 /* If there is a '?', then the remaining part can only contain a
4689 * query string or fragment, so start looking for the last leaf
4690 * from the '?'. Otherwise, if there is a '#' and the characters
4691 * immediately preceding it are ".htm[l]", then begin looking for
4692 * the last leaf starting from the '#'. Otherwise the '#' is not
4693 * meaningful and just start looking from the end. */
4694 if ((work = wcspbrk(base.pszSuffix + sizeloc, L"#?")))
4696 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
4697 manual_search = TRUE;
4698 else if (work - base.pszSuffix > 4)
4700 if (!wcsnicmp(work - 4, L".htm", 4)) manual_search = TRUE;
4703 if (!manual_search && work - base.pszSuffix > 5)
4705 if (!wcsnicmp(work - 5, L".html", 5)) manual_search = TRUE;
4709 if (manual_search)
4711 /* search backwards starting from the current position */
4712 while (*work != '/' && work > base.pszSuffix + sizeloc)
4713 --work;
4714 base.cchSuffix = work - base.pszSuffix + 1;
4716 else
4718 /* search backwards starting from the end of the string */
4719 work = wcsrchr((base.pszSuffix+sizeloc), '/');
4720 if (work)
4722 len = (DWORD)(work - base.pszSuffix + 1);
4723 base.cchSuffix = len;
4725 else
4726 base.cchSuffix = sizeloc;
4730 * At this point:
4731 * .pszSuffix points to location (starting with '//')
4732 * .cchSuffix length of location (above) and rest less the last
4733 * leaf (if any)
4734 * sizeloc length of location (above) up to but not including
4735 * the last '/'
4738 if (ParseURLW(mrelative, &relative) != S_OK)
4740 /* No scheme in relative */
4741 TRACE("no scheme detected in Relative\n");
4742 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
4743 relative.cchSuffix = lstrlenW(mrelative);
4744 if (*relativeW == ':')
4746 /* Case that is either left alone or uses base. */
4747 if (flags & URL_PLUGGABLE_PROTOCOL)
4749 process_case = 5;
4750 break;
4752 process_case = 1;
4753 break;
4755 if (is_drive_spec( mrelative ))
4757 /* case that becomes "file:///" */
4758 lstrcpyW(preliminary, L"file:///");
4759 process_case = 1;
4760 break;
4762 if (*mrelative == '/' && *(mrelative+1) == '/')
4764 /* Relative has location and the rest. */
4765 process_case = 3;
4766 break;
4768 if (*mrelative == '/')
4770 /* Relative is root to location. */
4771 process_case = 4;
4772 break;
4774 if (*mrelative == '#')
4776 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#')))
4777 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix);
4779 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
4780 preliminary[work-base.pszProtocol] = '\0';
4781 process_case = 1;
4782 break;
4784 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
4785 break;
4787 else
4789 work = (LPWSTR)relative.pszProtocol;
4790 for (i = 0; i < relative.cchProtocol; ++i)
4791 work[i] = RtlDowncaseUnicodeChar(work[i]);
4794 /* Handle cases where relative has scheme. */
4795 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol))
4797 /* since the schemes are the same */
4798 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4800 /* Relative replaces location and what follows. */
4801 process_case = 3;
4802 break;
4804 if (*relative.pszSuffix == '/')
4806 /* Relative is root to location */
4807 process_case = 4;
4808 break;
4810 /* replace either just location if base's location starts with a
4811 * slash or otherwise everything */
4812 process_case = (*base.pszSuffix == '/') ? 5 : 1;
4813 break;
4816 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4818 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4819 process_case = 2;
4820 break;
4822 process_case = 1;
4823 break;
4824 } while (FALSE); /* a little trick to allow easy exit from nested if's */
4826 hr = S_OK;
4827 switch (process_case)
4829 case 1:
4830 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4831 lstrcatW(preliminary, mrelative);
4832 break;
4834 case 2:
4835 /* Relative replaces scheme and location */
4836 lstrcpyW(preliminary, mrelative);
4837 break;
4839 case 3:
4840 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4841 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
4842 work = preliminary + base.cchProtocol + 1;
4843 lstrcpyW(work, relative.pszSuffix);
4844 break;
4846 case 4:
4847 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4848 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
4849 work = preliminary + base.cchProtocol + 1 + sizeloc;
4850 if (flags & URL_PLUGGABLE_PROTOCOL)
4851 *(work++) = '/';
4852 lstrcpyW(work, relative.pszSuffix);
4853 break;
4855 case 5:
4856 /* Return the base without its document (if any) and append relative after its scheme. */
4857 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR));
4858 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1;
4859 if (*work++ != '/')
4860 *(work++) = '/';
4861 lstrcpyW(work, relative.pszSuffix);
4862 break;
4864 default:
4865 FIXME("Unexpected case %ld.\n", process_case);
4866 hr = E_INVALIDARG;
4869 if (hr == S_OK)
4871 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
4872 if (*combined_len == 0)
4873 *combined_len = 1;
4874 hr = UrlCanonicalizeW(preliminary, mrelative, combined_len, flags & ~URL_FILE_USE_PATHURL);
4875 if (SUCCEEDED(hr) && combined)
4876 lstrcpyW(combined, mrelative);
4878 TRACE("return-%ld len=%ld, %s\n", process_case, *combined_len, debugstr_w(combined));
4881 heap_free(preliminary);
4882 return hr;
4885 HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len)
4887 INT src_count = src_len - 1, dest_count = dest_len - 1;
4889 if (!src || !dest)
4890 return E_INVALIDARG;
4892 while (dest_count >= 0)
4894 dest[dest_count] = (dest_count & 0xff);
4895 dest_count--;
4898 while (src_count >= 0)
4900 dest_count = dest_len - 1;
4901 while (dest_count >= 0)
4903 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]];
4904 dest_count--;
4906 src_count--;
4909 return S_OK;
4912 HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len)
4914 __TRY
4916 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len);
4918 __EXCEPT_PAGE_FAULT
4920 return E_INVALIDARG;
4922 __ENDTRY
4923 return S_OK;
4926 HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len)
4928 char urlA[MAX_PATH];
4930 TRACE("%s, %p, %ld\n", debugstr_w(url), dest, dest_len);
4932 __TRY
4934 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL);
4935 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len);
4937 __EXCEPT_PAGE_FAULT
4939 return E_INVALIDARG;
4941 __ENDTRY
4942 return S_OK;
4945 BOOL WINAPI IsInternetESCEnabled(void)
4947 FIXME(": stub\n");
4948 return FALSE;