tests: Don't initialize static variables to 0.
[wine.git] / dlls / kernelbase / path.c
blob9b016a882ea0e4c089e661c630e2c27949291ef9
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 #define iswalnum(ch) (iswctype((ch), C1_ALPHA|C1_DIGIT|C1_LOWER|C1_UPPER))
40 #define isxdigit(ch) (((ch) >= '0' && (ch) <= '9') || \
41 ((ch) >= 'A' && (ch) <= 'F') || \
42 ((ch) >= 'a' && (ch) <= 'f'))
44 static const char hexDigits[] = "0123456789ABCDEF";
46 static const unsigned char hashdata_lookup[256] =
48 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33,
49 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41,
50 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c,
51 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90,
52 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe,
53 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd,
54 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d,
55 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd,
56 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2,
57 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b,
58 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70,
59 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b,
60 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47,
61 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d,
62 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8,
63 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1,
66 struct parsed_url
68 const WCHAR *scheme; /* [out] start of scheme */
69 DWORD scheme_len; /* [out] size of scheme (until colon) */
70 const WCHAR *username; /* [out] start of Username */
71 DWORD username_len; /* [out] size of Username (until ":" or "@") */
72 const WCHAR *password; /* [out] start of Password */
73 DWORD password_len; /* [out] size of Password (until "@") */
74 const WCHAR *hostname; /* [out] start of Hostname */
75 DWORD hostname_len; /* [out] size of Hostname (until ":" or "/") */
76 const WCHAR *port; /* [out] start of Port */
77 DWORD port_len; /* [out] size of Port (until "/" or eos) */
78 const WCHAR *query; /* [out] start of Query */
79 DWORD query_len; /* [out] size of Query (until eos) */
82 enum url_scan_type
84 SCHEME,
85 HOST,
86 PORT,
87 USERPASS,
90 static WCHAR *heap_strdupAtoW(const char *str)
92 WCHAR *ret = NULL;
94 if (str)
96 DWORD len;
98 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
99 ret = heap_alloc(len * sizeof(WCHAR));
100 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
103 return ret;
106 static SIZE_T strnlenW(const WCHAR *string, SIZE_T maxlen)
108 SIZE_T i;
110 for (i = 0; i < maxlen; i++)
111 if (!string[i]) break;
112 return i;
115 static BOOL is_prefixed_unc(const WCHAR *string)
117 static const WCHAR prefixed_unc[] = {'\\', '\\', '?', '\\', 'U', 'N', 'C', '\\'};
118 return !wcsnicmp(string, prefixed_unc, ARRAY_SIZE(prefixed_unc));
121 static BOOL is_prefixed_disk(const WCHAR *string)
123 static const WCHAR prefix[] = {'\\', '\\', '?', '\\'};
124 return !wcsncmp(string, prefix, ARRAY_SIZE(prefix)) && iswalpha(string[4]) && string[5] == ':';
127 static BOOL is_prefixed_volume(const WCHAR *string)
129 static const WCHAR prefixed_volume[] = {'\\', '\\', '?', '\\', 'V', 'o', 'l', 'u', 'm', 'e'};
130 const WCHAR *guid;
131 INT i = 0;
133 if (wcsnicmp(string, prefixed_volume, ARRAY_SIZE(prefixed_volume))) return FALSE;
135 guid = string + ARRAY_SIZE(prefixed_volume);
137 while (i <= 37)
139 switch (i)
141 case 0:
142 if (guid[i] != '{') return FALSE;
143 break;
144 case 9:
145 case 14:
146 case 19:
147 case 24:
148 if (guid[i] != '-') return FALSE;
149 break;
150 case 37:
151 if (guid[i] != '}') return FALSE;
152 break;
153 default:
154 if (!iswxdigit(guid[i])) return FALSE;
155 break;
157 i++;
160 return TRUE;
163 /* Get the next character beyond end of the segment.
164 Return TRUE if the last segment ends with a backslash */
165 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment)
167 while (*next && *next != '\\') next++;
168 if (*next == '\\')
170 *next_segment = next + 1;
171 return TRUE;
173 else
175 *next_segment = next;
176 return FALSE;
180 /* Find the last character of the root in a path, if there is one, without any segments */
181 static const WCHAR *get_root_end(const WCHAR *path)
183 /* Find path root */
184 if (is_prefixed_volume(path))
185 return path[48] == '\\' ? path + 48 : path + 47;
186 else if (is_prefixed_unc(path))
187 return path + 7;
188 else if (is_prefixed_disk(path))
189 return path[6] == '\\' ? path + 6 : path + 5;
190 /* \\ */
191 else if (path[0] == '\\' && path[1] == '\\')
192 return path + 1;
193 /* \ */
194 else if (path[0] == '\\')
195 return path;
196 /* X:\ */
197 else if (iswalpha(path[0]) && path[1] == ':')
198 return path[2] == '\\' ? path + 2 : path + 1;
199 else
200 return NULL;
203 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
205 WCHAR *buffer, *dst;
206 const WCHAR *src;
207 const WCHAR *root_end;
208 SIZE_T buffer_size, length;
210 TRACE("%s %#x %p\n", debugstr_w(path_in), flags, path_out);
212 if (!path_in || !path_out
213 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS))
214 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)
215 && !(flags & PATHCCH_ALLOW_LONG_PATHS))
216 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS)))
218 if (path_out) *path_out = NULL;
219 return E_INVALIDARG;
222 length = lstrlenW(path_in);
223 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)))
224 || (length + 1 > PATHCCH_MAX_CCH))
226 *path_out = NULL;
227 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
230 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
231 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
233 /* path length + possible \\?\ addition + possible \ addition + NUL */
234 buffer_size = (length + 6) * sizeof(WCHAR);
235 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size);
236 if (!buffer)
238 *path_out = NULL;
239 return E_OUTOFMEMORY;
242 src = path_in;
243 dst = buffer;
245 root_end = get_root_end(path_in);
246 if (root_end) root_end = buffer + (root_end - path_in);
248 /* Copy path root */
249 if (root_end)
251 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR));
252 src += root_end - buffer + 1;
253 if(PathCchStripPrefix(dst, length + 6) == S_OK)
255 /* Fill in \ in X:\ if the \ is missing */
256 if(iswalpha(dst[0]) && dst[1] == ':' && dst[2]!= '\\')
258 dst[2] = '\\';
259 dst[3] = 0;
261 dst = buffer + lstrlenW(buffer);
262 root_end = dst;
264 else
265 dst += root_end - buffer + 1;
268 while (*src)
270 if (src[0] == '.')
272 if (src[1] == '.')
274 /* Keep one . after * */
275 if (dst > buffer && dst[-1] == '*')
277 *dst++ = *src++;
278 continue;
281 /* Keep the .. if not surrounded by \ */
282 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\'))
284 *dst++ = *src++;
285 *dst++ = *src++;
286 continue;
289 /* Remove the \ before .. if the \ is not part of root */
290 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
292 *--dst = '\0';
293 /* Remove characters until a \ is encountered */
294 while (dst > buffer)
296 if (dst[-1] == '\\')
298 *--dst = 0;
299 break;
301 else
302 *--dst = 0;
305 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
306 else if (src[2] == '\\')
307 src++;
309 src += 2;
311 else
313 /* Keep the . if not surrounded by \ */
314 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\'))
316 *dst++ = *src++;
317 continue;
320 /* Remove the \ before . if the \ is not part of root */
321 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--;
322 /* Remove the extra \ after . if the \ before . wasn't deleted */
323 else if (src[1] == '\\')
324 src++;
326 src++;
329 /* If X:\ is not complete, then complete it */
330 if (iswalpha(buffer[0]) && buffer[1] == ':' && buffer[2] != '\\')
332 root_end = buffer + 2;
333 dst = buffer + 3;
334 buffer[2] = '\\';
335 /* If next character is \, use the \ to fill in */
336 if (src[0] == '\\') src++;
339 /* Copy over */
340 else
341 *dst++ = *src++;
343 /* End the path */
344 *dst = 0;
346 /* Strip multiple trailing . */
347 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
349 while (dst > buffer && dst[-1] == '.')
351 /* Keep a . after * */
352 if (dst - 1 > buffer && dst[-2] == '*')
353 break;
354 /* If . follow a : at the second character, remove the . and add a \ */
355 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1)
356 *--dst = '\\';
357 else
358 *--dst = 0;
362 /* If result path is empty, fill in \ */
363 if (!*buffer)
365 buffer[0] = '\\';
366 buffer[1] = 0;
369 /* Extend the path if needed */
370 length = lstrlenW(buffer);
371 if (((length + 1 > MAX_PATH && iswalpha(buffer[0]) && buffer[1] == ':')
372 || (iswalpha(buffer[0]) && buffer[1] == ':' && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))
373 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS))
375 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR));
376 buffer[0] = '\\';
377 buffer[1] = '\\';
378 buffer[2] = '?';
379 buffer[3] = '\\';
382 /* Add a trailing backslash to the path if needed */
383 if (flags & PATHCCH_ENSURE_TRAILING_SLASH)
384 PathCchAddBackslash(buffer, buffer_size);
386 *path_out = buffer;
387 return S_OK;
390 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out)
392 SIZE_T combined_length, length2;
393 WCHAR *combined_path;
394 BOOL from_path2 = FALSE;
395 HRESULT hr;
397 TRACE("%s %s %#x %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out);
399 if ((!path1 && !path2) || !out)
401 if (out) *out = NULL;
402 return E_INVALIDARG;
405 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out);
407 /* If path2 is fully qualified, use path2 only */
408 if ((iswalpha(path2[0]) && path2[1] == ':') || (path2[0] == '\\' && path2[1] == '\\'))
410 path1 = path2;
411 path2 = NULL;
412 from_path2 = TRUE;
415 length2 = path2 ? lstrlenW(path2) : 0;
416 /* path1 length + path2 length + possible backslash + NULL */
417 combined_length = lstrlenW(path1) + length2 + 2;
419 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR));
420 if (!combined_path)
422 *out = NULL;
423 return E_OUTOFMEMORY;
426 lstrcpyW(combined_path, path1);
427 PathCchStripPrefix(combined_path, combined_length);
428 if (from_path2) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
430 if (path2 && path2[0])
432 if (path2[0] == '\\' && path2[1] != '\\')
434 PathCchStripToRoot(combined_path, combined_length);
435 path2++;
438 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
439 lstrcatW(combined_path, path2);
442 hr = PathAllocCanonicalize(combined_path, flags, out);
443 HeapFree(GetProcessHeap(), 0, combined_path);
444 return hr;
447 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size)
449 return PathCchAddBackslashEx(path, size, NULL, NULL);
452 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining)
454 BOOL needs_termination;
455 SIZE_T length;
457 TRACE("%s, %lu, %p, %p\n", debugstr_w(path), size, endptr, remaining);
459 length = lstrlenW(path);
460 needs_termination = size && length && path[length - 1] != '\\';
462 if (length >= (needs_termination ? size - 1 : size))
464 if (endptr) *endptr = NULL;
465 if (remaining) *remaining = 0;
466 return STRSAFE_E_INSUFFICIENT_BUFFER;
469 if (!needs_termination)
471 if (endptr) *endptr = path + length;
472 if (remaining) *remaining = size - length;
473 return S_FALSE;
476 path[length++] = '\\';
477 path[length] = 0;
479 if (endptr) *endptr = path + length;
480 if (remaining) *remaining = size - length;
482 return S_OK;
485 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
487 const WCHAR *existing_extension, *next;
488 SIZE_T path_length, extension_length, dot_length;
489 BOOL has_dot;
490 HRESULT hr;
492 TRACE("%s %lu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
494 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG;
496 next = extension;
497 while (*next)
499 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG;
500 next++;
503 has_dot = extension[0] == '.';
505 hr = PathCchFindExtension(path, size, &existing_extension);
506 if (FAILED(hr)) return hr;
507 if (*existing_extension) return S_FALSE;
509 path_length = strnlenW(path, size);
510 dot_length = has_dot ? 0 : 1;
511 extension_length = lstrlenW(extension);
513 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER;
515 /* If extension is empty or only dot, return S_OK with path unchanged */
516 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK;
518 if (!has_dot)
520 path[path_length] = '.';
521 path_length++;
524 lstrcpyW(path + path_length, extension);
525 return S_OK;
528 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2)
530 TRACE("%s %lu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2));
532 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE);
535 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags)
537 HRESULT hr;
538 WCHAR *result;
540 TRACE("%s %lu %s %#x\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags);
542 if (!path1 || !size) return E_INVALIDARG;
544 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
545 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
546 * buffer for PathCchCombineEx */
547 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
548 if (!result) return E_OUTOFMEMORY;
550 /* Avoid the single backslash behavior with PathCchCombineEx when appending */
551 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++;
553 hr = PathCchCombineEx(result, size, path1, path2, flags);
554 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR));
556 HeapFree(GetProcessHeap(), 0, result);
557 return hr;
560 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in)
562 TRACE("%p %lu %s\n", out, size, wine_dbgstr_w(in));
564 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
565 if (lstrlenW(in) > MAX_PATH - 4 && !(iswalpha(in[0]) && in[1] == ':' && in[2] == '\\'))
566 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
568 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE);
571 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags)
573 WCHAR *buffer;
574 SIZE_T length;
575 HRESULT hr;
577 TRACE("%p %lu %s %#x\n", out, size, wine_dbgstr_w(in), flags);
579 if (!size) return E_INVALIDARG;
581 hr = PathAllocCanonicalize(in, flags, &buffer);
582 if (FAILED(hr)) return hr;
584 length = lstrlenW(buffer);
585 if (size < length + 1)
587 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
588 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (iswalpha(in[0]) && in[1] == ':' && in[2] == '\\')))
589 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
590 else
591 hr = STRSAFE_E_INSUFFICIENT_BUFFER;
594 if (SUCCEEDED(hr))
596 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
598 /* Fill a backslash at the end of X: */
599 if (iswalpha(out[0]) && out[1] == ':' && !out[2] && size > 3)
601 out[2] = '\\';
602 out[3] = 0;
606 LocalFree(buffer);
607 return hr;
610 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2)
612 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2));
614 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE);
617 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags)
619 HRESULT hr;
620 WCHAR *buffer;
621 SIZE_T length;
623 TRACE("%p %s %s %#x\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags);
625 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
627 hr = PathAllocCombine(path1, path2, flags, &buffer);
628 if (FAILED(hr))
630 out[0] = 0;
631 return hr;
634 length = lstrlenW(buffer);
635 if (length + 1 > size)
637 out[0] = 0;
638 LocalFree(buffer);
639 return STRSAFE_E_INSUFFICIENT_BUFFER;
641 else
643 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
644 LocalFree(buffer);
645 return S_OK;
649 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension)
651 const WCHAR *lastpoint = NULL;
652 SIZE_T counter = 0;
654 TRACE("%s %lu %p\n", wine_dbgstr_w(path), size, extension);
656 if (!path || !size || size > PATHCCH_MAX_CCH)
658 *extension = NULL;
659 return E_INVALIDARG;
662 while (*path)
664 if (*path == '\\' || *path == ' ')
665 lastpoint = NULL;
666 else if (*path == '.')
667 lastpoint = path;
669 path++;
670 counter++;
671 if (counter == size || counter == PATHCCH_MAX_CCH)
673 *extension = NULL;
674 return E_INVALIDARG;
678 *extension = lastpoint ? lastpoint : path;
679 return S_OK;
682 BOOL WINAPI PathCchIsRoot(const WCHAR *path)
684 const WCHAR *root_end;
685 const WCHAR *next;
686 BOOL is_unc;
688 TRACE("%s\n", wine_dbgstr_w(path));
690 if (!path || !*path) return FALSE;
692 root_end = get_root_end(path);
693 if (!root_end) return FALSE;
695 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
697 next = root_end + 1;
698 /* No extra segments */
699 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE;
701 /* Has first segment with an ending backslash but no remaining characters */
702 if (get_next_segment(next, &next) && !*next) return FALSE;
703 /* Has first segment with no ending backslash */
704 else if (!*next)
705 return TRUE;
706 /* Has first segment with an ending backslash and has remaining characters*/
707 else
709 next++;
710 /* Second segment must have no backslash and no remaining characters */
711 return !get_next_segment(next, &next) && !*next;
714 else if (*root_end == '\\' && !root_end[1])
715 return TRUE;
716 else
717 return FALSE;
720 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size)
722 WCHAR *path_end;
723 SIZE_T free_size;
725 TRACE("%s %lu\n", debugstr_w(path), path_size);
727 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size);
730 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size)
732 const WCHAR *root_end;
733 SIZE_T path_length;
735 TRACE("%s %lu %p %p\n", debugstr_w(path), path_size, path_end, free_size);
737 if (!path_size || !path_end || !free_size)
739 if (path_end) *path_end = NULL;
740 if (free_size) *free_size = 0;
741 return E_INVALIDARG;
744 path_length = strnlenW(path, path_size);
745 if (path_length == path_size && !path[path_length]) return E_INVALIDARG;
747 root_end = get_root_end(path);
748 if (path_length > 0 && path[path_length - 1] == '\\')
750 *path_end = path + path_length - 1;
751 *free_size = path_size - path_length + 1;
752 /* If the last character is beyond end of root */
753 if (!root_end || path + path_length - 1 > root_end)
755 path[path_length - 1] = 0;
756 return S_OK;
758 else
759 return S_FALSE;
761 else
763 *path_end = path + path_length;
764 *free_size = path_size - path_length;
765 return S_FALSE;
769 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size)
771 const WCHAR *extension;
772 WCHAR *next;
773 HRESULT hr;
775 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
777 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
779 hr = PathCchFindExtension(path, size, &extension);
780 if (FAILED(hr)) return hr;
782 next = path + (extension - path);
783 while (next - path < size && *next) *next++ = 0;
785 return next == extension ? S_FALSE : S_OK;
788 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size)
790 const WCHAR *root_end = NULL;
791 SIZE_T length;
792 WCHAR *last;
794 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
796 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
798 if (PathCchIsRoot(path)) return S_FALSE;
800 PathCchSkipRoot(path, &root_end);
802 /* The backslash at the end of UNC and \\* are not considered part of root in this case */
803 if (root_end && root_end > path && root_end[-1] == '\\'
804 && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')))
805 root_end--;
807 length = lstrlenW(path);
808 last = path + length - 1;
809 while (last >= path && (!root_end || last >= root_end))
811 if (last - path >= size) return E_INVALIDARG;
813 if (*last == '\\')
815 *last-- = 0;
816 break;
819 *last-- = 0;
822 return last != path + length - 1 ? S_OK : S_FALSE;
825 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
827 HRESULT hr;
829 TRACE("%s %lu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
831 hr = PathCchRemoveExtension(path, size);
832 if (FAILED(hr)) return hr;
834 hr = PathCchAddExtension(path, size, extension);
835 return FAILED(hr) ? hr : S_OK;
838 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end)
840 static const WCHAR unc_prefix[] = {'\\', '\\', '?'};
842 TRACE("%s %p\n", debugstr_w(path), root_end);
844 if (!path || !path[0] || !root_end
845 || (!wcsnicmp(unc_prefix, path, ARRAY_SIZE(unc_prefix)) && !is_prefixed_volume(path) && !is_prefixed_unc(path)
846 && !is_prefixed_disk(path)))
847 return E_INVALIDARG;
849 *root_end = get_root_end(path);
850 if (*root_end)
852 (*root_end)++;
853 if (is_prefixed_unc(path))
855 get_next_segment(*root_end, root_end);
856 get_next_segment(*root_end, root_end);
858 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
860 /* Skip share server */
861 get_next_segment(*root_end, root_end);
862 /* If mount point is empty, don't skip over mount point */
863 if (**root_end != '\\') get_next_segment(*root_end, root_end);
867 return *root_end ? S_OK : E_INVALIDARG;
870 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size)
872 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
874 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
876 if (is_prefixed_unc(path))
878 /* \\?\UNC\a -> \\a */
879 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG;
880 lstrcpyW(path + 2, path + 8);
881 return S_OK;
883 else if (is_prefixed_disk(path))
885 /* \\?\C:\ -> C:\ */
886 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG;
887 lstrcpyW(path, path + 4);
888 return S_OK;
890 else
891 return S_FALSE;
894 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size)
896 const WCHAR *root_end;
897 WCHAR *segment_end;
898 BOOL is_unc;
900 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
902 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
904 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
905 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
906 * \\\\a\\b\\c -> \\\\a\\b */
907 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
909 root_end = is_unc ? path + 8 : path + 3;
910 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
911 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
913 if (root_end - path >= size) return E_INVALIDARG;
915 segment_end = path + (root_end - path) - 1;
916 *segment_end = 0;
917 return S_OK;
919 else if (PathCchSkipRoot(path, &root_end) == S_OK)
921 if (root_end - path >= size) return E_INVALIDARG;
923 segment_end = path + (root_end - path);
924 if (!*segment_end) return S_FALSE;
926 *segment_end = 0;
927 return S_OK;
929 else
930 return E_INVALIDARG;
933 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server)
935 const WCHAR *result = NULL;
937 TRACE("%s %p\n", wine_dbgstr_w(path), server);
939 if (is_prefixed_unc(path))
940 result = path + 8;
941 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
942 result = path + 2;
944 if (server) *server = result;
945 return !!result;
948 BOOL WINAPI PathIsUNCA(const char *path)
950 TRACE("%s\n", wine_dbgstr_a(path));
952 return path && (path[0] == '\\') && (path[1] == '\\');
955 BOOL WINAPI PathIsUNCW(const WCHAR *path)
957 TRACE("%s\n", wine_dbgstr_w(path));
959 return path && (path[0] == '\\') && (path[1] == '\\');
962 BOOL WINAPI PathIsRelativeA(const char *path)
964 TRACE("%s\n", wine_dbgstr_a(path));
966 if (!path || !*path || IsDBCSLeadByte(*path))
967 return TRUE;
969 return !(*path == '\\' || (*path && path[1] == ':'));
972 BOOL WINAPI PathIsRelativeW(const WCHAR *path)
974 TRACE("%s\n", wine_dbgstr_w(path));
976 if (!path || !*path)
977 return TRUE;
979 return !(*path == '\\' || (*path && path[1] == ':'));
982 BOOL WINAPI PathIsUNCServerShareA(const char *path)
984 BOOL seen_slash = FALSE;
986 TRACE("%s\n", wine_dbgstr_a(path));
988 if (path && *path++ == '\\' && *path++ == '\\')
990 while (*path)
992 if (*path == '\\')
994 if (seen_slash)
995 return FALSE;
996 seen_slash = TRUE;
999 path = CharNextA(path);
1003 return seen_slash;
1006 BOOL WINAPI PathIsUNCServerShareW(const WCHAR *path)
1008 BOOL seen_slash = FALSE;
1010 TRACE("%s\n", wine_dbgstr_w(path));
1012 if (path && *path++ == '\\' && *path++ == '\\')
1014 while (*path)
1016 if (*path == '\\')
1018 if (seen_slash)
1019 return FALSE;
1020 seen_slash = TRUE;
1023 path++;
1027 return seen_slash;
1030 BOOL WINAPI PathIsRootA(const char *path)
1032 TRACE("%s\n", wine_dbgstr_a(path));
1034 if (!path || !*path)
1035 return FALSE;
1037 if (*path == '\\')
1039 if (!path[1])
1040 return TRUE; /* \ */
1041 else if (path[1] == '\\')
1043 BOOL seen_slash = FALSE;
1044 path += 2;
1046 /* Check for UNC root path */
1047 while (*path)
1049 if (*path == '\\')
1051 if (seen_slash)
1052 return FALSE;
1053 seen_slash = TRUE;
1056 path = CharNextA(path);
1059 return TRUE;
1062 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1063 return TRUE; /* X:\ */
1065 return FALSE;
1068 BOOL WINAPI PathIsRootW(const WCHAR *path)
1070 TRACE("%s\n", wine_dbgstr_w(path));
1072 if (!path || !*path)
1073 return FALSE;
1075 if (*path == '\\')
1077 if (!path[1])
1078 return TRUE; /* \ */
1079 else if (path[1] == '\\')
1081 BOOL seen_slash = FALSE;
1083 path += 2;
1084 /* Check for UNC root path */
1085 while (*path)
1087 if (*path == '\\')
1089 if (seen_slash)
1090 return FALSE;
1091 seen_slash = TRUE;
1093 path++;
1096 return TRUE;
1099 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1100 return TRUE; /* X:\ */
1102 return FALSE;
1105 BOOL WINAPI PathRemoveFileSpecA(char *path)
1107 char *filespec = path;
1108 BOOL modified = FALSE;
1110 TRACE("%s\n", wine_dbgstr_a(path));
1112 if (!path)
1113 return FALSE;
1115 /* Skip directory or UNC path */
1116 if (*path == '\\')
1117 filespec = ++path;
1118 if (*path == '\\')
1119 filespec = ++path;
1121 while (*path)
1123 if (*path == '\\')
1124 filespec = path; /* Skip dir */
1125 else if (*path == ':')
1127 filespec = ++path; /* Skip drive */
1128 if (*path == '\\')
1129 filespec++;
1131 if (!(path = CharNextA(path)))
1132 break;
1135 if (*filespec)
1137 *filespec = '\0';
1138 modified = TRUE;
1141 return modified;
1144 BOOL WINAPI PathRemoveFileSpecW(WCHAR *path)
1146 WCHAR *filespec = path;
1147 BOOL modified = FALSE;
1149 TRACE("%s\n", wine_dbgstr_w(path));
1151 if (!path)
1152 return FALSE;
1154 /* Skip directory or UNC path */
1155 if (*path == '\\')
1156 filespec = ++path;
1157 if (*path == '\\')
1158 filespec = ++path;
1160 while (*path)
1162 if (*path == '\\')
1163 filespec = path; /* Skip dir */
1164 else if (*path == ':')
1166 filespec = ++path; /* Skip drive */
1167 if (*path == '\\')
1168 filespec++;
1171 path++;
1174 if (*filespec)
1176 *filespec = '\0';
1177 modified = TRUE;
1180 return modified;
1183 BOOL WINAPI PathStripToRootA(char *path)
1185 TRACE("%s\n", wine_dbgstr_a(path));
1187 if (!path)
1188 return FALSE;
1190 while (!PathIsRootA(path))
1191 if (!PathRemoveFileSpecA(path))
1192 return FALSE;
1194 return TRUE;
1197 BOOL WINAPI PathStripToRootW(WCHAR *path)
1199 TRACE("%s\n", wine_dbgstr_w(path));
1201 if (!path)
1202 return FALSE;
1204 while (!PathIsRootW(path))
1205 if (!PathRemoveFileSpecW(path))
1206 return FALSE;
1208 return TRUE;
1211 LPSTR WINAPI PathAddBackslashA(char *path)
1213 unsigned int len;
1214 char *prev = path;
1216 TRACE("%s\n", wine_dbgstr_a(path));
1218 if (!path || (len = strlen(path)) >= MAX_PATH)
1219 return NULL;
1221 if (len)
1225 path = CharNextA(prev);
1226 if (*path)
1227 prev = path;
1228 } while (*path);
1230 if (*prev != '\\')
1232 *path++ = '\\';
1233 *path = '\0';
1237 return path;
1240 LPWSTR WINAPI PathAddBackslashW(WCHAR *path)
1242 unsigned int len;
1244 TRACE("%s\n", wine_dbgstr_w(path));
1246 if (!path || (len = lstrlenW(path)) >= MAX_PATH)
1247 return NULL;
1249 if (len)
1251 path += len;
1252 if (path[-1] != '\\')
1254 *path++ = '\\';
1255 *path = '\0';
1259 return path;
1262 LPSTR WINAPI PathFindExtensionA(const char *path)
1264 const char *lastpoint = NULL;
1266 TRACE("%s\n", wine_dbgstr_a(path));
1268 if (path)
1270 while (*path)
1272 if (*path == '\\' || *path == ' ')
1273 lastpoint = NULL;
1274 else if (*path == '.')
1275 lastpoint = path;
1276 path = CharNextA(path);
1280 return (LPSTR)(lastpoint ? lastpoint : path);
1283 LPWSTR WINAPI PathFindExtensionW(const WCHAR *path)
1285 const WCHAR *lastpoint = NULL;
1287 TRACE("%s\n", wine_dbgstr_w(path));
1289 if (path)
1291 while (*path)
1293 if (*path == '\\' || *path == ' ')
1294 lastpoint = NULL;
1295 else if (*path == '.')
1296 lastpoint = path;
1297 path++;
1301 return (LPWSTR)(lastpoint ? lastpoint : path);
1304 BOOL WINAPI PathAddExtensionA(char *path, const char *ext)
1306 unsigned int len;
1308 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1310 if (!path || !ext || *(PathFindExtensionA(path)))
1311 return FALSE;
1313 len = strlen(path);
1314 if (len + strlen(ext) >= MAX_PATH)
1315 return FALSE;
1317 strcpy(path + len, ext);
1318 return TRUE;
1321 BOOL WINAPI PathAddExtensionW(WCHAR *path, const WCHAR *ext)
1323 unsigned int len;
1325 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1327 if (!path || !ext || *(PathFindExtensionW(path)))
1328 return FALSE;
1330 len = lstrlenW(path);
1331 if (len + lstrlenW(ext) >= MAX_PATH)
1332 return FALSE;
1334 lstrcpyW(path + len, ext);
1335 return TRUE;
1338 BOOL WINAPI PathCanonicalizeW(WCHAR *buffer, const WCHAR *path)
1340 const WCHAR *src = path;
1341 WCHAR *dst = buffer;
1343 TRACE("%p, %s\n", buffer, wine_dbgstr_w(path));
1345 if (dst)
1346 *dst = '\0';
1348 if (!dst || !path)
1350 SetLastError(ERROR_INVALID_PARAMETER);
1351 return FALSE;
1354 if (!*path)
1356 *buffer++ = '\\';
1357 *buffer = '\0';
1358 return TRUE;
1361 /* Copy path root */
1362 if (*src == '\\')
1364 *dst++ = *src++;
1366 else if (*src && src[1] == ':')
1368 /* X:\ */
1369 *dst++ = *src++;
1370 *dst++ = *src++;
1371 if (*src == '\\')
1372 *dst++ = *src++;
1375 /* Canonicalize the rest of the path */
1376 while (*src)
1378 if (*src == '.')
1380 if (src[1] == '\\' && (src == path || src[-1] == '\\' || src[-1] == ':'))
1382 src += 2; /* Skip .\ */
1384 else if (src[1] == '.' && (dst == buffer || dst[-1] == '\\'))
1386 /* \.. backs up a directory, over the root if it has no \ following X:.
1387 * .. is ignored if it would remove a UNC server name or initial \\
1389 if (dst != buffer)
1391 *dst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
1392 if (dst > buffer + 1 && dst[-1] == '\\' && (dst[-2] != '\\' || dst > buffer + 2))
1394 if (dst[-2] == ':' && (dst > buffer + 3 || dst[-3] == ':'))
1396 dst -= 2;
1397 while (dst > buffer && *dst != '\\')
1398 dst--;
1399 if (*dst == '\\')
1400 dst++; /* Reset to last '\' */
1401 else
1402 dst = buffer; /* Start path again from new root */
1404 else if (dst[-2] != ':' && !PathIsUNCServerShareW(buffer))
1405 dst -= 2;
1407 while (dst > buffer && *dst != '\\')
1408 dst--;
1409 if (dst == buffer)
1411 *dst++ = '\\';
1412 src++;
1415 src += 2; /* Skip .. in src path */
1417 else
1418 *dst++ = *src++;
1420 else
1421 *dst++ = *src++;
1424 /* Append \ to naked drive specs */
1425 if (dst - buffer == 2 && dst[-1] == ':')
1426 *dst++ = '\\';
1427 *dst++ = '\0';
1428 return TRUE;
1431 BOOL WINAPI PathCanonicalizeA(char *buffer, const char *path)
1433 WCHAR pathW[MAX_PATH], bufferW[MAX_PATH];
1434 BOOL ret;
1435 int len;
1437 TRACE("%p, %s\n", buffer, wine_dbgstr_a(path));
1439 if (buffer)
1440 *buffer = '\0';
1442 if (!buffer || !path)
1444 SetLastError(ERROR_INVALID_PARAMETER);
1445 return FALSE;
1448 len = MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, ARRAY_SIZE(pathW));
1449 if (!len)
1450 return FALSE;
1452 ret = PathCanonicalizeW(bufferW, pathW);
1453 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, 0, 0);
1455 return ret;
1458 WCHAR * WINAPI PathCombineW(WCHAR *dst, const WCHAR *dir, const WCHAR *file)
1460 BOOL use_both = FALSE, strip = FALSE;
1461 WCHAR tmp[MAX_PATH];
1463 TRACE("%p, %s, %s\n", dst, wine_dbgstr_w(dir), wine_dbgstr_w(file));
1465 /* Invalid parameters */
1466 if (!dst)
1467 return NULL;
1469 if (!dir && !file)
1471 dst[0] = 0;
1472 return NULL;
1475 if ((!file || !*file) && dir)
1477 /* Use dir only */
1478 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1480 else if (!dir || !*dir || !PathIsRelativeW(file))
1482 if (!dir || !*dir || *file != '\\' || PathIsUNCW(file))
1484 /* Use file only */
1485 lstrcpynW(tmp, file, ARRAY_SIZE(tmp));
1487 else
1489 use_both = TRUE;
1490 strip = TRUE;
1493 else
1494 use_both = TRUE;
1496 if (use_both)
1498 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1499 if (strip)
1501 PathStripToRootW(tmp);
1502 file++; /* Skip '\' */
1505 if (!PathAddBackslashW(tmp) || lstrlenW(tmp) + lstrlenW(file) >= MAX_PATH)
1507 dst[0] = 0;
1508 return NULL;
1511 lstrcatW(tmp, file);
1514 PathCanonicalizeW(dst, tmp);
1515 return dst;
1518 LPSTR WINAPI PathCombineA(char *dst, const char *dir, const char *file)
1520 WCHAR dstW[MAX_PATH], dirW[MAX_PATH], fileW[MAX_PATH];
1522 TRACE("%p, %s, %s\n", dst, wine_dbgstr_a(dir), wine_dbgstr_a(file));
1524 /* Invalid parameters */
1525 if (!dst)
1526 return NULL;
1528 if (!dir && !file)
1529 goto fail;
1531 if (dir && !MultiByteToWideChar(CP_ACP, 0, dir, -1, dirW, ARRAY_SIZE(dirW)))
1532 goto fail;
1534 if (file && !MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW)))
1535 goto fail;
1537 if (PathCombineW(dstW, dir ? dirW : NULL, file ? fileW : NULL))
1538 if (WideCharToMultiByte(CP_ACP, 0, dstW, -1, dst, MAX_PATH, 0, 0))
1539 return dst;
1540 fail:
1541 dst[0] = 0;
1542 return NULL;
1545 BOOL WINAPI PathAppendA(char *path, const char *append)
1547 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(append));
1549 if (path && append)
1551 if (!PathIsUNCA(append))
1552 while (*append == '\\')
1553 append++;
1555 if (PathCombineA(path, path, append))
1556 return TRUE;
1559 return FALSE;
1562 BOOL WINAPI PathAppendW(WCHAR *path, const WCHAR *append)
1564 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(append));
1566 if (path && append)
1568 if (!PathIsUNCW(append))
1569 while (*append == '\\')
1570 append++;
1572 if (PathCombineW(path, path, append))
1573 return TRUE;
1576 return FALSE;
1579 int WINAPI PathCommonPrefixA(const char *file1, const char *file2, char *path)
1581 const char *iter1 = file1;
1582 const char *iter2 = file2;
1583 unsigned int len = 0;
1585 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1), wine_dbgstr_a(file2), path);
1587 if (path)
1588 *path = '\0';
1590 if (!file1 || !file2)
1591 return 0;
1593 /* Handle roots first */
1594 if (PathIsUNCA(file1))
1596 if (!PathIsUNCA(file2))
1597 return 0;
1598 iter1 += 2;
1599 iter2 += 2;
1601 else if (PathIsUNCA(file2))
1602 return 0;
1604 for (;;)
1606 /* Update len */
1607 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1608 len = iter1 - file1; /* Common to this point */
1610 if (!*iter1 || (tolower(*iter1) != tolower(*iter2)))
1611 break; /* Strings differ at this point */
1613 iter1++;
1614 iter2++;
1617 if (len == 2)
1618 len++; /* Feature/Bug compatible with Win32 */
1620 if (len && path)
1622 memcpy(path, file1, len);
1623 path[len] = '\0';
1626 return len;
1629 int WINAPI PathCommonPrefixW(const WCHAR *file1, const WCHAR *file2, WCHAR *path)
1631 const WCHAR *iter1 = file1;
1632 const WCHAR *iter2 = file2;
1633 unsigned int len = 0;
1635 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1), wine_dbgstr_w(file2), path);
1637 if (path)
1638 *path = '\0';
1640 if (!file1 || !file2)
1641 return 0;
1643 /* Handle roots first */
1644 if (PathIsUNCW(file1))
1646 if (!PathIsUNCW(file2))
1647 return 0;
1648 iter1 += 2;
1649 iter2 += 2;
1651 else if (PathIsUNCW(file2))
1652 return 0;
1654 for (;;)
1656 /* Update len */
1657 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1658 len = iter1 - file1; /* Common to this point */
1660 if (!*iter1 || (towlower(*iter1) != towlower(*iter2)))
1661 break; /* Strings differ at this point */
1663 iter1++;
1664 iter2++;
1667 if (len == 2)
1668 len++; /* Feature/Bug compatible with Win32 */
1670 if (len && path)
1672 memcpy(path, file1, len * sizeof(WCHAR));
1673 path[len] = '\0';
1676 return len;
1679 BOOL WINAPI PathIsPrefixA(const char *prefix, const char *path)
1681 TRACE("%s, %s\n", wine_dbgstr_a(prefix), wine_dbgstr_a(path));
1683 return prefix && path && PathCommonPrefixA(path, prefix, NULL) == (int)strlen(prefix);
1686 BOOL WINAPI PathIsPrefixW(const WCHAR *prefix, const WCHAR *path)
1688 TRACE("%s, %s\n", wine_dbgstr_w(prefix), wine_dbgstr_w(path));
1690 return prefix && path && PathCommonPrefixW(path, prefix, NULL) == (int)lstrlenW(prefix);
1693 char * WINAPI PathFindFileNameA(const char *path)
1695 const char *last_slash = path;
1697 TRACE("%s\n", wine_dbgstr_a(path));
1699 while (path && *path)
1701 if ((*path == '\\' || *path == '/' || *path == ':') &&
1702 path[1] && path[1] != '\\' && path[1] != '/')
1703 last_slash = path + 1;
1704 path = CharNextA(path);
1707 return (char *)last_slash;
1710 WCHAR * WINAPI PathFindFileNameW(const WCHAR *path)
1712 const WCHAR *last_slash = path;
1714 TRACE("%s\n", wine_dbgstr_w(path));
1716 while (path && *path)
1718 if ((*path == '\\' || *path == '/' || *path == ':') &&
1719 path[1] && path[1] != '\\' && path[1] != '/')
1720 last_slash = path + 1;
1721 path++;
1724 return (WCHAR *)last_slash;
1727 char * WINAPI PathGetArgsA(const char *path)
1729 BOOL seen_quote = FALSE;
1731 TRACE("%s\n", wine_dbgstr_a(path));
1733 if (!path)
1734 return NULL;
1736 while (*path)
1738 if (*path == ' ' && !seen_quote)
1739 return (char *)path + 1;
1741 if (*path == '"')
1742 seen_quote = !seen_quote;
1743 path = CharNextA(path);
1746 return (char *)path;
1749 WCHAR * WINAPI PathGetArgsW(const WCHAR *path)
1751 BOOL seen_quote = FALSE;
1753 TRACE("%s\n", wine_dbgstr_w(path));
1755 if (!path)
1756 return NULL;
1758 while (*path)
1760 if (*path == ' ' && !seen_quote)
1761 return (WCHAR *)path + 1;
1763 if (*path == '"')
1764 seen_quote = !seen_quote;
1765 path++;
1768 return (WCHAR *)path;
1771 UINT WINAPI PathGetCharTypeW(WCHAR ch)
1773 UINT flags = 0;
1775 TRACE("%#x\n", ch);
1777 if (!ch || ch < ' ' || ch == '<' || ch == '>' || ch == '"' || ch == '|' || ch == '/')
1778 flags = GCT_INVALID; /* Invalid */
1779 else if (ch == '*' || ch == '?')
1780 flags = GCT_WILD; /* Wildchars */
1781 else if (ch == '\\' || ch == ':')
1782 return GCT_SEPARATOR; /* Path separators */
1783 else
1785 if (ch < 126)
1787 if (((ch & 0x1) && ch != ';') || !ch || iswalnum(ch) || ch == '$' || ch == '&' || ch == '(' ||
1788 ch == '.' || ch == '@' || ch == '^' || ch == '\'' || ch == 130 || ch == '`')
1790 flags |= GCT_SHORTCHAR; /* All these are valid for DOS */
1793 else
1794 flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */
1796 flags |= GCT_LFNCHAR; /* Valid for long file names */
1799 return flags;
1802 UINT WINAPI PathGetCharTypeA(UCHAR ch)
1804 return PathGetCharTypeW(ch);
1807 int WINAPI PathGetDriveNumberA(const char *path)
1809 TRACE("%s\n", wine_dbgstr_a(path));
1811 if (path && !IsDBCSLeadByte(*path) && path[1] == ':')
1813 if (*path >= 'a' && *path <= 'z') return *path - 'a';
1814 if (*path >= 'A' && *path <= 'Z') return *path - 'A';
1816 return -1;
1819 int WINAPI PathGetDriveNumberW(const WCHAR *path)
1821 static const WCHAR nt_prefixW[] = {'\\','\\','?','\\'};
1822 WCHAR drive;
1824 TRACE("%s\n", wine_dbgstr_w(path));
1826 if (!path)
1827 return -1;
1829 if (!wcsncmp(path, nt_prefixW, 4))
1830 path += 4;
1832 drive = towlower(path[0]);
1833 if (drive < 'a' || drive > 'z' || path[1] != ':')
1834 return -1;
1836 return drive - 'a';
1839 BOOL WINAPI PathIsFileSpecA(const char *path)
1841 TRACE("%s\n", wine_dbgstr_a(path));
1843 if (!path)
1844 return FALSE;
1846 while (*path)
1848 if (*path == '\\' || *path == ':')
1849 return FALSE;
1850 path = CharNextA(path);
1853 return TRUE;
1856 BOOL WINAPI PathIsFileSpecW(const WCHAR *path)
1858 TRACE("%s\n", wine_dbgstr_w(path));
1860 if (!path)
1861 return FALSE;
1863 while (*path)
1865 if (*path == '\\' || *path == ':')
1866 return FALSE;
1867 path++;
1870 return TRUE;
1873 BOOL WINAPI PathIsUNCServerA(const char *path)
1875 TRACE("%s\n", wine_dbgstr_a(path));
1877 if (!(path && path[0] == '\\' && path[1] == '\\'))
1878 return FALSE;
1880 while (*path)
1882 if (*path == '\\')
1883 return FALSE;
1884 path = CharNextA(path);
1887 return TRUE;
1890 BOOL WINAPI PathIsUNCServerW(const WCHAR *path)
1892 TRACE("%s\n", wine_dbgstr_w(path));
1894 if (!(path && path[0] == '\\' && path[1] == '\\'))
1895 return FALSE;
1897 return !wcschr(path + 2, '\\');
1900 void WINAPI PathRemoveBlanksA(char *path)
1902 char *start;
1904 TRACE("%s\n", wine_dbgstr_a(path));
1906 if (!path || !*path)
1907 return;
1909 start = path;
1911 while (*path == ' ')
1912 path = CharNextA(path);
1914 while (*path)
1915 *start++ = *path++;
1917 if (start != path)
1918 while (start[-1] == ' ')
1919 start--;
1921 *start = '\0';
1924 void WINAPI PathRemoveBlanksW(WCHAR *path)
1926 WCHAR *start = path;
1928 TRACE("%s\n", wine_dbgstr_w(path));
1930 if (!path || !*path)
1931 return;
1933 while (*path == ' ')
1934 path++;
1936 while (*path)
1937 *start++ = *path++;
1939 if (start != path)
1940 while (start[-1] == ' ')
1941 start--;
1943 *start = '\0';
1946 void WINAPI PathRemoveExtensionA(char *path)
1948 TRACE("%s\n", wine_dbgstr_a(path));
1950 if (!path)
1951 return;
1953 path = PathFindExtensionA(path);
1954 if (path && *path)
1955 *path = '\0';
1958 void WINAPI PathRemoveExtensionW(WCHAR *path)
1960 TRACE("%s\n", wine_dbgstr_w(path));
1962 if (!path)
1963 return;
1965 path = PathFindExtensionW(path);
1966 if (path && *path)
1967 *path = '\0';
1970 BOOL WINAPI PathRenameExtensionA(char *path, const char *ext)
1972 char *extension;
1974 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1976 extension = PathFindExtensionA(path);
1978 if (!extension || (extension - path + strlen(ext) >= MAX_PATH))
1979 return FALSE;
1981 strcpy(extension, ext);
1982 return TRUE;
1985 BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext)
1987 WCHAR *extension;
1989 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1991 extension = PathFindExtensionW(path);
1993 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH))
1994 return FALSE;
1996 lstrcpyW(extension, ext);
1997 return TRUE;
2000 void WINAPI PathUnquoteSpacesA(char *path)
2002 unsigned int len;
2004 TRACE("%s\n", wine_dbgstr_a(path));
2006 if (!path || *path != '"')
2007 return;
2009 len = strlen(path) - 1;
2010 if (path[len] == '"')
2012 path[len] = '\0';
2013 for (; *path; path++)
2014 *path = path[1];
2018 void WINAPI PathUnquoteSpacesW(WCHAR *path)
2020 unsigned int len;
2022 TRACE("%s\n", wine_dbgstr_w(path));
2024 if (!path || *path != '"')
2025 return;
2027 len = lstrlenW(path) - 1;
2028 if (path[len] == '"')
2030 path[len] = '\0';
2031 for (; *path; path++)
2032 *path = path[1];
2036 char * WINAPI PathRemoveBackslashA(char *path)
2038 char *ptr;
2040 TRACE("%s\n", wine_dbgstr_a(path));
2042 if (!path)
2043 return NULL;
2045 ptr = CharPrevA(path, path + strlen(path));
2046 if (!PathIsRootA(path) && *ptr == '\\')
2047 *ptr = '\0';
2049 return ptr;
2052 WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path)
2054 WCHAR *ptr;
2056 TRACE("%s\n", wine_dbgstr_w(path));
2058 if (!path)
2059 return NULL;
2061 ptr = path + lstrlenW(path);
2062 if (ptr > path) ptr--;
2063 if (!PathIsRootW(path) && *ptr == '\\')
2064 *ptr = '\0';
2066 return ptr;
2069 BOOL WINAPI PathIsLFNFileSpecA(const char *path)
2071 unsigned int name_len = 0, ext_len = 0;
2073 TRACE("%s\n", wine_dbgstr_a(path));
2075 if (!path)
2076 return FALSE;
2078 while (*path)
2080 if (*path == ' ')
2081 return TRUE; /* DOS names cannot have spaces */
2082 if (*path == '.')
2084 if (ext_len)
2085 return TRUE; /* DOS names have only one dot */
2086 ext_len = 1;
2088 else if (ext_len)
2090 ext_len++;
2091 if (ext_len > 4)
2092 return TRUE; /* DOS extensions are <= 3 chars*/
2094 else
2096 name_len++;
2097 if (name_len > 8)
2098 return TRUE; /* DOS names are <= 8 chars */
2100 path = CharNextA(path);
2103 return FALSE; /* Valid DOS path */
2106 BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path)
2108 unsigned int name_len = 0, ext_len = 0;
2110 TRACE("%s\n", wine_dbgstr_w(path));
2112 if (!path)
2113 return FALSE;
2115 while (*path)
2117 if (*path == ' ')
2118 return TRUE; /* DOS names cannot have spaces */
2119 if (*path == '.')
2121 if (ext_len)
2122 return TRUE; /* DOS names have only one dot */
2123 ext_len = 1;
2125 else if (ext_len)
2127 ext_len++;
2128 if (ext_len > 4)
2129 return TRUE; /* DOS extensions are <= 3 chars*/
2131 else
2133 name_len++;
2134 if (name_len > 8)
2135 return TRUE; /* DOS names are <= 8 chars */
2137 path++;
2140 return FALSE; /* Valid DOS path */
2143 #define PATH_CHAR_CLASS_LETTER 0x00000001
2144 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2145 #define PATH_CHAR_CLASS_DOT 0x00000004
2146 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2147 #define PATH_CHAR_CLASS_COLON 0x00000010
2148 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2149 #define PATH_CHAR_CLASS_COMMA 0x00000040
2150 #define PATH_CHAR_CLASS_SPACE 0x00000080
2151 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2152 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2154 #define PATH_CHAR_CLASS_INVALID 0x00000000
2155 #define PATH_CHAR_CLASS_ANY 0xffffffff
2157 static const DWORD path_charclass[] =
2159 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID,
2160 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID,
2161 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID,
2162 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID,
2163 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID,
2164 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID,
2165 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID,
2166 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID,
2167 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID,
2168 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID,
2169 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID,
2170 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID,
2171 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID,
2172 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID,
2173 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID,
2174 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID,
2175 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID,
2176 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID,
2177 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID,
2178 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID,
2179 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID,
2180 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID,
2181 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID,
2182 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID,
2183 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID,
2184 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID,
2185 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID,
2186 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID,
2187 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID,
2188 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON,
2189 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID,
2190 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER,
2191 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY,
2192 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY,
2193 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY,
2194 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY,
2195 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY,
2196 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY,
2197 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY,
2198 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY,
2199 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY,
2200 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY,
2201 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY,
2202 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY,
2203 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY,
2204 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID,
2205 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID,
2206 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID,
2207 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY,
2208 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY,
2209 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY,
2210 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY,
2211 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY,
2212 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY,
2213 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY,
2214 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY,
2215 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY,
2216 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY,
2217 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY,
2218 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY,
2219 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY,
2220 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID,
2221 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID,
2222 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2225 BOOL WINAPI PathIsValidCharA(char c, DWORD class)
2227 if ((unsigned)c > 0x7e)
2228 return class & PATH_CHAR_CLASS_OTHER_VALID;
2230 return class & path_charclass[(unsigned)c];
2233 BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class)
2235 if (c > 0x7e)
2236 return class & PATH_CHAR_CLASS_OTHER_VALID;
2238 return class & path_charclass[c];
2241 char * WINAPI PathFindNextComponentA(const char *path)
2243 char *slash;
2245 TRACE("%s\n", wine_dbgstr_a(path));
2247 if (!path || !*path)
2248 return NULL;
2250 if ((slash = StrChrA(path, '\\')))
2252 if (slash[1] == '\\')
2253 slash++;
2254 return slash + 1;
2257 return (char *)path + strlen(path);
2260 WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path)
2262 WCHAR *slash;
2264 TRACE("%s\n", wine_dbgstr_w(path));
2266 if (!path || !*path)
2267 return NULL;
2269 if ((slash = StrChrW(path, '\\')))
2271 if (slash[1] == '\\')
2272 slash++;
2273 return slash + 1;
2276 return (WCHAR *)path + lstrlenW(path);
2279 char * WINAPI PathSkipRootA(const char *path)
2281 TRACE("%s\n", wine_dbgstr_a(path));
2283 if (!path || !*path)
2284 return NULL;
2286 if (*path == '\\' && path[1] == '\\')
2288 /* Network share: skip share server and mount point */
2289 path += 2;
2290 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\')))
2291 path++;
2292 return (char *)path;
2295 if (IsDBCSLeadByte(*path))
2296 return NULL;
2298 /* Check x:\ */
2299 if (path[0] && path[1] == ':' && path[2] == '\\')
2300 return (char *)path + 3;
2302 return NULL;
2305 WCHAR * WINAPI PathSkipRootW(const WCHAR *path)
2307 TRACE("%s\n", wine_dbgstr_w(path));
2309 if (!path || !*path)
2310 return NULL;
2312 if (*path == '\\' && path[1] == '\\')
2314 /* Network share: skip share server and mount point */
2315 path += 2;
2316 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\')))
2317 path++;
2318 return (WCHAR *)path;
2321 /* Check x:\ */
2322 if (path[0] && path[1] == ':' && path[2] == '\\')
2323 return (WCHAR *)path + 3;
2325 return NULL;
2328 void WINAPI PathStripPathA(char *path)
2330 TRACE("%s\n", wine_dbgstr_a(path));
2332 if (path)
2334 char *filename = PathFindFileNameA(path);
2335 if (filename != path)
2336 RtlMoveMemory(path, filename, strlen(filename) + 1);
2340 void WINAPI PathStripPathW(WCHAR *path)
2342 WCHAR *filename;
2344 TRACE("%s\n", wine_dbgstr_w(path));
2345 filename = PathFindFileNameW(path);
2346 if (filename != path)
2347 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR));
2350 BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length)
2352 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length);
2354 if (SearchPathA(NULL, path, NULL, length, buffer, NULL))
2355 return TRUE;
2357 return !!GetFullPathNameA(path, length, buffer, NULL);
2360 BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length)
2362 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length);
2364 if (SearchPathW(NULL, path, NULL, length, buffer, NULL))
2365 return TRUE;
2366 return !!GetFullPathNameW(path, length, buffer, NULL);
2369 BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to,
2370 DWORD attributes_to)
2372 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH];
2373 BOOL ret;
2375 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to);
2377 if (!path || !from || !to)
2378 return FALSE;
2380 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW));
2381 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW));
2382 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to);
2383 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0);
2385 return ret;
2388 BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to,
2389 DWORD attributes_to)
2391 static const WCHAR szPrevDirSlash[] = { '.', '.', '\\', '\0' };
2392 static const WCHAR szPrevDir[] = { '.', '.', '\0' };
2393 WCHAR fromW[MAX_PATH], toW[MAX_PATH];
2394 DWORD len;
2396 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to);
2398 if (!path || !from || !to)
2399 return FALSE;
2401 *path = '\0';
2402 lstrcpynW(fromW, from, ARRAY_SIZE(fromW));
2403 lstrcpynW(toW, to, ARRAY_SIZE(toW));
2405 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY))
2406 PathRemoveFileSpecW(fromW);
2407 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY))
2408 PathRemoveFileSpecW(toW);
2410 /* Paths can only be relative if they have a common root */
2411 if (!(len = PathCommonPrefixW(fromW, toW, 0)))
2412 return FALSE;
2414 /* Strip off 'from' components to the root, by adding "..\" */
2415 from = fromW + len;
2416 if (!*from)
2418 path[0] = '.';
2419 path[1] = '\0';
2421 if (*from == '\\')
2422 from++;
2424 while (*from)
2426 from = PathFindNextComponentW(from);
2427 lstrcatW(path, *from ? szPrevDirSlash : szPrevDir);
2430 /* From the root add the components of 'to' */
2431 to += len;
2432 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2433 if (*to && to[-1])
2435 if (*to != '\\')
2436 to--;
2437 len = lstrlenW(path);
2438 if (len + lstrlenW(to) >= MAX_PATH)
2440 *path = '\0';
2441 return FALSE;
2443 lstrcpyW(path + len, to);
2446 return TRUE;
2449 BOOL WINAPI PathMatchSpecA(const char *path, const char *mask)
2451 WCHAR *pathW, *maskW;
2452 BOOL ret;
2454 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask));
2456 if (!lstrcmpA(mask, "*.*"))
2457 return TRUE; /* Matches every path */
2459 pathW = heap_strdupAtoW( path );
2460 maskW = heap_strdupAtoW( mask );
2461 ret = PathMatchSpecW( pathW, maskW );
2462 heap_free( pathW );
2463 heap_free( maskW );
2464 return ret;
2467 static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask)
2469 while (*name && *mask && *mask != ';')
2471 if (*mask == '*')
2475 if (path_match_maskW(name, mask + 1))
2476 return TRUE; /* try substrings */
2477 } while (*name++);
2478 return FALSE;
2481 if (towupper(*mask) != towupper(*name) && *mask != '?')
2482 return FALSE;
2484 name++;
2485 mask++;
2488 if (!*name)
2490 while (*mask == '*')
2491 mask++;
2492 if (!*mask || *mask == ';')
2493 return TRUE;
2496 return FALSE;
2499 BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask)
2501 static const WCHAR maskallW[] = {'*','.','*',0};
2503 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask));
2505 if (!lstrcmpW(mask, maskallW))
2506 return TRUE; /* Matches every path */
2508 while (*mask)
2510 while (*mask == ' ')
2511 mask++; /* Eat leading spaces */
2513 if (path_match_maskW(path, mask))
2514 return TRUE; /* Matches the current path */
2516 while (*mask && *mask != ';')
2517 mask++; /* masks separated by ';' */
2519 if (*mask == ';')
2520 mask++;
2523 return FALSE;
2526 void WINAPI PathQuoteSpacesA(char *path)
2528 TRACE("%s\n", wine_dbgstr_a(path));
2530 if (path && StrChrA(path, ' '))
2532 size_t len = strlen(path) + 1;
2534 if (len + 2 < MAX_PATH)
2536 memmove(path + 1, path, len);
2537 path[0] = '"';
2538 path[len] = '"';
2539 path[len + 1] = '\0';
2544 void WINAPI PathQuoteSpacesW(WCHAR *path)
2546 TRACE("%s\n", wine_dbgstr_w(path));
2548 if (path && StrChrW(path, ' '))
2550 int len = lstrlenW(path) + 1;
2552 if (len + 2 < MAX_PATH)
2554 memmove(path + 1, path, len * sizeof(WCHAR));
2555 path[0] = '"';
2556 path[len] = '"';
2557 path[len + 1] = '\0';
2562 BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2)
2564 const char *start;
2565 int len;
2567 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2));
2569 if (!path1 || !path2 || !(start = PathSkipRootA(path1)))
2570 return FALSE;
2572 len = PathCommonPrefixA(path1, path2, NULL) + 1;
2573 return start - path1 <= len;
2576 BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2)
2578 const WCHAR *start;
2579 int len;
2581 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2));
2583 if (!path1 || !path2 || !(start = PathSkipRootW(path1)))
2584 return FALSE;
2586 len = PathCommonPrefixW(path1, path2, NULL) + 1;
2587 return start - path1 <= len;
2590 BOOL WINAPI PathFileExistsA(const char *path)
2592 UINT prev_mode;
2593 DWORD attrs;
2595 TRACE("%s\n", wine_dbgstr_a(path));
2597 if (!path)
2598 return FALSE;
2600 /* Prevent a dialog box if path is on a disk that has been ejected. */
2601 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2602 attrs = GetFileAttributesA(path);
2603 SetErrorMode(prev_mode);
2604 return attrs != INVALID_FILE_ATTRIBUTES;
2607 BOOL WINAPI PathFileExistsW(const WCHAR *path)
2609 UINT prev_mode;
2610 DWORD attrs;
2612 TRACE("%s\n", wine_dbgstr_w(path));
2614 if (!path)
2615 return FALSE;
2617 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2618 attrs = GetFileAttributesW(path);
2619 SetErrorMode(prev_mode);
2620 return attrs != INVALID_FILE_ATTRIBUTES;
2623 int WINAPI PathParseIconLocationA(char *path)
2625 int ret = 0;
2626 char *comma;
2628 TRACE("%s\n", debugstr_a(path));
2630 if (!path)
2631 return 0;
2633 if ((comma = strchr(path, ',')))
2635 *comma++ = '\0';
2636 ret = StrToIntA(comma);
2638 PathUnquoteSpacesA(path);
2639 PathRemoveBlanksA(path);
2641 return ret;
2644 int WINAPI PathParseIconLocationW(WCHAR *path)
2646 WCHAR *comma;
2647 int ret = 0;
2649 TRACE("%s\n", debugstr_w(path));
2651 if (!path)
2652 return 0;
2654 if ((comma = StrChrW(path, ',')))
2656 *comma++ = '\0';
2657 ret = StrToIntW(comma);
2659 PathUnquoteSpacesW(path);
2660 PathRemoveBlanksW(path);
2662 return ret;
2665 BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len)
2667 WCHAR bufferW[MAX_PATH], *pathW;
2668 DWORD len;
2669 BOOL ret;
2671 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len);
2673 pathW = heap_strdupAtoW(path);
2674 if (!pathW) return FALSE;
2676 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
2677 HeapFree(GetProcessHeap(), 0, pathW);
2678 if (!ret) return FALSE;
2680 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
2681 if (buf_len < len + 1) return FALSE;
2683 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
2684 return TRUE;
2687 static const WCHAR allusersprofileW[] = {'%','A','L','L','U','S','E','R','S','P','R','O','F','I','L','E','%',0};
2688 static const WCHAR appdataW[] = {'%','A','P','P','D','A','T','A','%',0};
2689 static const WCHAR programfilesW[] = {'%','P','r','o','g','r','a','m','F','i','l','e','s','%',0};
2690 static const WCHAR systemrootW[] = {'%','S','y','s','t','e','m','R','o','o','t','%',0};
2691 static const WCHAR systemdriveW[] = {'%','S','y','s','t','e','m','D','r','i','v','e','%',0};
2692 static const WCHAR userprofileW[] = {'%','U','S','E','R','P','R','O','F','I','L','E','%',0};
2694 struct envvars_map
2696 const WCHAR *var;
2697 UINT varlen;
2698 WCHAR path[MAX_PATH];
2699 DWORD len;
2702 static void init_envvars_map(struct envvars_map *map)
2704 while (map->var)
2706 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path));
2707 /* exclude null from length */
2708 if (map->len) map->len--;
2709 map++;
2713 BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len)
2715 static struct envvars_map null_var = {NULL, 0, {0}, 0};
2716 struct envvars_map *match = &null_var, *cur;
2717 struct envvars_map envvars[] =
2719 { allusersprofileW, ARRAY_SIZE(allusersprofileW) },
2720 { appdataW, ARRAY_SIZE(appdataW) },
2721 { programfilesW, ARRAY_SIZE(programfilesW) },
2722 { systemrootW, ARRAY_SIZE(systemrootW) },
2723 { systemdriveW, ARRAY_SIZE(systemdriveW) },
2724 { userprofileW, ARRAY_SIZE(userprofileW) },
2725 { NULL }
2727 DWORD pathlen;
2728 UINT needed;
2730 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len);
2732 pathlen = lstrlenW(path);
2733 init_envvars_map(envvars);
2734 cur = envvars;
2735 while (cur->var)
2737 /* path can't contain expanded value or value wasn't retrieved */
2738 if (cur->len == 0 || cur->len > pathlen || wcsnicmp(cur->path, path, cur->len))
2740 cur++;
2741 continue;
2744 if (cur->len > match->len)
2745 match = cur;
2746 cur++;
2749 /* 'varlen' includes NULL termination char */
2750 needed = match->varlen + pathlen - match->len;
2751 if (match->len == 0 || needed > buf_len) return FALSE;
2753 lstrcpyW(buffer, match->var);
2754 lstrcatW(buffer, &path[match->len]);
2755 TRACE("ret %s\n", debugstr_w(buffer));
2757 return TRUE;
2760 static const struct
2762 URL_SCHEME scheme_number;
2763 WCHAR scheme_name[12];
2765 url_schemes[] =
2767 { URL_SCHEME_FTP, {'f','t','p',0}},
2768 { URL_SCHEME_HTTP, {'h','t','t','p',0}},
2769 { URL_SCHEME_GOPHER, {'g','o','p','h','e','r',0}},
2770 { URL_SCHEME_MAILTO, {'m','a','i','l','t','o',0}},
2771 { URL_SCHEME_NEWS, {'n','e','w','s',0}},
2772 { URL_SCHEME_NNTP, {'n','n','t','p',0}},
2773 { URL_SCHEME_TELNET, {'t','e','l','n','e','t',0}},
2774 { URL_SCHEME_WAIS, {'w','a','i','s',0}},
2775 { URL_SCHEME_FILE, {'f','i','l','e',0}},
2776 { URL_SCHEME_MK, {'m','k',0}},
2777 { URL_SCHEME_HTTPS, {'h','t','t','p','s',0}},
2778 { URL_SCHEME_SHELL, {'s','h','e','l','l',0}},
2779 { URL_SCHEME_SNEWS, {'s','n','e','w','s',0}},
2780 { URL_SCHEME_LOCAL, {'l','o','c','a','l',0}},
2781 { URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}},
2782 { URL_SCHEME_VBSCRIPT, {'v','b','s','c','r','i','p','t',0}},
2783 { URL_SCHEME_ABOUT, {'a','b','o','u','t',0}},
2784 { URL_SCHEME_RES, {'r','e','s',0}},
2787 static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len)
2789 unsigned int i;
2791 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i)
2793 if (scheme_len == lstrlenW(url_schemes[i].scheme_name)
2794 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len))
2795 return url_schemes[i].scheme_number;
2798 return URL_SCHEME_UNKNOWN;
2801 HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result)
2803 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
2804 const char *ptr = url;
2805 int len;
2807 TRACE("%s, %p\n", wine_dbgstr_a(url), result);
2809 if (result->cbSize != sizeof(*result))
2810 return E_INVALIDARG;
2812 while (*ptr && ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') ||
2813 (*ptr >= '0' && *ptr <= '9') || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2814 ptr++;
2816 if (*ptr != ':' || ptr <= url + 1)
2818 result->pszProtocol = NULL;
2819 return URL_E_INVALID_SYNTAX;
2822 result->pszProtocol = url;
2823 result->cchProtocol = ptr - url;
2824 result->pszSuffix = ptr + 1;
2825 result->cchSuffix = strlen(result->pszSuffix);
2827 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme));
2828 result->nScheme = get_scheme_code(scheme, len);
2830 return S_OK;
2833 HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result)
2835 const WCHAR *ptr = url;
2837 TRACE("%s, %p\n", wine_dbgstr_w(url), result);
2839 if (result->cbSize != sizeof(*result))
2840 return E_INVALIDARG;
2842 while (*ptr && (iswalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2843 ptr++;
2845 if (*ptr != ':' || ptr <= url + 1)
2847 result->pszProtocol = NULL;
2848 return URL_E_INVALID_SYNTAX;
2851 result->pszProtocol = url;
2852 result->cchProtocol = ptr - url;
2853 result->pszSuffix = ptr + 1;
2854 result->cchSuffix = lstrlenW(result->pszSuffix);
2855 result->nScheme = get_scheme_code(url, ptr - url);
2857 return S_OK;
2860 HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags)
2862 BOOL stop_unescaping = FALSE;
2863 const char *src;
2864 char *dst, next;
2865 DWORD needed;
2866 HRESULT hr;
2868 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags);
2870 if (!url)
2871 return E_INVALIDARG;
2873 if (flags & URL_UNESCAPE_INPLACE)
2874 dst = url;
2875 else
2877 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2878 dst = unescaped;
2881 for (src = url, needed = 0; *src; src++, needed++)
2883 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2885 stop_unescaping = TRUE;
2886 next = *src;
2888 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2890 INT ih;
2891 char buf[3];
2892 memcpy(buf, src + 1, 2);
2893 buf[2] = '\0';
2894 ih = strtol(buf, NULL, 16);
2895 next = (CHAR) ih;
2896 src += 2; /* Advance to end of escape */
2898 else
2899 next = *src;
2901 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2902 *dst++ = next;
2905 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2907 *dst = '\0';
2908 hr = S_OK;
2910 else
2912 needed++; /* add one for the '\0' */
2913 hr = E_POINTER;
2916 if (!(flags & URL_UNESCAPE_INPLACE))
2917 *unescaped_len = needed;
2919 if (hr == S_OK)
2920 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped));
2922 return hr;
2925 HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags)
2927 BOOL stop_unescaping = FALSE;
2928 const WCHAR *src;
2929 WCHAR *dst, next;
2930 DWORD needed;
2931 HRESULT hr;
2933 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags);
2935 if (!url)
2936 return E_INVALIDARG;
2938 if (flags & URL_UNESCAPE_INPLACE)
2939 dst = url;
2940 else
2942 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2943 dst = unescaped;
2946 for (src = url, needed = 0; *src; src++, needed++)
2948 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2950 stop_unescaping = TRUE;
2951 next = *src;
2953 else if (*src == '%' && iswxdigit(*(src + 1)) && iswxdigit(*(src + 2)) && !stop_unescaping)
2955 INT ih;
2956 WCHAR buf[5] = {'0','x',0};
2957 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
2958 buf[4] = 0;
2959 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
2960 next = (WCHAR) ih;
2961 src += 2; /* Advance to end of escape */
2963 else
2964 next = *src;
2966 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2967 *dst++ = next;
2970 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2972 *dst = '\0';
2973 hr = S_OK;
2975 else
2977 needed++; /* add one for the '\0' */
2978 hr = E_POINTER;
2981 if (!(flags & URL_UNESCAPE_INPLACE))
2982 *unescaped_len = needed;
2984 if (hr == S_OK)
2985 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped));
2987 return hr;
2990 HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved)
2992 WCHAR bufW[MAX_PATH];
2993 WCHAR *pathW = bufW;
2994 UNICODE_STRING urlW;
2995 HRESULT ret;
2996 DWORD lenW = ARRAY_SIZE(bufW), lenA;
2998 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
2999 return E_INVALIDARG;
3001 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
3002 return E_INVALIDARG;
3003 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
3004 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
3005 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
3007 if(ret == S_OK) {
3008 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
3009 if(*pcchPath > lenA) {
3010 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
3011 pszPath[lenA] = 0;
3012 *pcchPath = lenA;
3013 } else {
3014 *pcchPath = lenA + 1;
3015 ret = E_POINTER;
3018 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
3019 RtlFreeUnicodeString(&urlW);
3020 return ret;
3023 HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved)
3025 static const WCHAR file_colon[] = { 'f','i','l','e',':',0 };
3026 static const WCHAR localhost[] = { 'l','o','c','a','l','h','o','s','t',0 };
3027 DWORD nslashes, unescape, len;
3028 const WCHAR *src;
3029 WCHAR *tpath, *dst;
3030 HRESULT hr = S_OK;
3032 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), path, pcchPath, dwReserved);
3034 if (!url || !path || !pcchPath || !*pcchPath)
3035 return E_INVALIDARG;
3037 if (lstrlenW(url) < 5)
3038 return E_INVALIDARG;
3040 if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, file_colon, 5) != CSTR_EQUAL)
3041 return E_INVALIDARG;
3043 url += 5;
3045 src = url;
3046 nslashes = 0;
3047 while (*src == '/' || *src == '\\')
3049 nslashes++;
3050 src++;
3053 /* We need a temporary buffer so we can compute what size to ask for.
3054 * We know that the final string won't be longer than the current pszUrl
3055 * plus at most two backslashes. All the other transformations make it
3056 * shorter.
3058 len = 2 + lstrlenW(url) + 1;
3059 if (*pcchPath < len)
3060 tpath = heap_alloc(len * sizeof(WCHAR));
3061 else
3062 tpath = path;
3064 len = 0;
3065 dst = tpath;
3066 unescape = 1;
3067 switch (nslashes)
3069 case 0:
3070 /* 'file:' + escaped DOS path */
3071 break;
3072 case 1:
3073 /* 'file:/' + escaped DOS path */
3074 /* fall through */
3075 case 3:
3076 /* 'file:///' (implied localhost) + escaped DOS path */
3077 if (!iswalpha(*src) || (src[1] != ':' && src[1] != '|'))
3078 src -= 1;
3079 break;
3080 case 2:
3081 if (lstrlenW(src) >= 10 && CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE,
3082 src, 9, localhost, 9) == CSTR_EQUAL && (src[9] == '/' || src[9] == '\\'))
3084 /* 'file://localhost/' + escaped DOS path */
3085 src += 10;
3087 else if (iswalpha(*src) && (src[1] == ':' || src[1] == '|'))
3089 /* 'file://' + unescaped DOS path */
3090 unescape = 0;
3092 else
3094 /* 'file://hostname:port/path' (where path is escaped)
3095 * or 'file:' + escaped UNC path (\\server\share\path)
3096 * The second form is clearly specific to Windows and it might
3097 * even be doing a network lookup to try to figure it out.
3099 while (*src && *src != '/' && *src != '\\')
3100 src++;
3101 len = src - url;
3102 StrCpyNW(dst, url, len + 1);
3103 dst += len;
3104 if (*src && iswalpha(src[1]) && (src[2] == ':' || src[2] == '|'))
3106 /* 'Forget' to add a trailing '/', just like Windows */
3107 src++;
3110 break;
3111 case 4:
3112 /* 'file://' + unescaped UNC path (\\server\share\path) */
3113 unescape = 0;
3114 if (iswalpha(*src) && (src[1] == ':' || src[1] == '|'))
3115 break;
3116 /* fall through */
3117 default:
3118 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3119 src -= 2;
3122 /* Copy the remainder of the path */
3123 len += lstrlenW(src);
3124 lstrcpyW(dst, src);
3126 /* First do the Windows-specific path conversions */
3127 for (dst = tpath; *dst; dst++)
3128 if (*dst == '/') *dst = '\\';
3129 if (iswalpha(*tpath) && tpath[1] == '|')
3130 tpath[1] = ':'; /* c| -> c: */
3132 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3133 if (unescape)
3135 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3136 if (hr == S_OK)
3138 /* When working in-place UrlUnescapeW() does not set len */
3139 len = lstrlenW(tpath);
3143 if (*pcchPath < len + 1)
3145 hr = E_POINTER;
3146 *pcchPath = len + 1;
3148 else
3150 *pcchPath = len;
3151 if (tpath != path)
3152 lstrcpyW(path, tpath);
3154 if (tpath != path)
3155 heap_free(tpath);
3157 TRACE("Returning (%u) %s\n", *pcchPath, wine_dbgstr_w(path));
3158 return hr;
3161 HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved)
3163 WCHAR pathW[MAX_PATH];
3164 DWORD size;
3165 HRESULT hr;
3167 size = MAX_PATH;
3168 hr = PathCreateFromUrlW(url, pathW, &size, reserved);
3169 if (SUCCEEDED(hr))
3171 /* Yes, this is supposed to crash if 'path' is NULL */
3172 *path = StrDupW(pathW);
3175 return hr;
3178 BOOL WINAPI PathIsURLA(const char *path)
3180 PARSEDURLA base;
3181 HRESULT hr;
3183 TRACE("%s\n", wine_dbgstr_a(path));
3185 if (!path || !*path)
3186 return FALSE;
3188 /* get protocol */
3189 base.cbSize = sizeof(base);
3190 hr = ParseURLA(path, &base);
3191 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3194 BOOL WINAPI PathIsURLW(const WCHAR *path)
3196 PARSEDURLW base;
3197 HRESULT hr;
3199 TRACE("%s\n", wine_dbgstr_w(path));
3201 if (!path || !*path)
3202 return FALSE;
3204 /* get protocol */
3205 base.cbSize = sizeof(base);
3206 hr = ParseURLW(path, &base);
3207 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3210 #define WINE_URL_BASH_AS_SLASH 0x01
3211 #define WINE_URL_COLLAPSE_SLASHES 0x02
3212 #define WINE_URL_ESCAPE_SLASH 0x04
3213 #define WINE_URL_ESCAPE_HASH 0x08
3214 #define WINE_URL_ESCAPE_QUESTION 0x10
3215 #define WINE_URL_STOP_ON_HASH 0x20
3216 #define WINE_URL_STOP_ON_QUESTION 0x40
3218 static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags)
3220 if (flags & URL_ESCAPE_SPACES_ONLY)
3221 return ch == ' ';
3223 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%'))
3224 return TRUE;
3226 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80))
3227 return TRUE;
3229 if (ch <= 31 || (ch >= 127 && ch <= 255) )
3230 return TRUE;
3232 if (iswalnum(ch))
3233 return FALSE;
3235 switch (ch) {
3236 case ' ':
3237 case '<':
3238 case '>':
3239 case '\"':
3240 case '{':
3241 case '}':
3242 case '|':
3243 case '\\':
3244 case '^':
3245 case ']':
3246 case '[':
3247 case '`':
3248 case '&':
3249 return TRUE;
3250 case '/':
3251 return !!(int_flags & WINE_URL_ESCAPE_SLASH);
3252 case '?':
3253 return !!(int_flags & WINE_URL_ESCAPE_QUESTION);
3254 case '#':
3255 return !!(int_flags & WINE_URL_ESCAPE_HASH);
3256 default:
3257 return FALSE;
3261 HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags)
3263 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
3264 WCHAR *escapedW = bufW;
3265 UNICODE_STRING urlW;
3266 HRESULT hr;
3267 DWORD lenW = ARRAY_SIZE(bufW), lenA;
3269 if (!escaped || !escaped_len || !*escaped_len)
3270 return E_INVALIDARG;
3272 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url))
3273 return E_INVALIDARG;
3275 if (flags & URL_ESCAPE_AS_UTF8)
3277 RtlFreeUnicodeString(&urlW);
3278 return E_NOTIMPL;
3281 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER)
3283 escapedW = heap_alloc(lenW * sizeof(WCHAR));
3284 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags);
3287 if (hr == S_OK)
3289 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
3290 if (*escaped_len > lenA)
3292 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
3293 escaped[lenA] = 0;
3294 *escaped_len = lenA;
3296 else
3298 *escaped_len = lenA + 1;
3299 hr = E_POINTER;
3302 if (escapedW != bufW)
3303 heap_free(escapedW);
3304 RtlFreeUnicodeString(&urlW);
3305 return hr;
3308 HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags)
3310 static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
3311 DWORD needed = 0, slashes = 0, int_flags;
3312 WCHAR next[12], *dst, *dst_ptr;
3313 BOOL stop_escaping = FALSE;
3314 PARSEDURLW parsed_url;
3315 const WCHAR *src;
3316 INT i, len;
3317 HRESULT hr;
3319 TRACE("%p, %s, %p, %p, %#x\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags);
3321 if (!url || !escaped_len || !escaped || *escaped_len == 0)
3322 return E_INVALIDARG;
3324 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO |
3325 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8))
3327 FIXME("Unimplemented flags: %08x\n", flags);
3330 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR));
3331 if (!dst_ptr)
3332 return E_OUTOFMEMORY;
3334 /* fix up flags */
3335 if (flags & URL_ESCAPE_SPACES_ONLY)
3336 /* if SPACES_ONLY specified, reset the other controls */
3337 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY);
3338 else
3339 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3340 flags |= URL_DONT_ESCAPE_EXTRA_INFO;
3342 int_flags = 0;
3343 if (flags & URL_ESCAPE_SEGMENT_ONLY)
3344 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
3345 else
3347 parsed_url.cbSize = sizeof(parsed_url);
3348 if (ParseURLW(url, &parsed_url) != S_OK)
3349 parsed_url.nScheme = URL_SCHEME_INVALID;
3351 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
3353 if (flags & URL_DONT_ESCAPE_EXTRA_INFO)
3354 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
3356 switch(parsed_url.nScheme) {
3357 case URL_SCHEME_FILE:
3358 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
3359 int_flags &= ~WINE_URL_STOP_ON_HASH;
3360 break;
3362 case URL_SCHEME_HTTP:
3363 case URL_SCHEME_HTTPS:
3364 int_flags |= WINE_URL_BASH_AS_SLASH;
3365 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
3366 int_flags |= WINE_URL_ESCAPE_SLASH;
3367 break;
3369 case URL_SCHEME_MAILTO:
3370 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
3371 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
3372 break;
3374 case URL_SCHEME_INVALID:
3375 break;
3377 case URL_SCHEME_FTP:
3378 default:
3379 if(parsed_url.pszSuffix[0] != '/')
3380 int_flags |= WINE_URL_ESCAPE_SLASH;
3381 break;
3385 for (src = url; *src; )
3387 WCHAR cur = *src;
3388 len = 0;
3390 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1)
3392 int localhost_len = ARRAY_SIZE(localhost) - 1;
3393 while (cur == '/' || cur == '\\')
3395 slashes++;
3396 cur = *++src;
3398 if (slashes == 2 && !wcsnicmp(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
3399 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
3400 src += localhost_len + 1;
3401 slashes = 3;
3404 switch (slashes)
3406 case 1:
3407 case 3:
3408 next[0] = next[1] = next[2] = '/';
3409 len = 3;
3410 break;
3411 case 0:
3412 len = 0;
3413 break;
3414 default:
3415 next[0] = next[1] = '/';
3416 len = 2;
3417 break;
3420 if (len == 0)
3422 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
3423 stop_escaping = TRUE;
3425 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
3426 stop_escaping = TRUE;
3428 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
3430 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping)
3432 if (flags & URL_ESCAPE_AS_UTF8)
3434 char utf[16];
3436 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff))
3438 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL);
3439 src++;
3441 else
3442 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL);
3444 if (!len)
3446 utf[0] = 0xef;
3447 utf[1] = 0xbf;
3448 utf[2] = 0xbd;
3449 len = 3;
3452 for (i = 0; i < len; ++i)
3454 next[i*3+0] = '%';
3455 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf];
3456 next[i*3+2] = hexDigits[utf[i] & 0xf];
3458 len *= 3;
3460 else
3462 next[0] = '%';
3463 next[1] = hexDigits[(cur >> 4) & 0xf];
3464 next[2] = hexDigits[cur & 0xf];
3465 len = 3;
3468 else
3470 next[0] = cur;
3471 len = 1;
3473 src++;
3476 if (needed + len <= *escaped_len)
3478 memcpy(dst, next, len*sizeof(WCHAR));
3479 dst += len;
3481 needed += len;
3484 if (needed < *escaped_len)
3486 *dst = '\0';
3487 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR));
3488 hr = S_OK;
3490 else
3492 needed++; /* add one for the '\0' */
3493 hr = E_POINTER;
3495 *escaped_len = needed;
3497 heap_free(dst_ptr);
3498 return hr;
3501 HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags)
3503 LPWSTR url, canonical;
3504 HRESULT hr;
3506 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags);
3508 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3509 return E_INVALIDARG;
3511 url = heap_strdupAtoW(src_url);
3512 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR));
3513 if (!url || !canonical)
3515 heap_free(url);
3516 heap_free(canonical);
3517 return E_OUTOFMEMORY;
3520 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags);
3521 if (hr == S_OK)
3522 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL);
3524 heap_free(url);
3525 heap_free(canonical);
3526 return hr;
3529 HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags)
3531 static const WCHAR wszFile[] = {'f','i','l','e',':'};
3532 static const WCHAR wszRes[] = {'r','e','s',':'};
3533 static const WCHAR wszHttp[] = {'h','t','t','p',':'};
3534 static const WCHAR wszLocalhost[] = {'l','o','c','a','l','h','o','s','t'};
3535 static const WCHAR wszFilePrefix[] = {'f','i','l','e',':','/','/','/'};
3536 WCHAR *url_copy, *url, *wk2, *mp, *mp2;
3537 DWORD nByteLen, nLen, nWkLen;
3538 const WCHAR *wk1, *root;
3539 DWORD escape_flags;
3540 WCHAR slash = '\0';
3541 HRESULT hr = S_OK;
3542 BOOL is_file_url;
3543 INT state;
3545 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags);
3547 if (!src_url || !canonicalized || !canonicalized || !*canonicalized_len)
3548 return E_INVALIDARG;
3550 if (!*src_url)
3552 *canonicalized = 0;
3553 return S_OK;
3556 /* Remove '\t' characters from URL */
3557 nByteLen = (lstrlenW(src_url) + 1) * sizeof(WCHAR); /* length in bytes */
3558 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
3559 if(!url)
3560 return E_OUTOFMEMORY;
3562 wk1 = src_url;
3563 wk2 = url;
3566 while(*wk1 == '\t')
3567 wk1++;
3568 *wk2++ = *wk1;
3569 } while (*wk1++);
3571 /* Allocate memory for simplified URL (before escaping) */
3572 nByteLen = (wk2-url)*sizeof(WCHAR);
3573 url_copy = heap_alloc(nByteLen + sizeof(wszFilePrefix) + sizeof(WCHAR));
3574 if (!url_copy)
3576 heap_free(url);
3577 return E_OUTOFMEMORY;
3580 is_file_url = !wcsncmp(wszFile, url, ARRAY_SIZE(wszFile));
3582 if ((nByteLen >= sizeof(wszHttp) && !memcmp(wszHttp, url, sizeof(wszHttp))) || is_file_url)
3583 slash = '/';
3585 if ((flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
3586 slash = '\\';
3588 if (nByteLen >= sizeof(wszRes) && !memcmp(wszRes, url, sizeof(wszRes)))
3590 flags &= ~URL_FILE_USE_PATHURL;
3591 slash = '\0';
3595 * state =
3596 * 0 initial 1,3
3597 * 1 have 2[+] alnum 2,3
3598 * 2 have scheme (found :) 4,6,3
3599 * 3 failed (no location)
3600 * 4 have // 5,3
3601 * 5 have 1[+] alnum 6,3
3602 * 6 have location (found /) save root location
3605 wk1 = url;
3606 wk2 = url_copy;
3607 state = 0;
3609 /* Assume path */
3610 if (url[1] == ':')
3612 memcpy(wk2, wszFilePrefix, sizeof(wszFilePrefix));
3613 wk2 += ARRAY_SIZE(wszFilePrefix);
3614 if (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
3616 slash = '\\';
3617 --wk2;
3619 else
3620 flags |= URL_ESCAPE_UNSAFE;
3621 state = 5;
3622 is_file_url = TRUE;
3624 else if (url[0] == '/')
3626 state = 5;
3627 is_file_url = TRUE;
3630 while (*wk1)
3632 switch (state)
3634 case 0:
3635 if (!iswalnum(*wk1)) {state = 3; break;}
3636 *wk2++ = *wk1++;
3637 if (!iswalnum(*wk1)) {state = 3; break;}
3638 *wk2++ = *wk1++;
3639 state = 1;
3640 break;
3641 case 1:
3642 *wk2++ = *wk1;
3643 if (*wk1++ == ':') state = 2;
3644 break;
3645 case 2:
3646 *wk2++ = *wk1++;
3647 if (*wk1 != '/') {state = 6; break;}
3648 *wk2++ = *wk1++;
3649 if ((flags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszLocalhost) && is_file_url
3650 && !memcmp(wszLocalhost, wk1, sizeof(wszLocalhost)))
3652 wk1 += ARRAY_SIZE(wszLocalhost);
3653 while (*wk1 == '\\' && (flags & URL_FILE_USE_PATHURL))
3654 wk1++;
3657 if (*wk1 == '/' && (flags & URL_FILE_USE_PATHURL))
3658 wk1++;
3659 else if (is_file_url)
3661 const WCHAR *body = wk1;
3663 while (*body == '/')
3664 ++body;
3666 if (iswalnum(*body) && *(body+1) == ':')
3668 if (!(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)))
3670 if (slash)
3671 *wk2++ = slash;
3672 else
3673 *wk2++ = '/';
3676 else
3678 if (flags & URL_WININET_COMPATIBILITY)
3680 if (*wk1 == '/' && *(wk1 + 1) != '/')
3682 *wk2++ = '\\';
3684 else
3686 *wk2++ = '\\';
3687 *wk2++ = '\\';
3690 else
3692 if (*wk1 == '/' && *(wk1+1) != '/')
3694 if (slash)
3695 *wk2++ = slash;
3696 else
3697 *wk2++ = '/';
3701 wk1 = body;
3703 state = 4;
3704 break;
3705 case 3:
3706 nWkLen = lstrlenW(wk1);
3707 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3708 mp = wk2;
3709 wk1 += nWkLen;
3710 wk2 += nWkLen;
3712 if (slash)
3714 while (mp < wk2)
3716 if (*mp == '/' || *mp == '\\')
3717 *mp = slash;
3718 mp++;
3721 break;
3722 case 4:
3723 if (!iswalnum(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
3725 state = 3;
3726 break;
3728 while (iswalnum(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
3729 *wk2++ = *wk1++;
3730 state = 5;
3731 if (!*wk1)
3733 if (slash)
3734 *wk2++ = slash;
3735 else
3736 *wk2++ = '/';
3738 break;
3739 case 5:
3740 if (*wk1 != '/' && *wk1 != '\\')
3742 state = 3;
3743 break;
3745 while (*wk1 == '/' || *wk1 == '\\')
3747 if (slash)
3748 *wk2++ = slash;
3749 else
3750 *wk2++ = *wk1;
3751 wk1++;
3753 state = 6;
3754 break;
3755 case 6:
3756 if (flags & URL_DONT_SIMPLIFY)
3758 state = 3;
3759 break;
3762 /* Now at root location, cannot back up any more. */
3763 /* "root" will point at the '/' */
3765 root = wk2-1;
3766 while (*wk1)
3768 mp = wcschr(wk1, '/');
3769 mp2 = wcschr(wk1, '\\');
3770 if (mp2 && (!mp || mp2 < mp))
3771 mp = mp2;
3772 if (!mp)
3774 nWkLen = lstrlenW(wk1);
3775 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3776 wk1 += nWkLen;
3777 wk2 += nWkLen;
3778 continue;
3780 nLen = mp - wk1;
3781 if (nLen)
3783 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
3784 wk2 += nLen;
3785 wk1 += nLen;
3787 if (slash)
3788 *wk2++ = slash;
3789 else
3790 *wk2++ = *wk1;
3791 wk1++;
3793 while (*wk1 == '.')
3795 TRACE("found '/.'\n");
3796 if (wk1[1] == '/' || wk1[1] == '\\')
3798 /* case of /./ -> skip the ./ */
3799 wk1 += 2;
3801 else if (wk1[1] == '.' && (wk1[2] == '/' || wk1[2] == '\\' || wk1[2] == '?'
3802 || wk1[2] == '#' || !wk1[2]))
3804 /* case /../ -> need to backup wk2 */
3805 TRACE("found '/../'\n");
3806 *(wk2-1) = '\0'; /* set end of string */
3807 mp = wcsrchr(root, '/');
3808 mp2 = wcsrchr(root, '\\');
3809 if (mp2 && (!mp || mp2 < mp))
3810 mp = mp2;
3811 if (mp && (mp >= root))
3813 /* found valid backup point */
3814 wk2 = mp + 1;
3815 if(wk1[2] != '/' && wk1[2] != '\\')
3816 wk1 += 2;
3817 else
3818 wk1 += 3;
3820 else
3822 /* did not find point, restore '/' */
3823 *(wk2-1) = slash;
3824 break;
3827 else
3828 break;
3831 *wk2 = '\0';
3832 break;
3833 default:
3834 FIXME("how did we get here - state=%d\n", state);
3835 heap_free(url_copy);
3836 heap_free(url);
3837 return E_INVALIDARG;
3839 *wk2 = '\0';
3840 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url), wine_dbgstr_w(url_copy));
3842 nLen = lstrlenW(url_copy);
3843 while ((nLen > 0) && ((url_copy[nLen-1] <= ' ')))
3844 url_copy[--nLen]=0;
3846 if ((flags & URL_UNESCAPE) || ((flags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszFile)
3847 && !memcmp(wszFile, url, sizeof(wszFile))))
3849 UrlUnescapeW(url_copy, NULL, &nLen, URL_UNESCAPE_INPLACE);
3852 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT |
3853 URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_SEGMENT_ONLY);
3855 if (escape_flags)
3857 escape_flags &= ~URL_ESCAPE_UNSAFE;
3858 hr = UrlEscapeW(url_copy, canonicalized, canonicalized_len, escape_flags);
3860 else
3862 /* No escaping needed, just copy the string */
3863 nLen = lstrlenW(url_copy);
3864 if (nLen < *canonicalized_len)
3865 memcpy(canonicalized, url_copy, (nLen + 1)*sizeof(WCHAR));
3866 else
3868 hr = E_POINTER;
3869 nLen++;
3871 *canonicalized_len = nLen;
3874 heap_free(url_copy);
3875 heap_free(url);
3877 if (hr == S_OK)
3878 TRACE("result %s\n", wine_dbgstr_w(canonicalized));
3880 return hr;
3883 HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags)
3885 LPWSTR inW, outW;
3886 HRESULT hr;
3887 DWORD len;
3889 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags);
3891 if (!url || !out || !out_len)
3892 return E_INVALIDARG;
3894 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
3895 outW = inW + INTERNET_MAX_URL_LENGTH;
3897 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
3898 len = INTERNET_MAX_URL_LENGTH;
3900 hr = UrlApplySchemeW(inW, outW, &len, flags);
3901 if (hr != S_OK)
3903 heap_free(inW);
3904 return hr;
3907 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL);
3908 if (len > *out_len)
3910 hr = E_POINTER;
3911 goto cleanup;
3914 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL);
3915 len--;
3917 cleanup:
3918 *out_len = len;
3919 heap_free(inW);
3920 return hr;
3923 static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len)
3925 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH];
3926 DWORD value_len, data_len, dwType, i;
3927 WCHAR Wxx, Wyy;
3928 HKEY newkey;
3929 INT index;
3930 BOOL j;
3932 MultiByteToWideChar(CP_ACP, 0,
3933 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH);
3934 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
3935 index = 0;
3936 while (value_len = data_len = MAX_PATH,
3937 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0)
3939 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data));
3941 j = FALSE;
3942 for (i = 0; i < value_len; ++i)
3944 Wxx = url[i];
3945 Wyy = value[i];
3946 /* remember that TRUE is not-equal */
3947 j = ChrCmpIW(Wxx, Wyy);
3948 if (j) break;
3950 if ((i == value_len) && !j)
3952 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len)
3954 *out_len = lstrlenW(data) + lstrlenW(url) + 1;
3955 RegCloseKey(newkey);
3956 return E_POINTER;
3958 lstrcpyW(out, data);
3959 lstrcatW(out, url);
3960 *out_len = lstrlenW(out);
3961 TRACE("matched and set to %s\n", wine_dbgstr_w(out));
3962 RegCloseKey(newkey);
3963 return S_OK;
3965 index++;
3967 RegCloseKey(newkey);
3968 return E_FAIL;
3971 static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len)
3973 static const WCHAR file_colonW[] = {'f','i','l','e',':',0};
3974 static const WCHAR three_slashesW[] = {'/','/','/',0};
3975 PARSEDURLW parsed_url;
3976 WCHAR *new_url;
3977 DWORD needed;
3978 HRESULT hr;
3980 parsed_url.cbSize = sizeof(parsed_url);
3981 if (ParseURLW(path, &parsed_url) == S_OK)
3983 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1)
3985 needed = lstrlenW(path);
3986 if (needed >= *url_len)
3988 *url_len = needed + 1;
3989 return E_POINTER;
3991 else
3993 *url_len = needed;
3994 return S_FALSE;
3999 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */
4000 lstrcpyW(new_url, file_colonW);
4001 if (iswalpha(path[0]) && path[1] == ':')
4002 lstrcatW(new_url, three_slashesW);
4003 lstrcatW(new_url, path);
4004 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT);
4005 heap_free(new_url);
4006 return hr;
4009 static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length)
4011 static const WCHAR prefix_keyW[] =
4012 {'S','o','f','t','w','a','r','e',
4013 '\\','M','i','c','r','o','s','o','f','t',
4014 '\\','W','i','n','d','o','w','s',
4015 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
4016 '\\','U','R','L',
4017 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
4018 DWORD data_len, dwType;
4019 WCHAR data[MAX_PATH];
4020 HKEY newkey;
4022 /* get and prepend default */
4023 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
4024 data_len = sizeof(data);
4025 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len);
4026 RegCloseKey(newkey);
4027 if (lstrlenW(data) + lstrlenW(url) + 1 > *length)
4029 *length = lstrlenW(data) + lstrlenW(url) + 1;
4030 return E_POINTER;
4032 lstrcpyW(out, data);
4033 lstrcatW(out, url);
4034 *length = lstrlenW(out);
4035 TRACE("used default %s\n", wine_dbgstr_w(out));
4036 return S_OK;
4039 HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags)
4041 PARSEDURLW in_scheme;
4042 DWORD res1;
4043 HRESULT hr;
4045 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags);
4047 if (!url || !out || !length)
4048 return E_INVALIDARG;
4050 if (flags & URL_APPLY_GUESSFILE)
4052 if (*length > 1 && ':' == url[1])
4054 res1 = *length;
4055 hr = url_create_from_path(url, out, &res1);
4056 if (hr == S_OK || hr == E_POINTER)
4058 *length = res1;
4059 return hr;
4061 else if (hr == S_FALSE)
4063 return hr;
4068 in_scheme.cbSize = sizeof(in_scheme);
4069 /* See if the base has a scheme */
4070 res1 = ParseURLW(url, &in_scheme);
4071 if (res1)
4073 /* no scheme in input, need to see if we need to guess */
4074 if (flags & URL_APPLY_GUESSSCHEME)
4076 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL)
4077 return hr;
4081 /* If we are here, then either invalid scheme,
4082 * or no scheme and can't/failed guess.
4084 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT))
4085 return url_apply_default_scheme(url, out, length);
4087 return S_FALSE;
4090 INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash)
4092 INT ret, len, len1, len2;
4094 if (!ignore_slash)
4095 return strcmp(url1, url2);
4096 len1 = strlen(url1);
4097 if (url1[len1-1] == '/') len1--;
4098 len2 = strlen(url2);
4099 if (url2[len2-1] == '/') len2--;
4100 if (len1 == len2)
4101 return strncmp(url1, url2, len1);
4102 len = min(len1, len2);
4103 ret = strncmp(url1, url2, len);
4104 if (ret) return ret;
4105 if (len1 > len2) return 1;
4106 return -1;
4109 INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash)
4111 size_t len, len1, len2;
4112 INT ret;
4114 if (!ignore_slash)
4115 return lstrcmpW(url1, url2);
4116 len1 = lstrlenW(url1);
4117 if (url1[len1-1] == '/') len1--;
4118 len2 = lstrlenW(url2);
4119 if (url2[len2-1] == '/') len2--;
4120 if (len1 == len2)
4121 return wcsncmp(url1, url2, len1);
4122 len = min(len1, len2);
4123 ret = wcsncmp(url1, url2, len);
4124 if (ret) return ret;
4125 if (len1 > len2) return 1;
4126 return -1;
4129 HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars)
4131 DWORD srcLen;
4133 FIXME("%s, %p, %d stub\n", wine_dbgstr_w(url), translatedUrl, maxChars);
4135 if (!url)
4136 return E_FAIL;
4138 srcLen = lstrlenW(url) + 1;
4140 /* For now just copy the URL directly */
4141 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
4143 return S_OK;
4146 const char * WINAPI UrlGetLocationA(const char *url)
4148 PARSEDURLA base;
4150 base.cbSize = sizeof(base);
4151 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */
4153 /* if scheme is file: then never return pointer */
4154 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol)))
4155 return NULL;
4157 /* Look for '#' and return its addr */
4158 return strchr(base.pszSuffix, '#');
4161 const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url)
4163 static const WCHAR fileW[] = {'f','i','l','e','\0'};
4164 PARSEDURLW base;
4166 base.cbSize = sizeof(base);
4167 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */
4169 /* if scheme is file: then never return pointer */
4170 if (!wcsncmp(base.pszProtocol, fileW, min(4, base.cchProtocol)))
4171 return NULL;
4173 /* Look for '#' and return its addr */
4174 return wcschr(base.pszSuffix, '#');
4177 HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags)
4179 LPWSTR inW, outW;
4180 DWORD len, len2;
4181 HRESULT hr;
4183 if (!url || !out || !out_len || !*out_len)
4184 return E_INVALIDARG;
4186 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4187 outW = inW + INTERNET_MAX_URL_LENGTH;
4189 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
4191 len = INTERNET_MAX_URL_LENGTH;
4192 hr = UrlGetPartW(inW, outW, &len, part, flags);
4193 if (FAILED(hr))
4195 heap_free(inW);
4196 return hr;
4199 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len, NULL, 0, NULL, NULL);
4200 if (len2 > *out_len)
4202 *out_len = len2 + 1;
4203 heap_free(inW);
4204 return E_POINTER;
4206 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL);
4207 *out_len = len2 - 1;
4208 heap_free(inW);
4209 return hr;
4212 static const WCHAR * scan_url(const WCHAR *start, DWORD *size, enum url_scan_type type)
4214 static DWORD alwayszero = 0;
4215 BOOL cont = TRUE;
4217 *size = 0;
4219 switch (type)
4221 case SCHEME:
4222 while (cont)
4224 if ((iswlower(*start) && iswalpha(*start)) ||
4225 iswdigit(*start) || *start == '+' || *start == '-' || *start == '.')
4227 start++;
4228 (*size)++;
4230 else
4231 cont = FALSE;
4233 if (*start != ':')
4234 *size = 0;
4235 break;
4237 case USERPASS:
4238 while (cont)
4240 if (iswalpha(*start) ||
4241 iswdigit(*start) ||
4242 /* user/password only characters */
4243 (*start == ';') ||
4244 (*start == '?') ||
4245 (*start == '&') ||
4246 (*start == '=') ||
4247 /* *extra* characters */
4248 (*start == '!') ||
4249 (*start == '*') ||
4250 (*start == '\'') ||
4251 (*start == '(') ||
4252 (*start == ')') ||
4253 (*start == ',') ||
4254 /* *safe* characters */
4255 (*start == '$') ||
4256 (*start == '_') ||
4257 (*start == '+') ||
4258 (*start == '-') ||
4259 (*start == '.') ||
4260 (*start == ' '))
4262 start++;
4263 (*size)++;
4265 else if (*start == '%')
4267 if (iswxdigit(*(start + 1)) && iswxdigit(*(start + 2)))
4269 start += 3;
4270 *size += 3;
4272 else
4273 cont = FALSE;
4274 } else
4275 cont = FALSE;
4277 break;
4279 case PORT:
4280 while (cont)
4282 if (iswdigit(*start))
4284 start++;
4285 (*size)++;
4287 else
4288 cont = FALSE;
4290 break;
4292 case HOST:
4293 while (cont)
4295 if (iswalnum(*start) || *start == '-' || *start == '.' || *start == ' ' || *start == '*')
4297 start++;
4298 (*size)++;
4300 else
4301 cont = FALSE;
4303 break;
4305 default:
4306 FIXME("unknown type %d\n", type);
4307 return (LPWSTR)&alwayszero;
4310 return start;
4313 static LONG parse_url(const WCHAR *url, struct parsed_url *pl)
4315 const WCHAR *work;
4317 memset(pl, 0, sizeof(*pl));
4318 pl->scheme = url;
4319 work = scan_url(pl->scheme, &pl->scheme_len, SCHEME);
4320 if (!*work || (*work != ':')) goto ErrorExit;
4321 work++;
4322 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
4324 pl->username = work + 2;
4325 work = scan_url(pl->username, &pl->username_len, USERPASS);
4326 if (*work == ':' )
4328 /* parse password */
4329 work++;
4330 pl->password = work;
4331 work = scan_url(pl->password, &pl->password_len, USERPASS);
4332 if (*work != '@')
4334 /* what we just parsed must be the hostname and port
4335 * so reset pointers and clear then let it parse */
4336 pl->username_len = pl->password_len = 0;
4337 work = pl->username - 1;
4338 pl->username = pl->password = 0;
4341 else if (*work == '@')
4343 /* no password */
4344 pl->password_len = 0;
4345 pl->password = 0;
4347 else if (!*work || *work == '/' || *work == '.')
4349 /* what was parsed was hostname, so reset pointers and let it parse */
4350 pl->username_len = pl->password_len = 0;
4351 work = pl->username - 1;
4352 pl->username = pl->password = 0;
4354 else goto ErrorExit;
4356 /* now start parsing hostname or hostnumber */
4357 work++;
4358 pl->hostname = work;
4359 work = scan_url(pl->hostname, &pl->hostname_len, HOST);
4360 if (*work == ':')
4362 /* parse port */
4363 work++;
4364 pl->port = work;
4365 work = scan_url(pl->port, &pl->port_len, PORT);
4367 if (*work == '/')
4369 /* see if query string */
4370 pl->query = wcschr(work, '?');
4371 if (pl->query) pl->query_len = lstrlenW(pl->query);
4373 SuccessExit:
4374 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
4375 pl->scheme, pl->scheme_len, pl->username, pl->username_len, pl->password, pl->password_len, pl->hostname,
4376 pl->hostname_len, pl->port, pl->port_len, pl->query, pl->query_len);
4378 return S_OK;
4380 ErrorExit:
4381 FIXME("failed to parse %s\n", debugstr_w(url));
4382 return E_INVALIDARG;
4385 HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags)
4387 DWORD scheme, size, schsize;
4388 LPCWSTR addr, schaddr;
4389 struct parsed_url pl;
4390 HRESULT hr;
4392 TRACE("%s, %p, %p(%d), %#x, %#x\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags);
4394 if (!url || !out || !out_len || !*out_len)
4395 return E_INVALIDARG;
4397 *out = '\0';
4399 addr = wcschr(url, ':');
4400 if (!addr)
4401 scheme = URL_SCHEME_UNKNOWN;
4402 else
4403 scheme = get_scheme_code(url, addr - url);
4405 hr = parse_url(url, &pl);
4407 switch (part)
4409 case URL_PART_SCHEME:
4410 if (!pl.scheme_len)
4412 *out_len = 0;
4413 return S_FALSE;
4415 addr = pl.scheme;
4416 size = pl.scheme_len;
4417 break;
4419 case URL_PART_HOSTNAME:
4420 switch (scheme)
4422 case URL_SCHEME_FTP:
4423 case URL_SCHEME_HTTP:
4424 case URL_SCHEME_GOPHER:
4425 case URL_SCHEME_TELNET:
4426 case URL_SCHEME_FILE:
4427 case URL_SCHEME_HTTPS:
4428 break;
4429 default:
4430 *out_len = 0;
4431 return E_FAIL;
4434 if (scheme == URL_SCHEME_FILE && (!pl.hostname_len || (pl.hostname_len == 1 && *(pl.hostname + 1) == ':')))
4436 *out_len = 0;
4437 return S_FALSE;
4440 if (!pl.hostname_len)
4442 *out_len = 0;
4443 return S_FALSE;
4445 addr = pl.hostname;
4446 size = pl.hostname_len;
4447 break;
4449 case URL_PART_USERNAME:
4450 if (!pl.username_len)
4452 *out_len = 0;
4453 return S_FALSE;
4455 addr = pl.username;
4456 size = pl.username_len;
4457 break;
4459 case URL_PART_PASSWORD:
4460 if (!pl.password_len)
4462 *out_len = 0;
4463 return S_FALSE;
4465 addr = pl.password;
4466 size = pl.password_len;
4467 break;
4469 case URL_PART_PORT:
4470 if (!pl.port_len)
4472 *out_len = 0;
4473 return S_FALSE;
4475 addr = pl.port;
4476 size = pl.port_len;
4477 break;
4479 case URL_PART_QUERY:
4480 if (!pl.query_len)
4482 *out_len = 0;
4483 return S_FALSE;
4485 addr = pl.query;
4486 size = pl.query_len;
4487 break;
4489 default:
4490 *out_len = 0;
4491 return E_INVALIDARG;
4494 if (flags == URL_PARTFLAG_KEEPSCHEME)
4496 if (!pl.scheme || !pl.scheme_len)
4498 *out_len = 0;
4499 return E_FAIL;
4501 schaddr = pl.scheme;
4502 schsize = pl.scheme_len;
4503 if (*out_len < schsize + size + 2)
4505 *out_len = schsize + size + 2;
4506 return E_POINTER;
4508 memcpy(out, schaddr, schsize*sizeof(WCHAR));
4509 out[schsize] = ':';
4510 memcpy(out + schsize+1, addr, size*sizeof(WCHAR));
4511 out[schsize+1+size] = 0;
4512 *out_len = schsize + 1 + size;
4514 else
4516 if (*out_len < size + 1)
4518 *out_len = size + 1;
4519 return E_POINTER;
4521 memcpy(out, addr, size*sizeof(WCHAR));
4522 out[size] = 0;
4523 *out_len = size;
4525 TRACE("len=%d %s\n", *out_len, wine_dbgstr_w(out));
4527 return hr;
4530 BOOL WINAPI UrlIsA(const char *url, URLIS Urlis)
4532 const char *last;
4533 PARSEDURLA base;
4535 TRACE("%s, %d\n", debugstr_a(url), Urlis);
4537 if (!url)
4538 return FALSE;
4540 switch (Urlis) {
4542 case URLIS_OPAQUE:
4543 base.cbSize = sizeof(base);
4544 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */
4545 switch (base.nScheme)
4547 case URL_SCHEME_MAILTO:
4548 case URL_SCHEME_SHELL:
4549 case URL_SCHEME_JAVASCRIPT:
4550 case URL_SCHEME_VBSCRIPT:
4551 case URL_SCHEME_ABOUT:
4552 return TRUE;
4554 return FALSE;
4556 case URLIS_FILEURL:
4557 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL);
4559 case URLIS_DIRECTORY:
4560 last = url + strlen(url) - 1;
4561 return (last >= url && (*last == '/' || *last == '\\' ));
4563 case URLIS_URL:
4564 return PathIsURLA(url);
4566 case URLIS_NOHISTORY:
4567 case URLIS_APPLIABLE:
4568 case URLIS_HASQUERY:
4569 default:
4570 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis);
4573 return FALSE;
4576 BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis)
4578 static const WCHAR file_colon[] = {'f','i','l','e',':',0};
4579 const WCHAR *last;
4580 PARSEDURLW base;
4582 TRACE("%s, %d\n", debugstr_w(url), Urlis);
4584 if (!url)
4585 return FALSE;
4587 switch (Urlis)
4589 case URLIS_OPAQUE:
4590 base.cbSize = sizeof(base);
4591 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */
4592 switch (base.nScheme)
4594 case URL_SCHEME_MAILTO:
4595 case URL_SCHEME_SHELL:
4596 case URL_SCHEME_JAVASCRIPT:
4597 case URL_SCHEME_VBSCRIPT:
4598 case URL_SCHEME_ABOUT:
4599 return TRUE;
4601 return FALSE;
4603 case URLIS_FILEURL:
4604 return (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, file_colon, 5) == CSTR_EQUAL);
4606 case URLIS_DIRECTORY:
4607 last = url + lstrlenW(url) - 1;
4608 return (last >= url && (*last == '/' || *last == '\\'));
4610 case URLIS_URL:
4611 return PathIsURLW(url);
4613 case URLIS_NOHISTORY:
4614 case URLIS_APPLIABLE:
4615 case URLIS_HASQUERY:
4616 default:
4617 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis);
4620 return FALSE;
4623 BOOL WINAPI UrlIsOpaqueA(const char *url)
4625 return UrlIsA(url, URLIS_OPAQUE);
4628 BOOL WINAPI UrlIsOpaqueW(const WCHAR *url)
4630 return UrlIsW(url, URLIS_OPAQUE);
4633 BOOL WINAPI UrlIsNoHistoryA(const char *url)
4635 return UrlIsA(url, URLIS_NOHISTORY);
4638 BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url)
4640 return UrlIsW(url, URLIS_NOHISTORY);
4643 HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved)
4645 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
4646 DWORD lenW = ARRAY_SIZE(bufW), lenA;
4647 UNICODE_STRING pathW;
4648 WCHAR *urlW = bufW;
4649 HRESULT hr;
4651 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path))
4652 return E_INVALIDARG;
4654 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER)
4656 urlW = heap_alloc(lenW * sizeof(WCHAR));
4657 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved);
4660 if (SUCCEEDED(hr))
4662 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
4663 if (*url_len > lenA)
4665 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR));
4666 url[lenA] = 0;
4667 *url_len = lenA;
4669 else
4671 *url_len = lenA + 1;
4672 hr = E_POINTER;
4675 if (urlW != bufW)
4676 heap_free(urlW);
4677 RtlFreeUnicodeString(&pathW);
4678 return hr;
4681 HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved)
4683 HRESULT hr;
4685 TRACE("%s, %p, %p, %#x\n", debugstr_w(path), url, url_len, reserved);
4687 if (reserved || !url || !url_len)
4688 return E_INVALIDARG;
4690 hr = url_create_from_path(path, url, url_len);
4691 if (hr == S_FALSE)
4692 lstrcpyW(url, path);
4694 return hr;
4697 HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags)
4699 WCHAR *baseW, *relativeW, *combinedW;
4700 DWORD len, len2;
4701 HRESULT hr;
4703 TRACE("%s, %s, %d, %#x\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags);
4705 if (!base || !relative || !combined_len)
4706 return E_INVALIDARG;
4708 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4709 relativeW = baseW + INTERNET_MAX_URL_LENGTH;
4710 combinedW = relativeW + INTERNET_MAX_URL_LENGTH;
4712 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH);
4713 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH);
4714 len = *combined_len;
4716 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags);
4717 if (hr != S_OK)
4719 *combined_len = len;
4720 heap_free(baseW);
4721 return hr;
4724 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL);
4725 if (len2 > *combined_len)
4727 *combined_len = len2;
4728 heap_free(baseW);
4729 return E_POINTER;
4731 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL);
4732 *combined_len = len2;
4733 heap_free(baseW);
4734 return S_OK;
4737 HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags)
4739 static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
4740 static const WCHAR fragquerystr[] = {'#','?',0};
4741 DWORD i, len, process_case = 0, myflags, sizeloc = 0;
4742 LPWSTR work, preliminary, mbase, mrelative;
4743 PARSEDURLW base, relative;
4744 HRESULT hr;
4746 TRACE("%s, %s, %d, %#x\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags);
4748 if (!baseW || !relativeW || !combined_len)
4749 return E_INVALIDARG;
4751 base.cbSize = sizeof(base);
4752 relative.cbSize = sizeof(relative);
4754 /* Get space for duplicates of the input and the output */
4755 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4756 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
4757 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
4758 *preliminary = '\0';
4760 /* Canonicalize the base input prior to looking for the scheme */
4761 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
4762 len = INTERNET_MAX_URL_LENGTH;
4763 UrlCanonicalizeW(baseW, mbase, &len, myflags);
4765 /* Canonicalize the relative input prior to looking for the scheme */
4766 len = INTERNET_MAX_URL_LENGTH;
4767 UrlCanonicalizeW(relativeW, mrelative, &len, myflags);
4769 /* See if the base has a scheme */
4770 if (ParseURLW(mbase, &base) != S_OK)
4772 /* If base has no scheme return relative. */
4773 TRACE("no scheme detected in Base\n");
4774 process_case = 1;
4776 else do
4778 BOOL manual_search = FALSE;
4780 work = (LPWSTR)base.pszProtocol;
4781 for (i = 0; i < base.cchProtocol; ++i)
4782 work[i] = towlower(work[i]);
4784 /* mk is a special case */
4785 if (base.nScheme == URL_SCHEME_MK)
4787 static const WCHAR wsz[] = {':',':',0};
4788 WCHAR *ptr = wcsstr(base.pszSuffix, wsz);
4789 if (ptr)
4791 int delta;
4793 ptr += 2;
4794 delta = ptr-base.pszSuffix;
4795 base.cchProtocol += delta;
4796 base.pszSuffix += delta;
4797 base.cchSuffix -= delta;
4800 else
4802 /* get size of location field (if it exists) */
4803 work = (LPWSTR)base.pszSuffix;
4804 sizeloc = 0;
4805 if (*work++ == '/')
4807 if (*work++ == '/')
4809 /* At this point have start of location and
4810 * it ends at next '/' or end of string.
4812 while (*work && (*work != '/')) work++;
4813 sizeloc = (DWORD)(work - base.pszSuffix);
4818 /* If there is a '?', then the remaining part can only contain a
4819 * query string or fragment, so start looking for the last leaf
4820 * from the '?'. Otherwise, if there is a '#' and the characters
4821 * immediately preceding it are ".htm[l]", then begin looking for
4822 * the last leaf starting from the '#'. Otherwise the '#' is not
4823 * meaningful and just start looking from the end. */
4824 if ((work = wcspbrk(base.pszSuffix + sizeloc, fragquerystr)))
4826 static const WCHAR htmlW[] = {'.','h','t','m','l'};
4827 static const WCHAR htmW[] = {'.','h','t','m'};
4829 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
4830 manual_search = TRUE;
4831 else if (work - base.pszSuffix > ARRAY_SIZE(htmW))
4833 work -= ARRAY_SIZE(htmW);
4834 if (!wcsnicmp(work, htmW, ARRAY_SIZE(htmW)))
4835 manual_search = TRUE;
4836 work += ARRAY_SIZE(htmW);
4839 if (!manual_search && work - base.pszSuffix > ARRAY_SIZE(htmlW))
4841 work -= ARRAY_SIZE(htmlW);
4842 if (!wcsnicmp(work, htmlW, ARRAY_SIZE(htmlW)))
4843 manual_search = TRUE;
4844 work += ARRAY_SIZE(htmlW);
4848 if (manual_search)
4850 /* search backwards starting from the current position */
4851 while (*work != '/' && work > base.pszSuffix + sizeloc)
4852 --work;
4853 base.cchSuffix = work - base.pszSuffix + 1;
4855 else
4857 /* search backwards starting from the end of the string */
4858 work = wcsrchr((base.pszSuffix+sizeloc), '/');
4859 if (work)
4861 len = (DWORD)(work - base.pszSuffix + 1);
4862 base.cchSuffix = len;
4864 else
4865 base.cchSuffix = sizeloc;
4869 * At this point:
4870 * .pszSuffix points to location (starting with '//')
4871 * .cchSuffix length of location (above) and rest less the last
4872 * leaf (if any)
4873 * sizeloc length of location (above) up to but not including
4874 * the last '/'
4877 if (ParseURLW(mrelative, &relative) != S_OK)
4879 /* No scheme in relative */
4880 TRACE("no scheme detected in Relative\n");
4881 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
4882 relative.cchSuffix = lstrlenW(mrelative);
4883 if (*relativeW == ':')
4885 /* Case that is either left alone or uses base. */
4886 if (flags & URL_PLUGGABLE_PROTOCOL)
4888 process_case = 5;
4889 break;
4891 process_case = 1;
4892 break;
4894 if (iswalnum(*mrelative) && *(mrelative + 1) == ':')
4896 /* case that becomes "file:///" */
4897 lstrcpyW(preliminary, myfilestr);
4898 process_case = 1;
4899 break;
4901 if (*mrelative == '/' && *(mrelative+1) == '/')
4903 /* Relative has location and the rest. */
4904 process_case = 3;
4905 break;
4907 if (*mrelative == '/')
4909 /* Relative is root to location. */
4910 process_case = 4;
4911 break;
4913 if (*mrelative == '#')
4915 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#')))
4916 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix);
4918 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
4919 preliminary[work-base.pszProtocol] = '\0';
4920 process_case = 1;
4921 break;
4923 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
4924 break;
4926 else
4928 work = (LPWSTR)relative.pszProtocol;
4929 for (i = 0; i < relative.cchProtocol; ++i)
4930 work[i] = towlower(work[i]);
4933 /* Handle cases where relative has scheme. */
4934 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol))
4936 /* since the schemes are the same */
4937 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4939 /* Relative replaces location and what follows. */
4940 process_case = 3;
4941 break;
4943 if (*relative.pszSuffix == '/')
4945 /* Relative is root to location */
4946 process_case = 4;
4947 break;
4949 /* replace either just location if base's location starts with a
4950 * slash or otherwise everything */
4951 process_case = (*base.pszSuffix == '/') ? 5 : 1;
4952 break;
4955 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4957 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4958 process_case = 2;
4959 break;
4961 process_case = 1;
4962 break;
4963 } while (FALSE); /* a little trick to allow easy exit from nested if's */
4965 hr = S_OK;
4966 switch (process_case)
4968 case 1:
4969 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4970 lstrcatW(preliminary, mrelative);
4971 break;
4973 case 2:
4974 /* Relative replaces scheme and location */
4975 lstrcpyW(preliminary, mrelative);
4976 break;
4978 case 3:
4979 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4980 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
4981 work = preliminary + base.cchProtocol + 1;
4982 lstrcpyW(work, relative.pszSuffix);
4983 break;
4985 case 4:
4986 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4987 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
4988 work = preliminary + base.cchProtocol + 1 + sizeloc;
4989 if (flags & URL_PLUGGABLE_PROTOCOL)
4990 *(work++) = '/';
4991 lstrcpyW(work, relative.pszSuffix);
4992 break;
4994 case 5:
4995 /* Return the base without its document (if any) and append relative after its scheme. */
4996 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR));
4997 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1;
4998 if (*work++ != '/')
4999 *(work++) = '/';
5000 lstrcpyW(work, relative.pszSuffix);
5001 break;
5003 default:
5004 FIXME("Unexpected case %d.\n", process_case);
5005 hr = E_INVALIDARG;
5008 if (hr == S_OK)
5010 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
5011 if (*combined_len == 0)
5012 *combined_len = 1;
5013 hr = UrlCanonicalizeW(preliminary, mrelative, combined_len, flags & ~URL_FILE_USE_PATHURL);
5014 if (SUCCEEDED(hr) && combined)
5015 lstrcpyW(combined, mrelative);
5017 TRACE("return-%d len=%d, %s\n", process_case, *combined_len, debugstr_w(combined));
5020 heap_free(preliminary);
5021 return hr;
5024 HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len)
5026 INT src_count = src_len - 1, dest_count = dest_len - 1;
5028 if (!src || !dest)
5029 return E_INVALIDARG;
5031 while (dest_count >= 0)
5033 dest[dest_count] = (dest_count & 0xff);
5034 dest_count--;
5037 while (src_count >= 0)
5039 dest_count = dest_len - 1;
5040 while (dest_count >= 0)
5042 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]];
5043 dest_count--;
5045 src_count--;
5048 return S_OK;
5051 HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len)
5053 __TRY
5055 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len);
5057 __EXCEPT_PAGE_FAULT
5059 return E_INVALIDARG;
5061 __ENDTRY
5062 return S_OK;
5065 HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len)
5067 char urlA[MAX_PATH];
5069 TRACE("%s, %p, %d\n", debugstr_w(url), dest, dest_len);
5071 __TRY
5073 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL);
5074 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len);
5076 __EXCEPT_PAGE_FAULT
5078 return E_INVALIDARG;
5080 __ENDTRY
5081 return S_OK;
5084 BOOL WINAPI IsInternetESCEnabled(void)
5086 FIXME(": stub\n");
5087 return FALSE;