kernel32/tests/pipe: Enable compilation with long types.
[wine.git] / dlls / kernelbase / path.c
blobf83c68e14f5515f89445256a66837461302d38cd
1 /*
2 * Copyright 2018 Nikolay Sivov
3 * Copyright 2018 Zhiyi Zhang
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 #include <stdarg.h>
21 #include <string.h>
23 #include "windef.h"
24 #include "winbase.h"
25 #include "pathcch.h"
26 #include "strsafe.h"
27 #include "shlwapi.h"
28 #include "wininet.h"
29 #include "intshcut.h"
30 #include "winternl.h"
32 #include "kernelbase.h"
33 #include "wine/exception.h"
34 #include "wine/debug.h"
35 #include "wine/heap.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(path);
39 static const char hexDigits[] = "0123456789ABCDEF";
41 static const unsigned char hashdata_lookup[256] =
43 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33,
44 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41,
45 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c,
46 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90,
47 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe,
48 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd,
49 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d,
50 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd,
51 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2,
52 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b,
53 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70,
54 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b,
55 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47,
56 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d,
57 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8,
58 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1,
61 struct parsed_url
63 const WCHAR *scheme; /* [out] start of scheme */
64 DWORD scheme_len; /* [out] size of scheme (until colon) */
65 const WCHAR *username; /* [out] start of Username */
66 DWORD username_len; /* [out] size of Username (until ":" or "@") */
67 const WCHAR *password; /* [out] start of Password */
68 DWORD password_len; /* [out] size of Password (until "@") */
69 const WCHAR *hostname; /* [out] start of Hostname */
70 DWORD hostname_len; /* [out] size of Hostname (until ":" or "/") */
71 const WCHAR *port; /* [out] start of Port */
72 DWORD port_len; /* [out] size of Port (until "/" or eos) */
73 const WCHAR *query; /* [out] start of Query */
74 DWORD query_len; /* [out] size of Query (until eos) */
77 static WCHAR *heap_strdupAtoW(const char *str)
79 WCHAR *ret = NULL;
81 if (str)
83 DWORD len;
85 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
86 ret = heap_alloc(len * sizeof(WCHAR));
87 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
90 return ret;
93 static BOOL is_drive_spec( const WCHAR *str )
95 return isalpha( str[0] ) && str[1] == ':';
98 static BOOL is_escaped_drive_spec( const WCHAR *str )
100 return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|');
103 static BOOL is_prefixed_unc(const WCHAR *string)
105 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 );
108 static BOOL is_prefixed_disk(const WCHAR *string)
110 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 );
113 static BOOL is_prefixed_volume(const WCHAR *string)
115 const WCHAR *guid;
116 INT i = 0;
118 if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE;
120 guid = string + 10;
122 while (i <= 37)
124 switch (i)
126 case 0:
127 if (guid[i] != '{') return FALSE;
128 break;
129 case 9:
130 case 14:
131 case 19:
132 case 24:
133 if (guid[i] != '-') return FALSE;
134 break;
135 case 37:
136 if (guid[i] != '}') return FALSE;
137 break;
138 default:
139 if (!isxdigit(guid[i])) return FALSE;
140 break;
142 i++;
145 return TRUE;
148 /* Get the next character beyond end of the segment.
149 Return TRUE if the last segment ends with a backslash */
150 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment)
152 while (*next && *next != '\\') next++;
153 if (*next == '\\')
155 *next_segment = next + 1;
156 return TRUE;
158 else
160 *next_segment = next;
161 return FALSE;
165 /* Find the last character of the root in a path, if there is one, without any segments */
166 static const WCHAR *get_root_end(const WCHAR *path)
168 /* Find path root */
169 if (is_prefixed_volume(path))
170 return path[48] == '\\' ? path + 48 : path + 47;
171 else if (is_prefixed_unc(path))
172 return path + 7;
173 else if (is_prefixed_disk(path))
174 return path[6] == '\\' ? path + 6 : path + 5;
175 /* \\ */
176 else if (path[0] == '\\' && path[1] == '\\')
177 return path + 1;
178 /* \ */
179 else if (path[0] == '\\')
180 return path;
181 /* X:\ */
182 else if (is_drive_spec( path ))
183 return path[2] == '\\' ? path + 2 : path + 1;
184 else
185 return NULL;
188 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
190 WCHAR *buffer, *dst;
191 const WCHAR *src;
192 const WCHAR *root_end;
193 SIZE_T buffer_size, length;
195 TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out);
197 if (!path_in || !path_out
198 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS))
199 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)
200 && !(flags & PATHCCH_ALLOW_LONG_PATHS))
201 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS)))
203 if (path_out) *path_out = NULL;
204 return E_INVALIDARG;
207 length = lstrlenW(path_in);
208 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)))
209 || (length + 1 > PATHCCH_MAX_CCH))
211 *path_out = NULL;
212 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
215 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
216 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
218 /* path length + possible \\?\ addition + possible \ addition + NUL */
219 buffer_size = (length + 6) * sizeof(WCHAR);
220 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size);
221 if (!buffer)
223 *path_out = NULL;
224 return E_OUTOFMEMORY;
227 src = path_in;
228 dst = buffer;
230 root_end = get_root_end(path_in);
231 if (root_end) root_end = buffer + (root_end - path_in);
233 /* Copy path root */
234 if (root_end)
236 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR));
237 src += root_end - buffer + 1;
238 if(PathCchStripPrefix(dst, length + 6) == S_OK)
240 /* Fill in \ in X:\ if the \ is missing */
241 if (is_drive_spec( dst ) && dst[2]!= '\\')
243 dst[2] = '\\';
244 dst[3] = 0;
246 dst = buffer + lstrlenW(buffer);
247 root_end = dst;
249 else
250 dst += root_end - buffer + 1;
253 while (*src)
255 if (src[0] == '.')
257 if (src[1] == '.')
259 /* Keep one . after * */
260 if (dst > buffer && dst[-1] == '*')
262 *dst++ = *src++;
263 continue;
266 /* Keep the .. if not surrounded by \ */
267 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\'))
269 *dst++ = *src++;
270 *dst++ = *src++;
271 continue;
274 /* Remove the \ before .. if the \ is not part of root */
275 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
277 *--dst = '\0';
278 /* Remove characters until a \ is encountered */
279 while (dst > buffer)
281 if (dst[-1] == '\\')
283 *--dst = 0;
284 break;
286 else
287 *--dst = 0;
290 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
291 else if (src[2] == '\\')
292 src++;
294 src += 2;
296 else
298 /* Keep the . if not surrounded by \ */
299 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\'))
301 *dst++ = *src++;
302 continue;
305 /* Remove the \ before . if the \ is not part of root */
306 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--;
307 /* Remove the extra \ after . if the \ before . wasn't deleted */
308 else if (src[1] == '\\')
309 src++;
311 src++;
314 /* If X:\ is not complete, then complete it */
315 if (is_drive_spec( buffer ) && buffer[2] != '\\')
317 root_end = buffer + 2;
318 dst = buffer + 3;
319 buffer[2] = '\\';
320 /* If next character is \, use the \ to fill in */
321 if (src[0] == '\\') src++;
324 /* Copy over */
325 else
326 *dst++ = *src++;
328 /* End the path */
329 *dst = 0;
331 /* Strip multiple trailing . */
332 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
334 while (dst > buffer && dst[-1] == '.')
336 /* Keep a . after * */
337 if (dst - 1 > buffer && dst[-2] == '*')
338 break;
339 /* If . follow a : at the second character, remove the . and add a \ */
340 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1)
341 *--dst = '\\';
342 else
343 *--dst = 0;
347 /* If result path is empty, fill in \ */
348 if (!*buffer)
350 buffer[0] = '\\';
351 buffer[1] = 0;
354 /* Extend the path if needed */
355 length = lstrlenW(buffer);
356 if (((length + 1 > MAX_PATH && is_drive_spec( buffer ))
357 || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))
358 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS))
360 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR));
361 buffer[0] = '\\';
362 buffer[1] = '\\';
363 buffer[2] = '?';
364 buffer[3] = '\\';
367 /* Add a trailing backslash to the path if needed */
368 if (flags & PATHCCH_ENSURE_TRAILING_SLASH)
369 PathCchAddBackslash(buffer, buffer_size);
371 *path_out = buffer;
372 return S_OK;
375 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out)
377 SIZE_T combined_length, length2;
378 WCHAR *combined_path;
379 BOOL add_backslash = FALSE;
380 HRESULT hr;
382 TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out);
384 if ((!path1 && !path2) || !out)
386 if (out) *out = NULL;
387 return E_INVALIDARG;
390 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out);
392 /* If path2 is fully qualified, use path2 only */
393 if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\'))
395 path1 = path2;
396 path2 = NULL;
397 add_backslash = (is_drive_spec(path1) && !path1[2])
398 || (is_prefixed_disk(path1) && !path1[6]);
401 length2 = path2 ? lstrlenW(path2) : 0;
402 /* path1 length + path2 length + possible backslash + NULL */
403 combined_length = lstrlenW(path1) + length2 + 2;
405 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR));
406 if (!combined_path)
408 *out = NULL;
409 return E_OUTOFMEMORY;
412 lstrcpyW(combined_path, path1);
413 PathCchStripPrefix(combined_path, combined_length);
414 if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
416 if (path2 && path2[0])
418 if (path2[0] == '\\' && path2[1] != '\\')
420 PathCchStripToRoot(combined_path, combined_length);
421 path2++;
424 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
425 lstrcatW(combined_path, path2);
428 hr = PathAllocCanonicalize(combined_path, flags, out);
429 HeapFree(GetProcessHeap(), 0, combined_path);
430 return hr;
433 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size)
435 return PathCchAddBackslashEx(path, size, NULL, NULL);
438 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining)
440 BOOL needs_termination;
441 SIZE_T length;
443 TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining);
445 length = lstrlenW(path);
446 needs_termination = size && length && path[length - 1] != '\\';
448 if (length >= (needs_termination ? size - 1 : size))
450 if (endptr) *endptr = NULL;
451 if (remaining) *remaining = 0;
452 return STRSAFE_E_INSUFFICIENT_BUFFER;
455 if (!needs_termination)
457 if (endptr) *endptr = path + length;
458 if (remaining) *remaining = size - length;
459 return S_FALSE;
462 path[length++] = '\\';
463 path[length] = 0;
465 if (endptr) *endptr = path + length;
466 if (remaining) *remaining = size - length;
468 return S_OK;
471 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
473 const WCHAR *existing_extension, *next;
474 SIZE_T path_length, extension_length, dot_length;
475 BOOL has_dot;
476 HRESULT hr;
478 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
480 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG;
482 next = extension;
483 while (*next)
485 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG;
486 next++;
489 has_dot = extension[0] == '.';
491 hr = PathCchFindExtension(path, size, &existing_extension);
492 if (FAILED(hr)) return hr;
493 if (*existing_extension) return S_FALSE;
495 path_length = wcsnlen(path, size);
496 dot_length = has_dot ? 0 : 1;
497 extension_length = lstrlenW(extension);
499 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER;
501 /* If extension is empty or only dot, return S_OK with path unchanged */
502 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK;
504 if (!has_dot)
506 path[path_length] = '.';
507 path_length++;
510 lstrcpyW(path + path_length, extension);
511 return S_OK;
514 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2)
516 TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2));
518 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE);
521 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags)
523 HRESULT hr;
524 WCHAR *result;
526 TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags);
528 if (!path1 || !size) return E_INVALIDARG;
530 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
531 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
532 * buffer for PathCchCombineEx */
533 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
534 if (!result) return E_OUTOFMEMORY;
536 /* Avoid the single backslash behavior with PathCchCombineEx when appending */
537 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++;
539 hr = PathCchCombineEx(result, size, path1, path2, flags);
540 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR));
542 HeapFree(GetProcessHeap(), 0, result);
543 return hr;
546 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in)
548 TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in));
550 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
551 if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\'))
552 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
554 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE);
557 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags)
559 WCHAR *buffer;
560 SIZE_T length;
561 HRESULT hr;
563 TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags);
565 if (!size) return E_INVALIDARG;
567 hr = PathAllocCanonicalize(in, flags, &buffer);
568 if (FAILED(hr)) return hr;
570 length = lstrlenW(buffer);
571 if (size < length + 1)
573 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
574 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\')))
575 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
576 else
577 hr = STRSAFE_E_INSUFFICIENT_BUFFER;
580 if (SUCCEEDED(hr))
582 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
584 /* Fill a backslash at the end of X: */
585 if (is_drive_spec( out ) && !out[2] && size > 3)
587 out[2] = '\\';
588 out[3] = 0;
592 LocalFree(buffer);
593 return hr;
596 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2)
598 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2));
600 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE);
603 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags)
605 HRESULT hr;
606 WCHAR *buffer;
607 SIZE_T length;
609 TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags);
611 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
613 hr = PathAllocCombine(path1, path2, flags, &buffer);
614 if (FAILED(hr))
616 out[0] = 0;
617 return hr;
620 length = lstrlenW(buffer);
621 if (length + 1 > size)
623 out[0] = 0;
624 LocalFree(buffer);
625 return STRSAFE_E_INSUFFICIENT_BUFFER;
627 else
629 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
630 LocalFree(buffer);
631 return S_OK;
635 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension)
637 const WCHAR *lastpoint = NULL;
638 SIZE_T counter = 0;
640 TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension);
642 if (!path || !size || size > PATHCCH_MAX_CCH)
644 *extension = NULL;
645 return E_INVALIDARG;
648 while (*path)
650 if (*path == '\\' || *path == ' ')
651 lastpoint = NULL;
652 else if (*path == '.')
653 lastpoint = path;
655 path++;
656 counter++;
657 if (counter == size || counter == PATHCCH_MAX_CCH)
659 *extension = NULL;
660 return E_INVALIDARG;
664 *extension = lastpoint ? lastpoint : path;
665 return S_OK;
668 BOOL WINAPI PathCchIsRoot(const WCHAR *path)
670 const WCHAR *root_end;
671 const WCHAR *next;
672 BOOL is_unc;
674 TRACE("%s\n", wine_dbgstr_w(path));
676 if (!path || !*path) return FALSE;
678 root_end = get_root_end(path);
679 if (!root_end) return FALSE;
681 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
683 next = root_end + 1;
684 /* No extra segments */
685 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE;
687 /* Has first segment with an ending backslash but no remaining characters */
688 if (get_next_segment(next, &next) && !*next) return FALSE;
689 /* Has first segment with no ending backslash */
690 else if (!*next)
691 return TRUE;
692 /* Has first segment with an ending backslash and has remaining characters*/
693 else
695 next++;
696 /* Second segment must have no backslash and no remaining characters */
697 return !get_next_segment(next, &next) && !*next;
700 else if (*root_end == '\\' && !root_end[1])
701 return TRUE;
702 else
703 return FALSE;
706 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size)
708 WCHAR *path_end;
709 SIZE_T free_size;
711 TRACE("%s %Iu\n", debugstr_w(path), path_size);
713 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size);
716 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size)
718 const WCHAR *root_end;
719 SIZE_T path_length;
721 TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size);
723 if (!path_size || !path_end || !free_size)
725 if (path_end) *path_end = NULL;
726 if (free_size) *free_size = 0;
727 return E_INVALIDARG;
730 path_length = wcsnlen(path, path_size);
731 if (path_length == path_size && !path[path_length]) return E_INVALIDARG;
733 root_end = get_root_end(path);
734 if (path_length > 0 && path[path_length - 1] == '\\')
736 *path_end = path + path_length - 1;
737 *free_size = path_size - path_length + 1;
738 /* If the last character is beyond end of root */
739 if (!root_end || path + path_length - 1 > root_end)
741 path[path_length - 1] = 0;
742 return S_OK;
744 else
745 return S_FALSE;
747 else
749 *path_end = path + path_length;
750 *free_size = path_size - path_length;
751 return S_FALSE;
755 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size)
757 const WCHAR *extension;
758 WCHAR *next;
759 HRESULT hr;
761 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
763 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
765 hr = PathCchFindExtension(path, size, &extension);
766 if (FAILED(hr)) return hr;
768 next = path + (extension - path);
769 while (next - path < size && *next) *next++ = 0;
771 return next == extension ? S_FALSE : S_OK;
774 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size)
776 const WCHAR *root_end = NULL;
777 SIZE_T length;
778 WCHAR *last;
780 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
782 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
784 if (PathCchIsRoot(path)) return S_FALSE;
786 PathCchSkipRoot(path, &root_end);
788 /* The backslash at the end of UNC and \\* are not considered part of root in this case */
789 if (root_end && root_end > path && root_end[-1] == '\\'
790 && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')))
791 root_end--;
793 length = lstrlenW(path);
794 last = path + length - 1;
795 while (last >= path && (!root_end || last >= root_end))
797 if (last - path >= size) return E_INVALIDARG;
799 if (*last == '\\')
801 *last-- = 0;
802 break;
805 *last-- = 0;
808 return last != path + length - 1 ? S_OK : S_FALSE;
811 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
813 HRESULT hr;
815 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
817 hr = PathCchRemoveExtension(path, size);
818 if (FAILED(hr)) return hr;
820 hr = PathCchAddExtension(path, size, extension);
821 return FAILED(hr) ? hr : S_OK;
824 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end)
826 TRACE("%s %p\n", debugstr_w(path), root_end);
828 if (!path || !path[0] || !root_end
829 || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path)
830 && !is_prefixed_disk(path)))
831 return E_INVALIDARG;
833 *root_end = get_root_end(path);
834 if (*root_end)
836 (*root_end)++;
837 if (is_prefixed_unc(path))
839 get_next_segment(*root_end, root_end);
840 get_next_segment(*root_end, root_end);
842 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
844 /* Skip share server */
845 get_next_segment(*root_end, root_end);
846 /* If mount point is empty, don't skip over mount point */
847 if (**root_end != '\\') get_next_segment(*root_end, root_end);
851 return *root_end ? S_OK : E_INVALIDARG;
854 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size)
856 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
858 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
860 if (is_prefixed_unc(path))
862 /* \\?\UNC\a -> \\a */
863 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG;
864 lstrcpyW(path + 2, path + 8);
865 return S_OK;
867 else if (is_prefixed_disk(path))
869 /* \\?\C:\ -> C:\ */
870 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG;
871 lstrcpyW(path, path + 4);
872 return S_OK;
874 else
875 return S_FALSE;
878 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size)
880 const WCHAR *root_end;
881 WCHAR *segment_end;
882 BOOL is_unc;
884 TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
886 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
888 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
889 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
890 * \\\\a\\b\\c -> \\\\a\\b */
891 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
893 root_end = is_unc ? path + 8 : path + 3;
894 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
895 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
897 if (root_end - path >= size) return E_INVALIDARG;
899 segment_end = path + (root_end - path) - 1;
900 *segment_end = 0;
901 return S_OK;
903 else if (PathCchSkipRoot(path, &root_end) == S_OK)
905 if (root_end - path >= size) return E_INVALIDARG;
907 segment_end = path + (root_end - path);
908 if (!*segment_end) return S_FALSE;
910 *segment_end = 0;
911 return S_OK;
913 else
914 return E_INVALIDARG;
917 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server)
919 const WCHAR *result = NULL;
921 TRACE("%s %p\n", wine_dbgstr_w(path), server);
923 if (is_prefixed_unc(path))
924 result = path + 8;
925 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
926 result = path + 2;
928 if (server) *server = result;
929 return !!result;
932 BOOL WINAPI PathIsUNCA(const char *path)
934 TRACE("%s\n", wine_dbgstr_a(path));
936 return path && (path[0] == '\\') && (path[1] == '\\');
939 BOOL WINAPI PathIsUNCW(const WCHAR *path)
941 TRACE("%s\n", wine_dbgstr_w(path));
943 return path && (path[0] == '\\') && (path[1] == '\\');
946 BOOL WINAPI PathIsRelativeA(const char *path)
948 TRACE("%s\n", wine_dbgstr_a(path));
950 if (!path || !*path || IsDBCSLeadByte(*path))
951 return TRUE;
953 return !(*path == '\\' || (*path && path[1] == ':'));
956 BOOL WINAPI PathIsRelativeW(const WCHAR *path)
958 TRACE("%s\n", wine_dbgstr_w(path));
960 if (!path || !*path)
961 return TRUE;
963 return !(*path == '\\' || (*path && path[1] == ':'));
966 BOOL WINAPI PathIsUNCServerShareA(const char *path)
968 BOOL seen_slash = FALSE;
970 TRACE("%s\n", wine_dbgstr_a(path));
972 if (path && *path++ == '\\' && *path++ == '\\')
974 while (*path)
976 if (*path == '\\')
978 if (seen_slash)
979 return FALSE;
980 seen_slash = TRUE;
983 path = CharNextA(path);
987 return seen_slash;
990 BOOL WINAPI PathIsUNCServerShareW(const WCHAR *path)
992 BOOL seen_slash = FALSE;
994 TRACE("%s\n", wine_dbgstr_w(path));
996 if (path && *path++ == '\\' && *path++ == '\\')
998 while (*path)
1000 if (*path == '\\')
1002 if (seen_slash)
1003 return FALSE;
1004 seen_slash = TRUE;
1007 path++;
1011 return seen_slash;
1014 BOOL WINAPI PathIsRootA(const char *path)
1016 TRACE("%s\n", wine_dbgstr_a(path));
1018 if (!path || !*path)
1019 return FALSE;
1021 if (*path == '\\')
1023 if (!path[1])
1024 return TRUE; /* \ */
1025 else if (path[1] == '\\')
1027 BOOL seen_slash = FALSE;
1028 path += 2;
1030 /* Check for UNC root path */
1031 while (*path)
1033 if (*path == '\\')
1035 if (seen_slash)
1036 return FALSE;
1037 seen_slash = TRUE;
1040 path = CharNextA(path);
1043 return TRUE;
1046 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1047 return TRUE; /* X:\ */
1049 return FALSE;
1052 BOOL WINAPI PathIsRootW(const WCHAR *path)
1054 TRACE("%s\n", wine_dbgstr_w(path));
1056 if (!path || !*path)
1057 return FALSE;
1059 if (*path == '\\')
1061 if (!path[1])
1062 return TRUE; /* \ */
1063 else if (path[1] == '\\')
1065 BOOL seen_slash = FALSE;
1067 path += 2;
1068 /* Check for UNC root path */
1069 while (*path)
1071 if (*path == '\\')
1073 if (seen_slash)
1074 return FALSE;
1075 seen_slash = TRUE;
1077 path++;
1080 return TRUE;
1083 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1084 return TRUE; /* X:\ */
1086 return FALSE;
1089 BOOL WINAPI PathRemoveFileSpecA(char *path)
1091 char *filespec = path;
1092 BOOL modified = FALSE;
1094 TRACE("%s\n", wine_dbgstr_a(path));
1096 if (!path)
1097 return FALSE;
1099 /* Skip directory or UNC path */
1100 if (*path == '\\')
1101 filespec = ++path;
1102 if (*path == '\\')
1103 filespec = ++path;
1105 while (*path)
1107 if (*path == '\\')
1108 filespec = path; /* Skip dir */
1109 else if (*path == ':')
1111 filespec = ++path; /* Skip drive */
1112 if (*path == '\\')
1113 filespec++;
1115 if (!(path = CharNextA(path)))
1116 break;
1119 if (*filespec)
1121 *filespec = '\0';
1122 modified = TRUE;
1125 return modified;
1128 BOOL WINAPI PathRemoveFileSpecW(WCHAR *path)
1130 WCHAR *filespec = path;
1131 BOOL modified = FALSE;
1133 TRACE("%s\n", wine_dbgstr_w(path));
1135 if (!path)
1136 return FALSE;
1138 /* Skip directory or UNC path */
1139 if (*path == '\\')
1140 filespec = ++path;
1141 if (*path == '\\')
1142 filespec = ++path;
1144 while (*path)
1146 if (*path == '\\')
1147 filespec = path; /* Skip dir */
1148 else if (*path == ':')
1150 filespec = ++path; /* Skip drive */
1151 if (*path == '\\')
1152 filespec++;
1155 path++;
1158 if (*filespec)
1160 *filespec = '\0';
1161 modified = TRUE;
1164 return modified;
1167 BOOL WINAPI PathStripToRootA(char *path)
1169 TRACE("%s\n", wine_dbgstr_a(path));
1171 if (!path)
1172 return FALSE;
1174 while (!PathIsRootA(path))
1175 if (!PathRemoveFileSpecA(path))
1176 return FALSE;
1178 return TRUE;
1181 BOOL WINAPI PathStripToRootW(WCHAR *path)
1183 TRACE("%s\n", wine_dbgstr_w(path));
1185 if (!path)
1186 return FALSE;
1188 while (!PathIsRootW(path))
1189 if (!PathRemoveFileSpecW(path))
1190 return FALSE;
1192 return TRUE;
1195 LPSTR WINAPI PathAddBackslashA(char *path)
1197 unsigned int len;
1198 char *prev = path;
1200 TRACE("%s\n", wine_dbgstr_a(path));
1202 if (!path || (len = strlen(path)) >= MAX_PATH)
1203 return NULL;
1205 if (len)
1209 path = CharNextA(prev);
1210 if (*path)
1211 prev = path;
1212 } while (*path);
1214 if (*prev != '\\')
1216 *path++ = '\\';
1217 *path = '\0';
1221 return path;
1224 LPWSTR WINAPI PathAddBackslashW(WCHAR *path)
1226 unsigned int len;
1228 TRACE("%s\n", wine_dbgstr_w(path));
1230 if (!path || (len = lstrlenW(path)) >= MAX_PATH)
1231 return NULL;
1233 if (len)
1235 path += len;
1236 if (path[-1] != '\\')
1238 *path++ = '\\';
1239 *path = '\0';
1243 return path;
1246 LPSTR WINAPI PathFindExtensionA(const char *path)
1248 const char *lastpoint = NULL;
1250 TRACE("%s\n", wine_dbgstr_a(path));
1252 if (path)
1254 while (*path)
1256 if (*path == '\\' || *path == ' ')
1257 lastpoint = NULL;
1258 else if (*path == '.')
1259 lastpoint = path;
1260 path = CharNextA(path);
1264 return (LPSTR)(lastpoint ? lastpoint : path);
1267 LPWSTR WINAPI PathFindExtensionW(const WCHAR *path)
1269 const WCHAR *lastpoint = NULL;
1271 TRACE("%s\n", wine_dbgstr_w(path));
1273 if (path)
1275 while (*path)
1277 if (*path == '\\' || *path == ' ')
1278 lastpoint = NULL;
1279 else if (*path == '.')
1280 lastpoint = path;
1281 path++;
1285 return (LPWSTR)(lastpoint ? lastpoint : path);
1288 BOOL WINAPI PathAddExtensionA(char *path, const char *ext)
1290 unsigned int len;
1292 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1294 if (!path || !ext || *(PathFindExtensionA(path)))
1295 return FALSE;
1297 len = strlen(path);
1298 if (len + strlen(ext) >= MAX_PATH)
1299 return FALSE;
1301 strcpy(path + len, ext);
1302 return TRUE;
1305 BOOL WINAPI PathAddExtensionW(WCHAR *path, const WCHAR *ext)
1307 unsigned int len;
1309 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1311 if (!path || !ext || *(PathFindExtensionW(path)))
1312 return FALSE;
1314 len = lstrlenW(path);
1315 if (len + lstrlenW(ext) >= MAX_PATH)
1316 return FALSE;
1318 lstrcpyW(path + len, ext);
1319 return TRUE;
1322 BOOL WINAPI PathCanonicalizeW(WCHAR *buffer, const WCHAR *path)
1324 const WCHAR *src = path;
1325 WCHAR *dst = buffer;
1327 TRACE("%p, %s\n", buffer, wine_dbgstr_w(path));
1329 if (dst)
1330 *dst = '\0';
1332 if (!dst || !path)
1334 SetLastError(ERROR_INVALID_PARAMETER);
1335 return FALSE;
1338 if (!*path)
1340 *buffer++ = '\\';
1341 *buffer = '\0';
1342 return TRUE;
1345 /* Copy path root */
1346 if (*src == '\\')
1348 *dst++ = *src++;
1350 else if (*src && src[1] == ':')
1352 /* X:\ */
1353 *dst++ = *src++;
1354 *dst++ = *src++;
1355 if (*src == '\\')
1356 *dst++ = *src++;
1359 /* Canonicalize the rest of the path */
1360 while (*src)
1362 if (*src == '.')
1364 if (src[1] == '\\' && (src == path || src[-1] == '\\' || src[-1] == ':'))
1366 src += 2; /* Skip .\ */
1368 else if (src[1] == '.' && dst != buffer && dst[-1] == '\\')
1370 /* \.. backs up a directory, over the root if it has no \ following X:.
1371 * .. is ignored if it would remove a UNC server name or initial \\
1373 if (dst != buffer)
1375 *dst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
1376 if (dst > buffer + 1 && dst[-1] == '\\' && (dst[-2] != '\\' || dst > buffer + 2))
1378 if (dst[-2] == ':' && (dst > buffer + 3 || dst[-3] == ':'))
1380 dst -= 2;
1381 while (dst > buffer && *dst != '\\')
1382 dst--;
1383 if (*dst == '\\')
1384 dst++; /* Reset to last '\' */
1385 else
1386 dst = buffer; /* Start path again from new root */
1388 else if (dst[-2] != ':' && !PathIsUNCServerShareW(buffer))
1389 dst -= 2;
1391 while (dst > buffer && *dst != '\\')
1392 dst--;
1393 if (dst == buffer)
1395 *dst++ = '\\';
1396 src++;
1399 src += 2; /* Skip .. in src path */
1401 else
1402 *dst++ = *src++;
1404 else
1405 *dst++ = *src++;
1408 /* Append \ to naked drive specs */
1409 if (dst - buffer == 2 && dst[-1] == ':')
1410 *dst++ = '\\';
1411 *dst++ = '\0';
1412 return TRUE;
1415 BOOL WINAPI PathCanonicalizeA(char *buffer, const char *path)
1417 WCHAR pathW[MAX_PATH], bufferW[MAX_PATH];
1418 BOOL ret;
1419 int len;
1421 TRACE("%p, %s\n", buffer, wine_dbgstr_a(path));
1423 if (buffer)
1424 *buffer = '\0';
1426 if (!buffer || !path)
1428 SetLastError(ERROR_INVALID_PARAMETER);
1429 return FALSE;
1432 len = MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, ARRAY_SIZE(pathW));
1433 if (!len)
1434 return FALSE;
1436 ret = PathCanonicalizeW(bufferW, pathW);
1437 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, 0, 0);
1439 return ret;
1442 WCHAR * WINAPI PathCombineW(WCHAR *dst, const WCHAR *dir, const WCHAR *file)
1444 BOOL use_both = FALSE, strip = FALSE;
1445 WCHAR tmp[MAX_PATH];
1447 TRACE("%p, %s, %s\n", dst, wine_dbgstr_w(dir), wine_dbgstr_w(file));
1449 /* Invalid parameters */
1450 if (!dst)
1451 return NULL;
1453 if (!dir && !file)
1455 dst[0] = 0;
1456 return NULL;
1459 if ((!file || !*file) && dir)
1461 /* Use dir only */
1462 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1464 else if (!dir || !*dir || !PathIsRelativeW(file))
1466 if (!dir || !*dir || *file != '\\' || PathIsUNCW(file))
1468 /* Use file only */
1469 lstrcpynW(tmp, file, ARRAY_SIZE(tmp));
1471 else
1473 use_both = TRUE;
1474 strip = TRUE;
1477 else
1478 use_both = TRUE;
1480 if (use_both)
1482 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1483 if (strip)
1485 PathStripToRootW(tmp);
1486 file++; /* Skip '\' */
1489 if (!PathAddBackslashW(tmp) || lstrlenW(tmp) + lstrlenW(file) >= MAX_PATH)
1491 dst[0] = 0;
1492 return NULL;
1495 lstrcatW(tmp, file);
1498 PathCanonicalizeW(dst, tmp);
1499 return dst;
1502 LPSTR WINAPI PathCombineA(char *dst, const char *dir, const char *file)
1504 WCHAR dstW[MAX_PATH], dirW[MAX_PATH], fileW[MAX_PATH];
1506 TRACE("%p, %s, %s\n", dst, wine_dbgstr_a(dir), wine_dbgstr_a(file));
1508 /* Invalid parameters */
1509 if (!dst)
1510 return NULL;
1512 if (!dir && !file)
1513 goto fail;
1515 if (dir && !MultiByteToWideChar(CP_ACP, 0, dir, -1, dirW, ARRAY_SIZE(dirW)))
1516 goto fail;
1518 if (file && !MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW)))
1519 goto fail;
1521 if (PathCombineW(dstW, dir ? dirW : NULL, file ? fileW : NULL))
1522 if (WideCharToMultiByte(CP_ACP, 0, dstW, -1, dst, MAX_PATH, 0, 0))
1523 return dst;
1524 fail:
1525 dst[0] = 0;
1526 return NULL;
1529 BOOL WINAPI PathAppendA(char *path, const char *append)
1531 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(append));
1533 if (path && append)
1535 if (!PathIsUNCA(append))
1536 while (*append == '\\')
1537 append++;
1539 if (PathCombineA(path, path, append))
1540 return TRUE;
1543 return FALSE;
1546 BOOL WINAPI PathAppendW(WCHAR *path, const WCHAR *append)
1548 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(append));
1550 if (path && append)
1552 if (!PathIsUNCW(append))
1553 while (*append == '\\')
1554 append++;
1556 if (PathCombineW(path, path, append))
1557 return TRUE;
1560 return FALSE;
1563 int WINAPI PathCommonPrefixA(const char *file1, const char *file2, char *path)
1565 const char *iter1 = file1;
1566 const char *iter2 = file2;
1567 unsigned int len = 0;
1569 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1), wine_dbgstr_a(file2), path);
1571 if (path)
1572 *path = '\0';
1574 if (!file1 || !file2)
1575 return 0;
1577 /* Handle roots first */
1578 if (PathIsUNCA(file1))
1580 if (!PathIsUNCA(file2))
1581 return 0;
1582 iter1 += 2;
1583 iter2 += 2;
1585 else if (PathIsUNCA(file2))
1586 return 0;
1588 for (;;)
1590 /* Update len */
1591 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1592 len = iter1 - file1; /* Common to this point */
1594 if (!*iter1 || (tolower(*iter1) != tolower(*iter2)))
1595 break; /* Strings differ at this point */
1597 iter1++;
1598 iter2++;
1601 if (len == 2)
1602 len++; /* Feature/Bug compatible with Win32 */
1604 if (len && path)
1606 memcpy(path, file1, len);
1607 path[len] = '\0';
1610 return len;
1613 int WINAPI PathCommonPrefixW(const WCHAR *file1, const WCHAR *file2, WCHAR *path)
1615 const WCHAR *iter1 = file1;
1616 const WCHAR *iter2 = file2;
1617 unsigned int len = 0;
1619 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1), wine_dbgstr_w(file2), path);
1621 if (path)
1622 *path = '\0';
1624 if (!file1 || !file2)
1625 return 0;
1627 /* Handle roots first */
1628 if (PathIsUNCW(file1))
1630 if (!PathIsUNCW(file2))
1631 return 0;
1632 iter1 += 2;
1633 iter2 += 2;
1635 else if (PathIsUNCW(file2))
1636 return 0;
1638 for (;;)
1640 /* Update len */
1641 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1642 len = iter1 - file1; /* Common to this point */
1644 if (!*iter1 || (towupper(*iter1) != towupper(*iter2)))
1645 break; /* Strings differ at this point */
1647 iter1++;
1648 iter2++;
1651 if (len == 2)
1652 len++; /* Feature/Bug compatible with Win32 */
1654 if (len && path)
1656 memcpy(path, file1, len * sizeof(WCHAR));
1657 path[len] = '\0';
1660 return len;
1663 BOOL WINAPI PathIsPrefixA(const char *prefix, const char *path)
1665 TRACE("%s, %s\n", wine_dbgstr_a(prefix), wine_dbgstr_a(path));
1667 return prefix && path && PathCommonPrefixA(path, prefix, NULL) == (int)strlen(prefix);
1670 BOOL WINAPI PathIsPrefixW(const WCHAR *prefix, const WCHAR *path)
1672 TRACE("%s, %s\n", wine_dbgstr_w(prefix), wine_dbgstr_w(path));
1674 return prefix && path && PathCommonPrefixW(path, prefix, NULL) == (int)lstrlenW(prefix);
1677 char * WINAPI PathFindFileNameA(const char *path)
1679 const char *last_slash = path;
1681 TRACE("%s\n", wine_dbgstr_a(path));
1683 while (path && *path)
1685 if ((*path == '\\' || *path == '/' || *path == ':') &&
1686 path[1] && path[1] != '\\' && path[1] != '/')
1687 last_slash = path + 1;
1688 path = CharNextA(path);
1691 return (char *)last_slash;
1694 WCHAR * WINAPI PathFindFileNameW(const WCHAR *path)
1696 const WCHAR *last_slash = path;
1698 TRACE("%s\n", wine_dbgstr_w(path));
1700 while (path && *path)
1702 if ((*path == '\\' || *path == '/' || *path == ':') &&
1703 path[1] && path[1] != '\\' && path[1] != '/')
1704 last_slash = path + 1;
1705 path++;
1708 return (WCHAR *)last_slash;
1711 char * WINAPI PathGetArgsA(const char *path)
1713 BOOL seen_quote = FALSE;
1715 TRACE("%s\n", wine_dbgstr_a(path));
1717 if (!path)
1718 return NULL;
1720 while (*path)
1722 if (*path == ' ' && !seen_quote)
1723 return (char *)path + 1;
1725 if (*path == '"')
1726 seen_quote = !seen_quote;
1727 path = CharNextA(path);
1730 return (char *)path;
1733 WCHAR * WINAPI PathGetArgsW(const WCHAR *path)
1735 BOOL seen_quote = FALSE;
1737 TRACE("%s\n", wine_dbgstr_w(path));
1739 if (!path)
1740 return NULL;
1742 while (*path)
1744 if (*path == ' ' && !seen_quote)
1745 return (WCHAR *)path + 1;
1747 if (*path == '"')
1748 seen_quote = !seen_quote;
1749 path++;
1752 return (WCHAR *)path;
1755 UINT WINAPI PathGetCharTypeW(WCHAR ch)
1757 UINT flags = 0;
1759 TRACE("%#x\n", ch);
1761 if (!ch || ch < ' ' || ch == '<' || ch == '>' || ch == '"' || ch == '|' || ch == '/')
1762 flags = GCT_INVALID; /* Invalid */
1763 else if (ch == '*' || ch == '?')
1764 flags = GCT_WILD; /* Wildchars */
1765 else if (ch == '\\' || ch == ':')
1766 return GCT_SEPARATOR; /* Path separators */
1767 else
1769 if (ch < 126)
1771 if (((ch & 0x1) && ch != ';') || !ch || isalnum(ch) || ch == '$' || ch == '&' || ch == '(' ||
1772 ch == '.' || ch == '@' || ch == '^' || ch == '\'' || ch == '`')
1774 flags |= GCT_SHORTCHAR; /* All these are valid for DOS */
1777 else
1778 flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */
1780 flags |= GCT_LFNCHAR; /* Valid for long file names */
1783 return flags;
1786 UINT WINAPI PathGetCharTypeA(UCHAR ch)
1788 return PathGetCharTypeW(ch);
1791 int WINAPI PathGetDriveNumberA(const char *path)
1793 TRACE("%s\n", wine_dbgstr_a(path));
1795 if (path && *path && path[1] == ':')
1797 if (*path >= 'a' && *path <= 'z') return *path - 'a';
1798 if (*path >= 'A' && *path <= 'Z') return *path - 'A';
1800 return -1;
1803 int WINAPI PathGetDriveNumberW(const WCHAR *path)
1805 TRACE("%s\n", wine_dbgstr_w(path));
1807 if (!path)
1808 return -1;
1810 if (!wcsncmp(path, L"\\\\?\\", 4)) path += 4;
1812 if (!path[0] || path[1] != ':') return -1;
1813 if (path[0] >= 'A' && path[0] <= 'Z') return path[0] - 'A';
1814 if (path[0] >= 'a' && path[0] <= 'z') return path[0] - 'a';
1815 return -1;
1818 BOOL WINAPI PathIsFileSpecA(const char *path)
1820 TRACE("%s\n", wine_dbgstr_a(path));
1822 if (!path)
1823 return FALSE;
1825 while (*path)
1827 if (*path == '\\' || *path == ':')
1828 return FALSE;
1829 path = CharNextA(path);
1832 return TRUE;
1835 BOOL WINAPI PathIsFileSpecW(const WCHAR *path)
1837 TRACE("%s\n", wine_dbgstr_w(path));
1839 if (!path)
1840 return FALSE;
1842 while (*path)
1844 if (*path == '\\' || *path == ':')
1845 return FALSE;
1846 path++;
1849 return TRUE;
1852 BOOL WINAPI PathIsUNCServerA(const char *path)
1854 TRACE("%s\n", wine_dbgstr_a(path));
1856 if (!(path && path[0] == '\\' && path[1] == '\\'))
1857 return FALSE;
1859 while (*path)
1861 if (*path == '\\')
1862 return FALSE;
1863 path = CharNextA(path);
1866 return TRUE;
1869 BOOL WINAPI PathIsUNCServerW(const WCHAR *path)
1871 TRACE("%s\n", wine_dbgstr_w(path));
1873 if (!(path && path[0] == '\\' && path[1] == '\\'))
1874 return FALSE;
1876 return !wcschr(path + 2, '\\');
1879 void WINAPI PathRemoveBlanksA(char *path)
1881 char *start, *first;
1883 TRACE("%s\n", wine_dbgstr_a(path));
1885 if (!path || !*path)
1886 return;
1888 start = first = path;
1890 while (*path == ' ')
1891 path = CharNextA(path);
1893 while (*path)
1894 *start++ = *path++;
1896 if (start != first)
1897 while (start[-1] == ' ')
1898 start--;
1900 *start = '\0';
1903 void WINAPI PathRemoveBlanksW(WCHAR *path)
1905 WCHAR *start, *first;
1907 TRACE("%s\n", wine_dbgstr_w(path));
1909 if (!path || !*path)
1910 return;
1912 start = first = path;
1914 while (*path == ' ')
1915 path++;
1917 while (*path)
1918 *start++ = *path++;
1920 if (start != first)
1921 while (start[-1] == ' ')
1922 start--;
1924 *start = '\0';
1927 void WINAPI PathRemoveExtensionA(char *path)
1929 TRACE("%s\n", wine_dbgstr_a(path));
1931 if (!path)
1932 return;
1934 path = PathFindExtensionA(path);
1935 if (path && *path)
1936 *path = '\0';
1939 void WINAPI PathRemoveExtensionW(WCHAR *path)
1941 TRACE("%s\n", wine_dbgstr_w(path));
1943 if (!path)
1944 return;
1946 path = PathFindExtensionW(path);
1947 if (path && *path)
1948 *path = '\0';
1951 BOOL WINAPI PathRenameExtensionA(char *path, const char *ext)
1953 char *extension;
1955 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1957 extension = PathFindExtensionA(path);
1959 if (!extension || (extension - path + strlen(ext) >= MAX_PATH))
1960 return FALSE;
1962 strcpy(extension, ext);
1963 return TRUE;
1966 BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext)
1968 WCHAR *extension;
1970 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1972 extension = PathFindExtensionW(path);
1974 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH))
1975 return FALSE;
1977 lstrcpyW(extension, ext);
1978 return TRUE;
1981 void WINAPI PathUnquoteSpacesA(char *path)
1983 unsigned int len;
1985 TRACE("%s\n", wine_dbgstr_a(path));
1987 if (!path || *path != '"')
1988 return;
1990 len = strlen(path) - 1;
1991 if (path[len] == '"')
1993 path[len] = '\0';
1994 for (; *path; path++)
1995 *path = path[1];
1999 void WINAPI PathUnquoteSpacesW(WCHAR *path)
2001 unsigned int len;
2003 TRACE("%s\n", wine_dbgstr_w(path));
2005 if (!path || *path != '"')
2006 return;
2008 len = lstrlenW(path) - 1;
2009 if (path[len] == '"')
2011 path[len] = '\0';
2012 for (; *path; path++)
2013 *path = path[1];
2017 char * WINAPI PathRemoveBackslashA(char *path)
2019 char *ptr;
2021 TRACE("%s\n", wine_dbgstr_a(path));
2023 if (!path)
2024 return NULL;
2026 ptr = CharPrevA(path, path + strlen(path));
2027 if (!PathIsRootA(path) && *ptr == '\\')
2028 *ptr = '\0';
2030 return ptr;
2033 WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path)
2035 WCHAR *ptr;
2037 TRACE("%s\n", wine_dbgstr_w(path));
2039 if (!path)
2040 return NULL;
2042 ptr = path + lstrlenW(path);
2043 if (ptr > path) ptr--;
2044 if (!PathIsRootW(path) && *ptr == '\\')
2045 *ptr = '\0';
2047 return ptr;
2050 BOOL WINAPI PathIsLFNFileSpecA(const char *path)
2052 unsigned int name_len = 0, ext_len = 0;
2054 TRACE("%s\n", wine_dbgstr_a(path));
2056 if (!path)
2057 return FALSE;
2059 while (*path)
2061 if (*path == ' ')
2062 return TRUE; /* DOS names cannot have spaces */
2063 if (*path == '.')
2065 if (ext_len)
2066 return TRUE; /* DOS names have only one dot */
2067 ext_len = 1;
2069 else if (ext_len)
2071 ext_len++;
2072 if (ext_len > 4)
2073 return TRUE; /* DOS extensions are <= 3 chars*/
2075 else
2077 name_len++;
2078 if (name_len > 8)
2079 return TRUE; /* DOS names are <= 8 chars */
2081 path = CharNextA(path);
2084 return FALSE; /* Valid DOS path */
2087 BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path)
2089 unsigned int name_len = 0, ext_len = 0;
2091 TRACE("%s\n", wine_dbgstr_w(path));
2093 if (!path)
2094 return FALSE;
2096 while (*path)
2098 if (*path == ' ')
2099 return TRUE; /* DOS names cannot have spaces */
2100 if (*path == '.')
2102 if (ext_len)
2103 return TRUE; /* DOS names have only one dot */
2104 ext_len = 1;
2106 else if (ext_len)
2108 ext_len++;
2109 if (ext_len > 4)
2110 return TRUE; /* DOS extensions are <= 3 chars*/
2112 else
2114 name_len++;
2115 if (name_len > 8)
2116 return TRUE; /* DOS names are <= 8 chars */
2118 path++;
2121 return FALSE; /* Valid DOS path */
2124 #define PATH_CHAR_CLASS_LETTER 0x00000001
2125 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2126 #define PATH_CHAR_CLASS_DOT 0x00000004
2127 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2128 #define PATH_CHAR_CLASS_COLON 0x00000010
2129 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2130 #define PATH_CHAR_CLASS_COMMA 0x00000040
2131 #define PATH_CHAR_CLASS_SPACE 0x00000080
2132 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2133 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2135 #define PATH_CHAR_CLASS_INVALID 0x00000000
2136 #define PATH_CHAR_CLASS_ANY 0xffffffff
2138 static const DWORD path_charclass[] =
2140 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID,
2141 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID,
2142 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID,
2143 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID,
2144 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID,
2145 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID,
2146 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID,
2147 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID,
2148 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID,
2149 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID,
2150 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID,
2151 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID,
2152 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID,
2153 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID,
2154 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID,
2155 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID,
2156 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID,
2157 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID,
2158 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID,
2159 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID,
2160 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID,
2161 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID,
2162 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID,
2163 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID,
2164 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID,
2165 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID,
2166 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID,
2167 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID,
2168 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID,
2169 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON,
2170 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID,
2171 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER,
2172 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY,
2173 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY,
2174 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY,
2175 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY,
2176 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY,
2177 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY,
2178 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY,
2179 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY,
2180 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY,
2181 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY,
2182 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY,
2183 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY,
2184 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY,
2185 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID,
2186 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID,
2187 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID,
2188 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY,
2189 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY,
2190 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY,
2191 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY,
2192 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY,
2193 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY,
2194 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY,
2195 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY,
2196 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY,
2197 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY,
2198 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY,
2199 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY,
2200 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY,
2201 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID,
2202 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID,
2203 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2206 BOOL WINAPI PathIsValidCharA(char c, DWORD class)
2208 if ((unsigned)c > 0x7e)
2209 return class & PATH_CHAR_CLASS_OTHER_VALID;
2211 return class & path_charclass[(unsigned)c];
2214 BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class)
2216 if (c > 0x7e)
2217 return class & PATH_CHAR_CLASS_OTHER_VALID;
2219 return class & path_charclass[c];
2222 char * WINAPI PathFindNextComponentA(const char *path)
2224 char *slash;
2226 TRACE("%s\n", wine_dbgstr_a(path));
2228 if (!path || !*path)
2229 return NULL;
2231 if ((slash = StrChrA(path, '\\')))
2233 if (slash[1] == '\\')
2234 slash++;
2235 return slash + 1;
2238 return (char *)path + strlen(path);
2241 WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path)
2243 WCHAR *slash;
2245 TRACE("%s\n", wine_dbgstr_w(path));
2247 if (!path || !*path)
2248 return NULL;
2250 if ((slash = StrChrW(path, '\\')))
2252 if (slash[1] == '\\')
2253 slash++;
2254 return slash + 1;
2257 return (WCHAR *)path + lstrlenW(path);
2260 char * WINAPI PathSkipRootA(const char *path)
2262 TRACE("%s\n", wine_dbgstr_a(path));
2264 if (!path || !*path)
2265 return NULL;
2267 if (*path == '\\' && path[1] == '\\')
2269 /* Network share: skip share server and mount point */
2270 path += 2;
2271 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\')))
2272 path++;
2273 return (char *)path;
2276 if (IsDBCSLeadByte(*path))
2277 return NULL;
2279 /* Check x:\ */
2280 if (path[0] && path[1] == ':' && path[2] == '\\')
2281 return (char *)path + 3;
2283 return NULL;
2286 WCHAR * WINAPI PathSkipRootW(const WCHAR *path)
2288 TRACE("%s\n", wine_dbgstr_w(path));
2290 if (!path || !*path)
2291 return NULL;
2293 if (*path == '\\' && path[1] == '\\')
2295 /* Network share: skip share server and mount point */
2296 path += 2;
2297 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\')))
2298 path++;
2299 return (WCHAR *)path;
2302 /* Check x:\ */
2303 if (path[0] && path[1] == ':' && path[2] == '\\')
2304 return (WCHAR *)path + 3;
2306 return NULL;
2309 void WINAPI PathStripPathA(char *path)
2311 TRACE("%s\n", wine_dbgstr_a(path));
2313 if (path)
2315 char *filename = PathFindFileNameA(path);
2316 if (filename != path)
2317 RtlMoveMemory(path, filename, strlen(filename) + 1);
2321 void WINAPI PathStripPathW(WCHAR *path)
2323 WCHAR *filename;
2325 TRACE("%s\n", wine_dbgstr_w(path));
2326 filename = PathFindFileNameW(path);
2327 if (filename != path)
2328 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR));
2331 BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length)
2333 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length);
2335 if (SearchPathA(NULL, path, NULL, length, buffer, NULL))
2336 return TRUE;
2338 return !!GetFullPathNameA(path, length, buffer, NULL);
2341 BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length)
2343 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length);
2345 if (SearchPathW(NULL, path, NULL, length, buffer, NULL))
2346 return TRUE;
2347 return !!GetFullPathNameW(path, length, buffer, NULL);
2350 BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to,
2351 DWORD attributes_to)
2353 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH];
2354 BOOL ret;
2356 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to);
2358 if (!path || !from || !to)
2359 return FALSE;
2361 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW));
2362 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW));
2363 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to);
2364 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0);
2366 return ret;
2369 BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to,
2370 DWORD attributes_to)
2372 WCHAR fromW[MAX_PATH], toW[MAX_PATH];
2373 DWORD len;
2375 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to);
2377 if (!path || !from || !to)
2378 return FALSE;
2380 *path = '\0';
2381 lstrcpynW(fromW, from, ARRAY_SIZE(fromW));
2382 lstrcpynW(toW, to, ARRAY_SIZE(toW));
2384 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY))
2385 PathRemoveFileSpecW(fromW);
2386 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY))
2387 PathRemoveFileSpecW(toW);
2389 /* Paths can only be relative if they have a common root */
2390 if (!(len = PathCommonPrefixW(fromW, toW, 0)))
2391 return FALSE;
2393 /* Strip off 'from' components to the root, by adding "..\" */
2394 from = fromW + len;
2395 if (!*from)
2397 path[0] = '.';
2398 path[1] = '\0';
2400 if (*from == '\\')
2401 from++;
2403 while (*from)
2405 from = PathFindNextComponentW(from);
2406 lstrcatW(path, *from ? L"..\\" : L"..");
2409 /* From the root add the components of 'to' */
2410 to += len;
2411 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2412 if (*to && to[-1])
2414 if (*to != '\\')
2415 to--;
2416 len = lstrlenW(path);
2417 if (len + lstrlenW(to) >= MAX_PATH)
2419 *path = '\0';
2420 return FALSE;
2422 lstrcpyW(path + len, to);
2425 return TRUE;
2428 BOOL WINAPI PathMatchSpecA(const char *path, const char *mask)
2430 WCHAR *pathW, *maskW;
2431 BOOL ret;
2433 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask));
2435 if (!lstrcmpA(mask, "*.*"))
2436 return TRUE; /* Matches every path */
2438 pathW = heap_strdupAtoW( path );
2439 maskW = heap_strdupAtoW( mask );
2440 ret = PathMatchSpecW( pathW, maskW );
2441 heap_free( pathW );
2442 heap_free( maskW );
2443 return ret;
2446 static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask)
2448 while (*name && *mask && *mask != ';')
2450 if (*mask == '*')
2454 if (path_match_maskW(name, mask + 1))
2455 return TRUE; /* try substrings */
2456 } while (*name++);
2457 return FALSE;
2460 if (towupper(*mask) != towupper(*name) && *mask != '?')
2461 return FALSE;
2463 name++;
2464 mask++;
2467 if (!*name)
2469 while (*mask == '*')
2470 mask++;
2471 if (!*mask || *mask == ';')
2472 return TRUE;
2475 return FALSE;
2478 BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask)
2480 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask));
2482 if (!lstrcmpW(mask, L"*.*"))
2483 return TRUE; /* Matches every path */
2485 while (*mask)
2487 while (*mask == ' ')
2488 mask++; /* Eat leading spaces */
2490 if (path_match_maskW(path, mask))
2491 return TRUE; /* Matches the current path */
2493 while (*mask && *mask != ';')
2494 mask++; /* masks separated by ';' */
2496 if (*mask == ';')
2497 mask++;
2500 return FALSE;
2503 void WINAPI PathQuoteSpacesA(char *path)
2505 TRACE("%s\n", wine_dbgstr_a(path));
2507 if (path && StrChrA(path, ' '))
2509 size_t len = strlen(path) + 1;
2511 if (len + 2 < MAX_PATH)
2513 memmove(path + 1, path, len);
2514 path[0] = '"';
2515 path[len] = '"';
2516 path[len + 1] = '\0';
2521 void WINAPI PathQuoteSpacesW(WCHAR *path)
2523 TRACE("%s\n", wine_dbgstr_w(path));
2525 if (path && StrChrW(path, ' '))
2527 int len = lstrlenW(path) + 1;
2529 if (len + 2 < MAX_PATH)
2531 memmove(path + 1, path, len * sizeof(WCHAR));
2532 path[0] = '"';
2533 path[len] = '"';
2534 path[len + 1] = '\0';
2539 BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2)
2541 const char *start;
2542 int len;
2544 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2));
2546 if (!path1 || !path2 || !(start = PathSkipRootA(path1)))
2547 return FALSE;
2549 len = PathCommonPrefixA(path1, path2, NULL) + 1;
2550 return start - path1 <= len;
2553 BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2)
2555 const WCHAR *start;
2556 int len;
2558 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2));
2560 if (!path1 || !path2 || !(start = PathSkipRootW(path1)))
2561 return FALSE;
2563 len = PathCommonPrefixW(path1, path2, NULL) + 1;
2564 return start - path1 <= len;
2567 BOOL WINAPI PathFileExistsA(const char *path)
2569 UINT prev_mode;
2570 DWORD attrs;
2572 TRACE("%s\n", wine_dbgstr_a(path));
2574 if (!path)
2575 return FALSE;
2577 /* Prevent a dialog box if path is on a disk that has been ejected. */
2578 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2579 attrs = GetFileAttributesA(path);
2580 SetErrorMode(prev_mode);
2581 return attrs != INVALID_FILE_ATTRIBUTES;
2584 BOOL WINAPI PathFileExistsW(const WCHAR *path)
2586 UINT prev_mode;
2587 DWORD attrs;
2589 TRACE("%s\n", wine_dbgstr_w(path));
2591 if (!path)
2592 return FALSE;
2594 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2595 attrs = GetFileAttributesW(path);
2596 SetErrorMode(prev_mode);
2597 return attrs != INVALID_FILE_ATTRIBUTES;
2600 int WINAPI PathParseIconLocationA(char *path)
2602 int ret = 0;
2603 char *comma;
2605 TRACE("%s\n", debugstr_a(path));
2607 if (!path)
2608 return 0;
2610 if ((comma = strchr(path, ',')))
2612 *comma++ = '\0';
2613 ret = StrToIntA(comma);
2615 PathUnquoteSpacesA(path);
2616 PathRemoveBlanksA(path);
2618 return ret;
2621 int WINAPI PathParseIconLocationW(WCHAR *path)
2623 WCHAR *comma;
2624 int ret = 0;
2626 TRACE("%s\n", debugstr_w(path));
2628 if (!path)
2629 return 0;
2631 if ((comma = StrChrW(path, ',')))
2633 *comma++ = '\0';
2634 ret = StrToIntW(comma);
2636 PathUnquoteSpacesW(path);
2637 PathRemoveBlanksW(path);
2639 return ret;
2642 BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len)
2644 WCHAR bufferW[MAX_PATH], *pathW;
2645 DWORD len;
2646 BOOL ret;
2648 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len);
2650 pathW = heap_strdupAtoW(path);
2651 if (!pathW) return FALSE;
2653 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
2654 HeapFree(GetProcessHeap(), 0, pathW);
2655 if (!ret) return FALSE;
2657 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
2658 if (buf_len < len + 1) return FALSE;
2660 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
2661 return TRUE;
2664 struct envvars_map
2666 const WCHAR *var;
2667 WCHAR path[MAX_PATH];
2668 DWORD len;
2671 static void init_envvars_map(struct envvars_map *map)
2673 while (map->var)
2675 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path));
2676 /* exclude null from length */
2677 if (map->len) map->len--;
2678 map++;
2682 BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len)
2684 static struct envvars_map null_var = {L"", {0}, 0};
2685 struct envvars_map *match = &null_var, *cur;
2686 struct envvars_map envvars[] =
2688 { L"%ALLUSERSPROFILE%" },
2689 { L"%APPDATA%" },
2690 { L"%ProgramFiles%" },
2691 { L"%SystemRoot%" },
2692 { L"%SystemDrive%" },
2693 { L"%USERPROFILE%" },
2694 { NULL }
2696 DWORD pathlen;
2697 UINT needed;
2699 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len);
2701 pathlen = lstrlenW(path);
2702 init_envvars_map(envvars);
2703 cur = envvars;
2704 while (cur->var)
2706 /* path can't contain expanded value or value wasn't retrieved */
2707 if (cur->len == 0 || cur->len > pathlen ||
2708 CompareStringOrdinal( cur->path, cur->len, path, cur->len, TRUE ) != CSTR_EQUAL)
2710 cur++;
2711 continue;
2714 if (cur->len > match->len)
2715 match = cur;
2716 cur++;
2719 needed = lstrlenW(match->var) + 1 + pathlen - match->len;
2720 if (match->len == 0 || needed > buf_len) return FALSE;
2722 lstrcpyW(buffer, match->var);
2723 lstrcatW(buffer, &path[match->len]);
2724 TRACE("ret %s\n", debugstr_w(buffer));
2726 return TRUE;
2729 static const struct
2731 URL_SCHEME scheme_number;
2732 const WCHAR *scheme_name;
2734 url_schemes[] =
2736 { URL_SCHEME_FTP, L"ftp"},
2737 { URL_SCHEME_HTTP, L"http"},
2738 { URL_SCHEME_GOPHER, L"gopher"},
2739 { URL_SCHEME_MAILTO, L"mailto"},
2740 { URL_SCHEME_NEWS, L"news"},
2741 { URL_SCHEME_NNTP, L"nntp"},
2742 { URL_SCHEME_TELNET, L"telnet"},
2743 { URL_SCHEME_WAIS, L"wais"},
2744 { URL_SCHEME_FILE, L"file"},
2745 { URL_SCHEME_MK, L"mk"},
2746 { URL_SCHEME_HTTPS, L"https"},
2747 { URL_SCHEME_SHELL, L"shell"},
2748 { URL_SCHEME_SNEWS, L"snews"},
2749 { URL_SCHEME_LOCAL, L"local"},
2750 { URL_SCHEME_JAVASCRIPT, L"javascript"},
2751 { URL_SCHEME_VBSCRIPT, L"vbscript"},
2752 { URL_SCHEME_ABOUT, L"about"},
2753 { URL_SCHEME_RES, L"res"},
2756 static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len)
2758 unsigned int i;
2760 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i)
2762 if (scheme_len == lstrlenW(url_schemes[i].scheme_name)
2763 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len))
2764 return url_schemes[i].scheme_number;
2767 return URL_SCHEME_UNKNOWN;
2770 HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result)
2772 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
2773 const char *ptr = url;
2774 int len;
2776 TRACE("%s, %p\n", wine_dbgstr_a(url), result);
2778 if (result->cbSize != sizeof(*result))
2779 return E_INVALIDARG;
2781 while (*ptr && (isalnum( *ptr ) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2782 ptr++;
2784 if (*ptr != ':' || ptr <= url + 1)
2786 result->pszProtocol = NULL;
2787 return URL_E_INVALID_SYNTAX;
2790 result->pszProtocol = url;
2791 result->cchProtocol = ptr - url;
2792 result->pszSuffix = ptr + 1;
2793 result->cchSuffix = strlen(result->pszSuffix);
2795 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme));
2796 result->nScheme = get_scheme_code(scheme, len);
2798 return S_OK;
2801 HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result)
2803 const WCHAR *ptr = url;
2805 TRACE("%s, %p\n", wine_dbgstr_w(url), result);
2807 if (result->cbSize != sizeof(*result))
2808 return E_INVALIDARG;
2810 while (*ptr && (isalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2811 ptr++;
2813 if (*ptr != ':' || ptr <= url + 1)
2815 result->pszProtocol = NULL;
2816 return URL_E_INVALID_SYNTAX;
2819 result->pszProtocol = url;
2820 result->cchProtocol = ptr - url;
2821 result->pszSuffix = ptr + 1;
2822 result->cchSuffix = lstrlenW(result->pszSuffix);
2823 result->nScheme = get_scheme_code(url, ptr - url);
2825 return S_OK;
2828 HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags)
2830 BOOL stop_unescaping = FALSE;
2831 const char *src;
2832 char *dst, next;
2833 DWORD needed;
2834 HRESULT hr;
2836 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags);
2838 if (!url)
2839 return E_INVALIDARG;
2841 if (flags & URL_UNESCAPE_INPLACE)
2842 dst = url;
2843 else
2845 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2846 dst = unescaped;
2849 for (src = url, needed = 0; *src; src++, needed++)
2851 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2853 stop_unescaping = TRUE;
2854 next = *src;
2856 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2858 INT ih;
2859 char buf[3];
2860 memcpy(buf, src + 1, 2);
2861 buf[2] = '\0';
2862 ih = strtol(buf, NULL, 16);
2863 next = (CHAR) ih;
2864 src += 2; /* Advance to end of escape */
2866 else
2867 next = *src;
2869 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2870 *dst++ = next;
2873 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2875 *dst = '\0';
2876 hr = S_OK;
2878 else
2880 needed++; /* add one for the '\0' */
2881 hr = E_POINTER;
2884 if (!(flags & URL_UNESCAPE_INPLACE))
2885 *unescaped_len = needed;
2887 if (hr == S_OK)
2888 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped));
2890 return hr;
2893 HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags)
2895 BOOL stop_unescaping = FALSE;
2896 const WCHAR *src;
2897 WCHAR *dst, next;
2898 DWORD needed;
2899 HRESULT hr;
2901 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags);
2903 if (!url)
2904 return E_INVALIDARG;
2906 if (flags & URL_UNESCAPE_INPLACE)
2907 dst = url;
2908 else
2910 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2911 dst = unescaped;
2914 for (src = url, needed = 0; *src; src++, needed++)
2916 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2918 stop_unescaping = TRUE;
2919 next = *src;
2921 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2923 INT ih;
2924 WCHAR buf[5] = L"0x";
2925 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
2926 buf[4] = 0;
2927 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
2928 next = (WCHAR) ih;
2929 src += 2; /* Advance to end of escape */
2931 else
2932 next = *src;
2934 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2935 *dst++ = next;
2938 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2940 *dst = '\0';
2941 hr = S_OK;
2943 else
2945 needed++; /* add one for the '\0' */
2946 hr = E_POINTER;
2949 if (!(flags & URL_UNESCAPE_INPLACE))
2950 *unescaped_len = needed;
2952 if (hr == S_OK)
2953 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped));
2955 return hr;
2958 HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved)
2960 WCHAR bufW[MAX_PATH];
2961 WCHAR *pathW = bufW;
2962 UNICODE_STRING urlW;
2963 HRESULT ret;
2964 DWORD lenW = ARRAY_SIZE(bufW), lenA;
2966 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
2967 return E_INVALIDARG;
2969 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
2970 return E_INVALIDARG;
2971 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
2972 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2973 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
2975 if(ret == S_OK) {
2976 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
2977 if(*pcchPath > lenA) {
2978 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
2979 pszPath[lenA] = 0;
2980 *pcchPath = lenA;
2981 } else {
2982 *pcchPath = lenA + 1;
2983 ret = E_POINTER;
2986 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
2987 RtlFreeUnicodeString(&urlW);
2988 return ret;
2991 HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved)
2993 DWORD nslashes, unescape, len;
2994 const WCHAR *src;
2995 WCHAR *tpath, *dst;
2996 HRESULT hr = S_OK;
2998 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), path, pcchPath, dwReserved);
3000 if (!url || !path || !pcchPath || !*pcchPath)
3001 return E_INVALIDARG;
3003 if (wcsnicmp( url, L"file:", 5))
3004 return E_INVALIDARG;
3006 url += 5;
3008 src = url;
3009 nslashes = 0;
3010 while (*src == '/' || *src == '\\')
3012 nslashes++;
3013 src++;
3016 /* We need a temporary buffer so we can compute what size to ask for.
3017 * We know that the final string won't be longer than the current pszUrl
3018 * plus at most two backslashes. All the other transformations make it
3019 * shorter.
3021 len = 2 + lstrlenW(url) + 1;
3022 if (*pcchPath < len)
3023 tpath = heap_alloc(len * sizeof(WCHAR));
3024 else
3025 tpath = path;
3027 len = 0;
3028 dst = tpath;
3029 unescape = 1;
3030 switch (nslashes)
3032 case 0:
3033 /* 'file:' + escaped DOS path */
3034 break;
3035 case 1:
3036 /* 'file:/' + escaped DOS path */
3037 /* fall through */
3038 case 3:
3039 /* 'file:///' (implied localhost) + escaped DOS path */
3040 if (!is_escaped_drive_spec( src ))
3041 src -= 1;
3042 break;
3043 case 2:
3044 if (lstrlenW(src) >= 10 && !wcsnicmp( src, L"localhost", 9) && (src[9] == '/' || src[9] == '\\'))
3046 /* 'file://localhost/' + escaped DOS path */
3047 src += 10;
3049 else if (is_escaped_drive_spec( src ))
3051 /* 'file://' + unescaped DOS path */
3052 unescape = 0;
3054 else
3056 /* 'file://hostname:port/path' (where path is escaped)
3057 * or 'file:' + escaped UNC path (\\server\share\path)
3058 * The second form is clearly specific to Windows and it might
3059 * even be doing a network lookup to try to figure it out.
3061 while (*src && *src != '/' && *src != '\\')
3062 src++;
3063 len = src - url;
3064 StrCpyNW(dst, url, len + 1);
3065 dst += len;
3066 if (*src && is_escaped_drive_spec( src + 1 ))
3068 /* 'Forget' to add a trailing '/', just like Windows */
3069 src++;
3072 break;
3073 case 4:
3074 /* 'file://' + unescaped UNC path (\\server\share\path) */
3075 unescape = 0;
3076 if (is_escaped_drive_spec( src ))
3077 break;
3078 /* fall through */
3079 default:
3080 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3081 src -= 2;
3084 /* Copy the remainder of the path */
3085 len += lstrlenW(src);
3086 lstrcpyW(dst, src);
3088 /* First do the Windows-specific path conversions */
3089 for (dst = tpath; *dst; dst++)
3090 if (*dst == '/') *dst = '\\';
3091 if (is_escaped_drive_spec( tpath ))
3092 tpath[1] = ':'; /* c| -> c: */
3094 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3095 if (unescape)
3097 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3098 if (hr == S_OK)
3100 /* When working in-place UrlUnescapeW() does not set len */
3101 len = lstrlenW(tpath);
3105 if (*pcchPath < len + 1)
3107 hr = E_POINTER;
3108 *pcchPath = len + 1;
3110 else
3112 *pcchPath = len;
3113 if (tpath != path)
3114 lstrcpyW(path, tpath);
3116 if (tpath != path)
3117 heap_free(tpath);
3119 TRACE("Returning (%lu) %s\n", *pcchPath, wine_dbgstr_w(path));
3120 return hr;
3123 HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved)
3125 WCHAR pathW[MAX_PATH];
3126 DWORD size;
3127 HRESULT hr;
3129 size = MAX_PATH;
3130 hr = PathCreateFromUrlW(url, pathW, &size, reserved);
3131 if (SUCCEEDED(hr))
3133 /* Yes, this is supposed to crash if 'path' is NULL */
3134 *path = StrDupW(pathW);
3137 return hr;
3140 BOOL WINAPI PathIsURLA(const char *path)
3142 PARSEDURLA base;
3143 HRESULT hr;
3145 TRACE("%s\n", wine_dbgstr_a(path));
3147 if (!path || !*path)
3148 return FALSE;
3150 /* get protocol */
3151 base.cbSize = sizeof(base);
3152 hr = ParseURLA(path, &base);
3153 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3156 BOOL WINAPI PathIsURLW(const WCHAR *path)
3158 PARSEDURLW base;
3159 HRESULT hr;
3161 TRACE("%s\n", wine_dbgstr_w(path));
3163 if (!path || !*path)
3164 return FALSE;
3166 /* get protocol */
3167 base.cbSize = sizeof(base);
3168 hr = ParseURLW(path, &base);
3169 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3172 #define WINE_URL_BASH_AS_SLASH 0x01
3173 #define WINE_URL_COLLAPSE_SLASHES 0x02
3174 #define WINE_URL_ESCAPE_SLASH 0x04
3175 #define WINE_URL_ESCAPE_HASH 0x08
3176 #define WINE_URL_ESCAPE_QUESTION 0x10
3177 #define WINE_URL_STOP_ON_HASH 0x20
3178 #define WINE_URL_STOP_ON_QUESTION 0x40
3180 static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags)
3182 if (flags & URL_ESCAPE_SPACES_ONLY)
3183 return ch == ' ';
3185 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%'))
3186 return TRUE;
3188 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80))
3189 return TRUE;
3191 if (ch <= 31 || (ch >= 127 && ch <= 255) )
3192 return TRUE;
3194 if (isalnum(ch))
3195 return FALSE;
3197 switch (ch) {
3198 case ' ':
3199 case '<':
3200 case '>':
3201 case '\"':
3202 case '{':
3203 case '}':
3204 case '|':
3205 case '\\':
3206 case '^':
3207 case ']':
3208 case '[':
3209 case '`':
3210 case '&':
3211 return TRUE;
3212 case '/':
3213 return !!(int_flags & WINE_URL_ESCAPE_SLASH);
3214 case '?':
3215 return !!(int_flags & WINE_URL_ESCAPE_QUESTION);
3216 case '#':
3217 return !!(int_flags & WINE_URL_ESCAPE_HASH);
3218 default:
3219 return FALSE;
3223 HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags)
3225 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
3226 WCHAR *escapedW = bufW;
3227 UNICODE_STRING urlW;
3228 HRESULT hr;
3229 DWORD lenW = ARRAY_SIZE(bufW), lenA;
3231 if (!escaped || !escaped_len || !*escaped_len)
3232 return E_INVALIDARG;
3234 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url))
3235 return E_INVALIDARG;
3237 if (flags & URL_ESCAPE_AS_UTF8)
3239 RtlFreeUnicodeString(&urlW);
3240 return E_NOTIMPL;
3243 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER)
3245 escapedW = heap_alloc(lenW * sizeof(WCHAR));
3246 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags);
3249 if (hr == S_OK)
3251 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
3252 if (*escaped_len > lenA)
3254 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
3255 escaped[lenA] = 0;
3256 *escaped_len = lenA;
3258 else
3260 *escaped_len = lenA + 1;
3261 hr = E_POINTER;
3264 if (escapedW != bufW)
3265 heap_free(escapedW);
3266 RtlFreeUnicodeString(&urlW);
3267 return hr;
3270 HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags)
3272 DWORD needed = 0, slashes = 0, int_flags;
3273 WCHAR next[12], *dst, *dst_ptr;
3274 BOOL stop_escaping = FALSE;
3275 PARSEDURLW parsed_url;
3276 const WCHAR *src;
3277 INT i, len;
3278 HRESULT hr;
3280 TRACE("%p, %s, %p, %p, %#lx\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags);
3282 if (!url || !escaped_len || !escaped || *escaped_len == 0)
3283 return E_INVALIDARG;
3285 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO |
3286 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8))
3288 FIXME("Unimplemented flags: %08lx\n", flags);
3291 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR));
3292 if (!dst_ptr)
3293 return E_OUTOFMEMORY;
3295 /* fix up flags */
3296 if (flags & URL_ESCAPE_SPACES_ONLY)
3297 /* if SPACES_ONLY specified, reset the other controls */
3298 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY);
3299 else
3300 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3301 flags |= URL_DONT_ESCAPE_EXTRA_INFO;
3303 int_flags = 0;
3304 if (flags & URL_ESCAPE_SEGMENT_ONLY)
3305 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
3306 else
3308 parsed_url.cbSize = sizeof(parsed_url);
3309 if (ParseURLW(url, &parsed_url) != S_OK)
3310 parsed_url.nScheme = URL_SCHEME_INVALID;
3312 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
3314 if (flags & URL_DONT_ESCAPE_EXTRA_INFO)
3315 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
3317 switch(parsed_url.nScheme) {
3318 case URL_SCHEME_FILE:
3319 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
3320 int_flags &= ~WINE_URL_STOP_ON_HASH;
3321 break;
3323 case URL_SCHEME_HTTP:
3324 case URL_SCHEME_HTTPS:
3325 int_flags |= WINE_URL_BASH_AS_SLASH;
3326 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
3327 int_flags |= WINE_URL_ESCAPE_SLASH;
3328 break;
3330 case URL_SCHEME_MAILTO:
3331 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
3332 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
3333 break;
3335 case URL_SCHEME_INVALID:
3336 break;
3338 case URL_SCHEME_FTP:
3339 default:
3340 if(parsed_url.pszSuffix[0] != '/')
3341 int_flags |= WINE_URL_ESCAPE_SLASH;
3342 break;
3346 for (src = url; *src; )
3348 WCHAR cur = *src;
3349 len = 0;
3351 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1)
3353 while (cur == '/' || cur == '\\')
3355 slashes++;
3356 cur = *++src;
3358 if (slashes == 2 && !wcsnicmp(src, L"localhost", 9)) { /* file://localhost/ -> file:/// */
3359 if(src[9] == '/' || src[9] == '\\') src += 10;
3360 slashes = 3;
3363 switch (slashes)
3365 case 1:
3366 case 3:
3367 next[0] = next[1] = next[2] = '/';
3368 len = 3;
3369 break;
3370 case 0:
3371 len = 0;
3372 break;
3373 default:
3374 next[0] = next[1] = '/';
3375 len = 2;
3376 break;
3379 if (len == 0)
3381 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
3382 stop_escaping = TRUE;
3384 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
3385 stop_escaping = TRUE;
3387 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
3389 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping)
3391 if (flags & URL_ESCAPE_AS_UTF8)
3393 char utf[16];
3395 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff))
3397 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL);
3398 src++;
3400 else
3401 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL);
3403 if (!len)
3405 utf[0] = 0xef;
3406 utf[1] = 0xbf;
3407 utf[2] = 0xbd;
3408 len = 3;
3411 for (i = 0; i < len; ++i)
3413 next[i*3+0] = '%';
3414 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf];
3415 next[i*3+2] = hexDigits[utf[i] & 0xf];
3417 len *= 3;
3419 else
3421 next[0] = '%';
3422 next[1] = hexDigits[(cur >> 4) & 0xf];
3423 next[2] = hexDigits[cur & 0xf];
3424 len = 3;
3427 else
3429 next[0] = cur;
3430 len = 1;
3432 src++;
3435 if (needed + len <= *escaped_len)
3437 memcpy(dst, next, len*sizeof(WCHAR));
3438 dst += len;
3440 needed += len;
3443 if (needed < *escaped_len)
3445 *dst = '\0';
3446 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR));
3447 hr = S_OK;
3449 else
3451 needed++; /* add one for the '\0' */
3452 hr = E_POINTER;
3454 *escaped_len = needed;
3456 heap_free(dst_ptr);
3457 return hr;
3460 HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags)
3462 LPWSTR url, canonical;
3463 HRESULT hr;
3465 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags);
3467 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3468 return E_INVALIDARG;
3470 url = heap_strdupAtoW(src_url);
3471 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR));
3472 if (!url || !canonical)
3474 heap_free(url);
3475 heap_free(canonical);
3476 return E_OUTOFMEMORY;
3479 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags);
3480 if (hr == S_OK)
3481 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL);
3483 heap_free(url);
3484 heap_free(canonical);
3485 return hr;
3488 HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags)
3490 WCHAR *url_copy, *url, *wk2, *mp, *mp2;
3491 DWORD nByteLen, nLen, nWkLen;
3492 const WCHAR *wk1, *root;
3493 DWORD escape_flags;
3494 WCHAR slash = '\0';
3495 HRESULT hr = S_OK;
3496 BOOL is_file_url;
3497 INT state;
3499 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags);
3501 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3502 return E_INVALIDARG;
3504 if (!*src_url)
3506 *canonicalized = 0;
3507 return S_OK;
3510 /* Remove '\t' characters from URL */
3511 nByteLen = (lstrlenW(src_url) + 1) * sizeof(WCHAR); /* length in bytes */
3512 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
3513 if(!url)
3514 return E_OUTOFMEMORY;
3516 wk1 = src_url;
3517 wk2 = url;
3520 while(*wk1 == '\t')
3521 wk1++;
3522 *wk2++ = *wk1;
3523 } while (*wk1++);
3525 /* Allocate memory for simplified URL (before escaping) */
3526 nByteLen = (wk2-url)*sizeof(WCHAR);
3527 url_copy = heap_alloc(nByteLen + sizeof(L"file:///"));
3528 if (!url_copy)
3530 heap_free(url);
3531 return E_OUTOFMEMORY;
3534 is_file_url = !wcsncmp(url, L"file:", 5);
3536 if ((nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"http:", 5)) || is_file_url)
3537 slash = '/';
3539 if ((flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
3540 slash = '\\';
3542 if (nByteLen >= 4*sizeof(WCHAR) && !wcsncmp(url, L"res:", 4))
3544 flags &= ~URL_FILE_USE_PATHURL;
3545 slash = '\0';
3549 * state =
3550 * 0 initial 1,3
3551 * 1 have 2[+] alnum 2,3
3552 * 2 have scheme (found :) 4,6,3
3553 * 3 failed (no location)
3554 * 4 have // 5,3
3555 * 5 have 1[+] alnum 6,3
3556 * 6 have location (found /) save root location
3559 wk1 = url;
3560 wk2 = url_copy;
3561 state = 0;
3563 /* Assume path */
3564 if (url[1] == ':')
3566 lstrcpyW(wk2, L"file:///");
3567 wk2 += lstrlenW(wk2);
3568 if (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
3570 slash = '\\';
3571 --wk2;
3573 else
3574 flags |= URL_ESCAPE_UNSAFE;
3575 state = 5;
3576 is_file_url = TRUE;
3578 else if (url[0] == '/')
3580 state = 5;
3581 is_file_url = TRUE;
3584 while (*wk1)
3586 switch (state)
3588 case 0:
3589 if (!isalnum(*wk1)) {state = 3; break;}
3590 *wk2++ = *wk1++;
3591 if (!isalnum(*wk1)) {state = 3; break;}
3592 *wk2++ = *wk1++;
3593 state = 1;
3594 break;
3595 case 1:
3596 *wk2++ = *wk1;
3597 if (*wk1++ == ':') state = 2;
3598 break;
3599 case 2:
3600 *wk2++ = *wk1++;
3601 if (*wk1 != '/') {state = 6; break;}
3602 *wk2++ = *wk1++;
3603 if ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 9*sizeof(WCHAR) && is_file_url
3604 && !wcsncmp(wk1, L"localhost", 9))
3606 wk1 += 9;
3607 while (*wk1 == '\\' && (flags & URL_FILE_USE_PATHURL))
3608 wk1++;
3611 if (*wk1 == '/' && (flags & URL_FILE_USE_PATHURL))
3612 wk1++;
3613 else if (is_file_url)
3615 const WCHAR *body = wk1;
3617 while (*body == '/')
3618 ++body;
3620 if (is_drive_spec( body ))
3622 if (!(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)))
3624 if (slash)
3625 *wk2++ = slash;
3626 else
3627 *wk2++ = '/';
3630 else
3632 if (flags & URL_WININET_COMPATIBILITY)
3634 if (*wk1 == '/' && *(wk1 + 1) != '/')
3636 *wk2++ = '\\';
3638 else
3640 *wk2++ = '\\';
3641 *wk2++ = '\\';
3644 else
3646 if (*wk1 == '/' && *(wk1+1) != '/')
3648 if (slash)
3649 *wk2++ = slash;
3650 else
3651 *wk2++ = '/';
3655 wk1 = body;
3657 state = 4;
3658 break;
3659 case 3:
3660 nWkLen = lstrlenW(wk1);
3661 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3662 mp = wk2;
3663 wk1 += nWkLen;
3664 wk2 += nWkLen;
3666 if (slash)
3668 while (mp < wk2)
3670 if (*mp == '/' || *mp == '\\')
3671 *mp = slash;
3672 mp++;
3675 break;
3676 case 4:
3677 if (!isalnum(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
3679 state = 3;
3680 break;
3682 while (isalnum(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
3683 *wk2++ = *wk1++;
3684 state = 5;
3685 if (!*wk1)
3687 if (slash)
3688 *wk2++ = slash;
3689 else
3690 *wk2++ = '/';
3692 break;
3693 case 5:
3694 if (*wk1 != '/' && *wk1 != '\\')
3696 state = 3;
3697 break;
3699 while (*wk1 == '/' || *wk1 == '\\')
3701 if (slash)
3702 *wk2++ = slash;
3703 else
3704 *wk2++ = *wk1;
3705 wk1++;
3707 state = 6;
3708 break;
3709 case 6:
3710 if (flags & URL_DONT_SIMPLIFY)
3712 state = 3;
3713 break;
3716 /* Now at root location, cannot back up any more. */
3717 /* "root" will point at the '/' */
3719 root = wk2-1;
3720 while (*wk1)
3722 mp = wcschr(wk1, '/');
3723 mp2 = wcschr(wk1, '\\');
3724 if (mp2 && (!mp || mp2 < mp))
3725 mp = mp2;
3726 if (!mp)
3728 nWkLen = lstrlenW(wk1);
3729 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3730 wk1 += nWkLen;
3731 wk2 += nWkLen;
3732 continue;
3734 nLen = mp - wk1;
3735 if (nLen)
3737 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
3738 wk2 += nLen;
3739 wk1 += nLen;
3741 if (slash)
3742 *wk2++ = slash;
3743 else
3744 *wk2++ = *wk1;
3745 wk1++;
3747 while (*wk1 == '.')
3749 TRACE("found '/.'\n");
3750 if (wk1[1] == '/' || wk1[1] == '\\')
3752 /* case of /./ -> skip the ./ */
3753 wk1 += 2;
3755 else if (wk1[1] == '.' && (wk1[2] == '/' || wk1[2] == '\\' || wk1[2] == '?'
3756 || wk1[2] == '#' || !wk1[2]))
3758 /* case /../ -> need to backup wk2 */
3759 TRACE("found '/../'\n");
3760 *(wk2-1) = '\0'; /* set end of string */
3761 mp = wcsrchr(root, '/');
3762 mp2 = wcsrchr(root, '\\');
3763 if (mp2 && (!mp || mp2 < mp))
3764 mp = mp2;
3765 if (mp && (mp >= root))
3767 /* found valid backup point */
3768 wk2 = mp + 1;
3769 if(wk1[2] != '/' && wk1[2] != '\\')
3770 wk1 += 2;
3771 else
3772 wk1 += 3;
3774 else
3776 /* did not find point, restore '/' */
3777 *(wk2-1) = slash;
3778 break;
3781 else
3782 break;
3785 *wk2 = '\0';
3786 break;
3787 default:
3788 FIXME("how did we get here - state=%d\n", state);
3789 heap_free(url_copy);
3790 heap_free(url);
3791 return E_INVALIDARG;
3793 *wk2 = '\0';
3794 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url), wine_dbgstr_w(url_copy));
3796 nLen = lstrlenW(url_copy);
3797 while ((nLen > 0) && ((url_copy[nLen-1] <= ' ')))
3798 url_copy[--nLen]=0;
3800 if ((flags & URL_UNESCAPE) ||
3801 ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"file:", 5)))
3803 UrlUnescapeW(url_copy, NULL, &nLen, URL_UNESCAPE_INPLACE);
3806 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT |
3807 URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_SEGMENT_ONLY);
3809 if (escape_flags)
3811 escape_flags &= ~URL_ESCAPE_UNSAFE;
3812 hr = UrlEscapeW(url_copy, canonicalized, canonicalized_len, escape_flags);
3814 else
3816 /* No escaping needed, just copy the string */
3817 nLen = lstrlenW(url_copy);
3818 if (nLen < *canonicalized_len)
3819 memcpy(canonicalized, url_copy, (nLen + 1)*sizeof(WCHAR));
3820 else
3822 hr = E_POINTER;
3823 nLen++;
3825 *canonicalized_len = nLen;
3828 heap_free(url_copy);
3829 heap_free(url);
3831 if (hr == S_OK)
3832 TRACE("result %s\n", wine_dbgstr_w(canonicalized));
3834 return hr;
3837 HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags)
3839 LPWSTR inW, outW;
3840 HRESULT hr;
3841 DWORD len;
3843 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags);
3845 if (!url || !out || !out_len)
3846 return E_INVALIDARG;
3848 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
3849 outW = inW + INTERNET_MAX_URL_LENGTH;
3851 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
3852 len = INTERNET_MAX_URL_LENGTH;
3854 hr = UrlApplySchemeW(inW, outW, &len, flags);
3855 if (hr != S_OK)
3857 heap_free(inW);
3858 return hr;
3861 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL);
3862 if (len > *out_len)
3864 hr = E_POINTER;
3865 goto cleanup;
3868 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL);
3869 len--;
3871 cleanup:
3872 *out_len = len;
3873 heap_free(inW);
3874 return hr;
3877 static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len)
3879 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH];
3880 DWORD value_len, data_len, dwType, i;
3881 WCHAR Wxx, Wyy;
3882 HKEY newkey;
3883 INT index;
3884 BOOL j;
3886 MultiByteToWideChar(CP_ACP, 0,
3887 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH);
3888 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
3889 index = 0;
3890 while (value_len = data_len = MAX_PATH,
3891 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0)
3893 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data));
3895 j = FALSE;
3896 for (i = 0; i < value_len; ++i)
3898 Wxx = url[i];
3899 Wyy = value[i];
3900 /* remember that TRUE is not-equal */
3901 j = ChrCmpIW(Wxx, Wyy);
3902 if (j) break;
3904 if ((i == value_len) && !j)
3906 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len)
3908 *out_len = lstrlenW(data) + lstrlenW(url) + 1;
3909 RegCloseKey(newkey);
3910 return E_POINTER;
3912 lstrcpyW(out, data);
3913 lstrcatW(out, url);
3914 *out_len = lstrlenW(out);
3915 TRACE("matched and set to %s\n", wine_dbgstr_w(out));
3916 RegCloseKey(newkey);
3917 return S_OK;
3919 index++;
3921 RegCloseKey(newkey);
3922 return E_FAIL;
3925 static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len)
3927 PARSEDURLW parsed_url;
3928 WCHAR *new_url;
3929 DWORD needed;
3930 HRESULT hr;
3932 parsed_url.cbSize = sizeof(parsed_url);
3933 if (ParseURLW(path, &parsed_url) == S_OK)
3935 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1)
3937 needed = lstrlenW(path);
3938 if (needed >= *url_len)
3940 *url_len = needed + 1;
3941 return E_POINTER;
3943 else
3945 *url_len = needed;
3946 return S_FALSE;
3951 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */
3952 lstrcpyW(new_url, L"file:");
3953 if (is_drive_spec( path )) lstrcatW(new_url, L"///");
3954 lstrcatW(new_url, path);
3955 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT);
3956 heap_free(new_url);
3957 return hr;
3960 static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length)
3962 DWORD data_len, dwType;
3963 WCHAR data[MAX_PATH];
3964 HKEY newkey;
3966 /* get and prepend default */
3967 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
3968 0, 1, &newkey);
3969 data_len = sizeof(data);
3970 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len);
3971 RegCloseKey(newkey);
3972 if (lstrlenW(data) + lstrlenW(url) + 1 > *length)
3974 *length = lstrlenW(data) + lstrlenW(url) + 1;
3975 return E_POINTER;
3977 lstrcpyW(out, data);
3978 lstrcatW(out, url);
3979 *length = lstrlenW(out);
3980 TRACE("used default %s\n", wine_dbgstr_w(out));
3981 return S_OK;
3984 HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags)
3986 PARSEDURLW in_scheme;
3987 DWORD res1;
3988 HRESULT hr;
3990 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags);
3992 if (!url || !out || !length)
3993 return E_INVALIDARG;
3995 if (flags & URL_APPLY_GUESSFILE)
3997 if ((*length > 1 && ':' == url[1]) || PathIsUNCW(url))
3999 res1 = *length;
4000 hr = url_create_from_path(url, out, &res1);
4001 if (hr == S_OK || hr == E_POINTER)
4003 *length = res1;
4004 return hr;
4006 else if (hr == S_FALSE)
4008 return hr;
4013 in_scheme.cbSize = sizeof(in_scheme);
4014 /* See if the base has a scheme */
4015 res1 = ParseURLW(url, &in_scheme);
4016 if (res1)
4018 /* no scheme in input, need to see if we need to guess */
4019 if (flags & URL_APPLY_GUESSSCHEME)
4021 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL)
4022 return hr;
4026 /* If we are here, then either invalid scheme,
4027 * or no scheme and can't/failed guess.
4029 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT))
4030 return url_apply_default_scheme(url, out, length);
4032 return S_FALSE;
4035 INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash)
4037 INT ret, len, len1, len2;
4039 if (!ignore_slash)
4040 return strcmp(url1, url2);
4041 len1 = strlen(url1);
4042 if (url1[len1-1] == '/') len1--;
4043 len2 = strlen(url2);
4044 if (url2[len2-1] == '/') len2--;
4045 if (len1 == len2)
4046 return strncmp(url1, url2, len1);
4047 len = min(len1, len2);
4048 ret = strncmp(url1, url2, len);
4049 if (ret) return ret;
4050 if (len1 > len2) return 1;
4051 return -1;
4054 INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash)
4056 size_t len, len1, len2;
4057 INT ret;
4059 if (!ignore_slash)
4060 return lstrcmpW(url1, url2);
4061 len1 = lstrlenW(url1);
4062 if (url1[len1-1] == '/') len1--;
4063 len2 = lstrlenW(url2);
4064 if (url2[len2-1] == '/') len2--;
4065 if (len1 == len2)
4066 return wcsncmp(url1, url2, len1);
4067 len = min(len1, len2);
4068 ret = wcsncmp(url1, url2, len);
4069 if (ret) return ret;
4070 if (len1 > len2) return 1;
4071 return -1;
4074 HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars)
4076 DWORD srcLen;
4078 FIXME("%s, %p, %ld stub\n", wine_dbgstr_w(url), translatedUrl, maxChars);
4080 if (!url)
4081 return E_FAIL;
4083 srcLen = lstrlenW(url) + 1;
4085 /* For now just copy the URL directly */
4086 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
4088 return S_OK;
4091 const char * WINAPI UrlGetLocationA(const char *url)
4093 PARSEDURLA base;
4095 base.cbSize = sizeof(base);
4096 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */
4098 /* if scheme is file: then never return pointer */
4099 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol)))
4100 return NULL;
4102 /* Look for '#' and return its addr */
4103 return strchr(base.pszSuffix, '#');
4106 const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url)
4108 PARSEDURLW base;
4110 base.cbSize = sizeof(base);
4111 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */
4113 /* if scheme is file: then never return pointer */
4114 if (!wcsncmp(base.pszProtocol, L"file", min(4, base.cchProtocol)))
4115 return NULL;
4117 /* Look for '#' and return its addr */
4118 return wcschr(base.pszSuffix, '#');
4121 HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags)
4123 LPWSTR inW, outW;
4124 DWORD len, len2;
4125 HRESULT hr;
4127 if (!url || !out || !out_len || !*out_len)
4128 return E_INVALIDARG;
4130 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4131 outW = inW + INTERNET_MAX_URL_LENGTH;
4133 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
4135 len = INTERNET_MAX_URL_LENGTH;
4136 hr = UrlGetPartW(inW, outW, &len, part, flags);
4137 if (FAILED(hr))
4139 heap_free(inW);
4140 return hr;
4143 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, NULL, 0, NULL, NULL);
4144 if (len2 > *out_len)
4146 *out_len = len2;
4147 heap_free(inW);
4148 return E_POINTER;
4150 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL);
4151 *out_len = len2 - 1;
4152 heap_free(inW);
4153 return hr;
4156 static const WCHAR *parse_scheme( const WCHAR *p )
4158 while (isalnum( *p ) || *p == '+' || *p == '-' || *p == '.')
4159 ++p;
4160 return p;
4163 static const WCHAR *parse_url_element( const WCHAR *url, const WCHAR *separators )
4165 const WCHAR *p;
4167 if ((p = wcspbrk( url, separators )))
4168 return p;
4169 return url + wcslen( url );
4172 static BOOL is_slash( char c )
4174 return c == '/' || c == '\\';
4177 static void parse_url( const WCHAR *url, struct parsed_url *pl )
4179 const WCHAR *work;
4181 memset(pl, 0, sizeof(*pl));
4182 pl->scheme = url;
4183 work = parse_scheme( pl->scheme );
4184 if (*work != ':') return;
4185 pl->scheme_len = work - pl->scheme;
4186 work++;
4187 if (!is_slash( work[0] ) || !is_slash( work[1] ))
4188 return;
4190 pl->username = work + 2;
4191 work = parse_url_element( pl->username, L":@/\\?#" );
4192 pl->username_len = work - pl->username;
4193 if (*work == ':')
4195 pl->password = work + 1;
4196 work = parse_url_element( pl->password, L"@/\\?#" );
4197 pl->password_len = work - pl->password;
4198 if (*work != '@')
4200 /* what we just parsed must be the hostname and port
4201 * so reset pointers and clear then let it parse */
4202 pl->username_len = pl->password_len = 0;
4203 work = pl->username - 1;
4204 pl->username = pl->password = 0;
4207 else if (*work == '@')
4209 /* no password */
4210 pl->password_len = 0;
4211 pl->password = 0;
4213 else
4215 /* what was parsed was hostname, so reset pointers and let it parse */
4216 pl->username_len = pl->password_len = 0;
4217 work = pl->username - 1;
4218 pl->username = pl->password = 0;
4221 pl->hostname = work + 1;
4222 work = parse_url_element( pl->hostname, L":/\\?#" );
4223 pl->hostname_len = work - pl->hostname;
4225 if (*work == ':')
4227 pl->port = work + 1;
4228 work = parse_url_element( pl->port, L"/\\?#" );
4229 pl->port_len = work - pl->port;
4232 if ((pl->query = wcschr( work, '?' )))
4234 ++pl->query;
4235 pl->query_len = lstrlenW(pl->query);
4239 HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags)
4241 DWORD scheme, size, schsize;
4242 LPCWSTR addr, schaddr;
4243 struct parsed_url pl;
4245 TRACE("%s, %p, %p(%ld), %#lx, %#lx\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags);
4247 if (!url || !out || !out_len || !*out_len)
4248 return E_INVALIDARG;
4250 addr = wcschr(url, ':');
4251 if (!addr)
4252 scheme = URL_SCHEME_UNKNOWN;
4253 else
4254 scheme = get_scheme_code(url, addr - url);
4256 parse_url(url, &pl);
4258 switch (scheme)
4260 case URL_SCHEME_FILE:
4261 case URL_SCHEME_FTP:
4262 case URL_SCHEME_GOPHER:
4263 case URL_SCHEME_HTTP:
4264 case URL_SCHEME_HTTPS:
4265 case URL_SCHEME_TELNET:
4266 case URL_SCHEME_NEWS:
4267 case URL_SCHEME_NNTP:
4268 case URL_SCHEME_SNEWS:
4269 break;
4271 default:
4272 if (part != URL_PART_SCHEME && part != URL_PART_QUERY)
4273 return E_FAIL;
4276 switch (part)
4278 case URL_PART_SCHEME:
4279 flags &= ~URL_PARTFLAG_KEEPSCHEME;
4280 addr = pl.scheme;
4281 size = pl.scheme_len;
4282 break;
4284 case URL_PART_HOSTNAME:
4285 if (scheme == URL_SCHEME_FILE && (!pl.hostname_len || (pl.hostname_len == 1 && *(pl.hostname + 1) == ':')))
4287 addr = NULL;
4288 size = 0;
4290 else
4292 addr = pl.hostname;
4293 size = pl.hostname_len;
4295 break;
4297 case URL_PART_USERNAME:
4298 if (!pl.username)
4299 return E_INVALIDARG;
4300 addr = pl.username;
4301 size = pl.username_len;
4302 break;
4304 case URL_PART_PASSWORD:
4305 if (!pl.password)
4306 return E_INVALIDARG;
4307 addr = pl.password;
4308 size = pl.password_len;
4309 break;
4311 case URL_PART_PORT:
4312 if (!pl.port)
4313 return E_INVALIDARG;
4314 addr = pl.port;
4315 size = pl.port_len;
4316 break;
4318 case URL_PART_QUERY:
4319 flags &= ~URL_PARTFLAG_KEEPSCHEME;
4320 addr = pl.query;
4321 size = pl.query_len;
4322 break;
4324 default:
4325 return E_INVALIDARG;
4328 if (flags == URL_PARTFLAG_KEEPSCHEME && scheme != URL_SCHEME_FILE)
4330 if (!pl.scheme || !pl.scheme_len)
4331 return E_FAIL;
4332 schaddr = pl.scheme;
4333 schsize = pl.scheme_len;
4334 if (*out_len < schsize + size + 2)
4336 *out_len = schsize + size + 2;
4337 return E_POINTER;
4339 memcpy(out, schaddr, schsize*sizeof(WCHAR));
4340 out[schsize] = ':';
4341 memcpy(out + schsize+1, addr, size*sizeof(WCHAR));
4342 out[schsize+1+size] = 0;
4343 *out_len = schsize + 1 + size;
4345 else
4347 if (*out_len < size + 1)
4349 *out_len = size + 1;
4350 return E_POINTER;
4353 if (part == URL_PART_SCHEME)
4355 unsigned int i;
4357 for (i = 0; i < size; ++i)
4358 out[i] = tolower( addr[i] );
4360 else
4362 memcpy( out, addr, size * sizeof(WCHAR) );
4364 out[size] = 0;
4365 *out_len = size;
4367 TRACE("len=%ld %s\n", *out_len, wine_dbgstr_w(out));
4369 return *out_len ? S_OK : S_FALSE;
4372 BOOL WINAPI UrlIsA(const char *url, URLIS Urlis)
4374 const char *last;
4375 PARSEDURLA base;
4377 TRACE("%s, %d\n", debugstr_a(url), Urlis);
4379 if (!url)
4380 return FALSE;
4382 switch (Urlis) {
4384 case URLIS_OPAQUE:
4385 base.cbSize = sizeof(base);
4386 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */
4387 switch (base.nScheme)
4389 case URL_SCHEME_MAILTO:
4390 case URL_SCHEME_SHELL:
4391 case URL_SCHEME_JAVASCRIPT:
4392 case URL_SCHEME_VBSCRIPT:
4393 case URL_SCHEME_ABOUT:
4394 return TRUE;
4396 return FALSE;
4398 case URLIS_FILEURL:
4399 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL);
4401 case URLIS_DIRECTORY:
4402 last = url + strlen(url) - 1;
4403 return (last >= url && (*last == '/' || *last == '\\' ));
4405 case URLIS_URL:
4406 return PathIsURLA(url);
4408 case URLIS_NOHISTORY:
4409 case URLIS_APPLIABLE:
4410 case URLIS_HASQUERY:
4411 default:
4412 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis);
4415 return FALSE;
4418 BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis)
4420 const WCHAR *last;
4421 PARSEDURLW base;
4423 TRACE("%s, %d\n", debugstr_w(url), Urlis);
4425 if (!url)
4426 return FALSE;
4428 switch (Urlis)
4430 case URLIS_OPAQUE:
4431 base.cbSize = sizeof(base);
4432 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */
4433 switch (base.nScheme)
4435 case URL_SCHEME_MAILTO:
4436 case URL_SCHEME_SHELL:
4437 case URL_SCHEME_JAVASCRIPT:
4438 case URL_SCHEME_VBSCRIPT:
4439 case URL_SCHEME_ABOUT:
4440 return TRUE;
4442 return FALSE;
4444 case URLIS_FILEURL:
4445 return !wcsnicmp( url, L"file:", 5 );
4447 case URLIS_DIRECTORY:
4448 last = url + lstrlenW(url) - 1;
4449 return (last >= url && (*last == '/' || *last == '\\'));
4451 case URLIS_URL:
4452 return PathIsURLW(url);
4454 case URLIS_NOHISTORY:
4455 case URLIS_APPLIABLE:
4456 case URLIS_HASQUERY:
4457 default:
4458 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis);
4461 return FALSE;
4464 BOOL WINAPI UrlIsOpaqueA(const char *url)
4466 return UrlIsA(url, URLIS_OPAQUE);
4469 BOOL WINAPI UrlIsOpaqueW(const WCHAR *url)
4471 return UrlIsW(url, URLIS_OPAQUE);
4474 BOOL WINAPI UrlIsNoHistoryA(const char *url)
4476 return UrlIsA(url, URLIS_NOHISTORY);
4479 BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url)
4481 return UrlIsW(url, URLIS_NOHISTORY);
4484 HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved)
4486 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
4487 DWORD lenW = ARRAY_SIZE(bufW), lenA;
4488 UNICODE_STRING pathW;
4489 WCHAR *urlW = bufW;
4490 HRESULT hr;
4492 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path))
4493 return E_INVALIDARG;
4495 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER)
4497 urlW = heap_alloc(lenW * sizeof(WCHAR));
4498 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved);
4501 if (SUCCEEDED(hr))
4503 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
4504 if (*url_len > lenA)
4506 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR));
4507 url[lenA] = 0;
4508 *url_len = lenA;
4510 else
4512 *url_len = lenA + 1;
4513 hr = E_POINTER;
4516 if (urlW != bufW)
4517 heap_free(urlW);
4518 RtlFreeUnicodeString(&pathW);
4519 return hr;
4522 HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved)
4524 HRESULT hr;
4526 TRACE("%s, %p, %p, %#lx\n", debugstr_w(path), url, url_len, reserved);
4528 if (reserved || !url || !url_len)
4529 return E_INVALIDARG;
4531 hr = url_create_from_path(path, url, url_len);
4532 if (hr == S_FALSE)
4533 lstrcpyW(url, path);
4535 return hr;
4538 HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags)
4540 WCHAR *baseW, *relativeW, *combinedW;
4541 DWORD len, len2;
4542 HRESULT hr;
4544 TRACE("%s, %s, %ld, %#lx\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags);
4546 if (!base || !relative || !combined_len)
4547 return E_INVALIDARG;
4549 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4550 relativeW = baseW + INTERNET_MAX_URL_LENGTH;
4551 combinedW = relativeW + INTERNET_MAX_URL_LENGTH;
4553 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH);
4554 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH);
4555 len = *combined_len;
4557 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags);
4558 if (hr != S_OK)
4560 *combined_len = len;
4561 heap_free(baseW);
4562 return hr;
4565 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL);
4566 if (len2 > *combined_len)
4568 *combined_len = len2;
4569 heap_free(baseW);
4570 return E_POINTER;
4572 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL);
4573 *combined_len = len2;
4574 heap_free(baseW);
4575 return S_OK;
4578 HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags)
4580 DWORD i, len, process_case = 0, myflags, sizeloc = 0;
4581 LPWSTR work, preliminary, mbase, mrelative;
4582 PARSEDURLW base, relative;
4583 HRESULT hr;
4585 TRACE("%s, %s, %ld, %#lx\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags);
4587 if (!baseW || !relativeW || !combined_len)
4588 return E_INVALIDARG;
4590 base.cbSize = sizeof(base);
4591 relative.cbSize = sizeof(relative);
4593 /* Get space for duplicates of the input and the output */
4594 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4595 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
4596 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
4597 *preliminary = '\0';
4599 /* Canonicalize the base input prior to looking for the scheme */
4600 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
4601 len = INTERNET_MAX_URL_LENGTH;
4602 UrlCanonicalizeW(baseW, mbase, &len, myflags);
4604 /* Canonicalize the relative input prior to looking for the scheme */
4605 len = INTERNET_MAX_URL_LENGTH;
4606 UrlCanonicalizeW(relativeW, mrelative, &len, myflags);
4608 /* See if the base has a scheme */
4609 if (ParseURLW(mbase, &base) != S_OK)
4611 /* If base has no scheme return relative. */
4612 TRACE("no scheme detected in Base\n");
4613 process_case = 1;
4615 else do
4617 BOOL manual_search = FALSE;
4619 work = (LPWSTR)base.pszProtocol;
4620 for (i = 0; i < base.cchProtocol; ++i)
4621 work[i] = RtlDowncaseUnicodeChar(work[i]);
4623 /* mk is a special case */
4624 if (base.nScheme == URL_SCHEME_MK)
4626 WCHAR *ptr = wcsstr(base.pszSuffix, L"::");
4627 if (ptr)
4629 int delta;
4631 ptr += 2;
4632 delta = ptr-base.pszSuffix;
4633 base.cchProtocol += delta;
4634 base.pszSuffix += delta;
4635 base.cchSuffix -= delta;
4638 else
4640 /* get size of location field (if it exists) */
4641 work = (LPWSTR)base.pszSuffix;
4642 sizeloc = 0;
4643 if (*work++ == '/')
4645 if (*work++ == '/')
4647 /* At this point have start of location and
4648 * it ends at next '/' or end of string.
4650 while (*work && (*work != '/')) work++;
4651 sizeloc = (DWORD)(work - base.pszSuffix);
4656 /* If there is a '?', then the remaining part can only contain a
4657 * query string or fragment, so start looking for the last leaf
4658 * from the '?'. Otherwise, if there is a '#' and the characters
4659 * immediately preceding it are ".htm[l]", then begin looking for
4660 * the last leaf starting from the '#'. Otherwise the '#' is not
4661 * meaningful and just start looking from the end. */
4662 if ((work = wcspbrk(base.pszSuffix + sizeloc, L"#?")))
4664 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
4665 manual_search = TRUE;
4666 else if (work - base.pszSuffix > 4)
4668 if (!wcsnicmp(work - 4, L".htm", 4)) manual_search = TRUE;
4671 if (!manual_search && work - base.pszSuffix > 5)
4673 if (!wcsnicmp(work - 5, L".html", 5)) manual_search = TRUE;
4677 if (manual_search)
4679 /* search backwards starting from the current position */
4680 while (*work != '/' && work > base.pszSuffix + sizeloc)
4681 --work;
4682 base.cchSuffix = work - base.pszSuffix + 1;
4684 else
4686 /* search backwards starting from the end of the string */
4687 work = wcsrchr((base.pszSuffix+sizeloc), '/');
4688 if (work)
4690 len = (DWORD)(work - base.pszSuffix + 1);
4691 base.cchSuffix = len;
4693 else
4694 base.cchSuffix = sizeloc;
4698 * At this point:
4699 * .pszSuffix points to location (starting with '//')
4700 * .cchSuffix length of location (above) and rest less the last
4701 * leaf (if any)
4702 * sizeloc length of location (above) up to but not including
4703 * the last '/'
4706 if (ParseURLW(mrelative, &relative) != S_OK)
4708 /* No scheme in relative */
4709 TRACE("no scheme detected in Relative\n");
4710 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
4711 relative.cchSuffix = lstrlenW(mrelative);
4712 if (*relativeW == ':')
4714 /* Case that is either left alone or uses base. */
4715 if (flags & URL_PLUGGABLE_PROTOCOL)
4717 process_case = 5;
4718 break;
4720 process_case = 1;
4721 break;
4723 if (is_drive_spec( mrelative ))
4725 /* case that becomes "file:///" */
4726 lstrcpyW(preliminary, L"file:///");
4727 process_case = 1;
4728 break;
4730 if (*mrelative == '/' && *(mrelative+1) == '/')
4732 /* Relative has location and the rest. */
4733 process_case = 3;
4734 break;
4736 if (*mrelative == '/')
4738 /* Relative is root to location. */
4739 process_case = 4;
4740 break;
4742 if (*mrelative == '#')
4744 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#')))
4745 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix);
4747 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
4748 preliminary[work-base.pszProtocol] = '\0';
4749 process_case = 1;
4750 break;
4752 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
4753 break;
4755 else
4757 work = (LPWSTR)relative.pszProtocol;
4758 for (i = 0; i < relative.cchProtocol; ++i)
4759 work[i] = RtlDowncaseUnicodeChar(work[i]);
4762 /* Handle cases where relative has scheme. */
4763 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol))
4765 /* since the schemes are the same */
4766 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4768 /* Relative replaces location and what follows. */
4769 process_case = 3;
4770 break;
4772 if (*relative.pszSuffix == '/')
4774 /* Relative is root to location */
4775 process_case = 4;
4776 break;
4778 /* replace either just location if base's location starts with a
4779 * slash or otherwise everything */
4780 process_case = (*base.pszSuffix == '/') ? 5 : 1;
4781 break;
4784 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4786 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4787 process_case = 2;
4788 break;
4790 process_case = 1;
4791 break;
4792 } while (FALSE); /* a little trick to allow easy exit from nested if's */
4794 hr = S_OK;
4795 switch (process_case)
4797 case 1:
4798 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4799 lstrcatW(preliminary, mrelative);
4800 break;
4802 case 2:
4803 /* Relative replaces scheme and location */
4804 lstrcpyW(preliminary, mrelative);
4805 break;
4807 case 3:
4808 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4809 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
4810 work = preliminary + base.cchProtocol + 1;
4811 lstrcpyW(work, relative.pszSuffix);
4812 break;
4814 case 4:
4815 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4816 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
4817 work = preliminary + base.cchProtocol + 1 + sizeloc;
4818 if (flags & URL_PLUGGABLE_PROTOCOL)
4819 *(work++) = '/';
4820 lstrcpyW(work, relative.pszSuffix);
4821 break;
4823 case 5:
4824 /* Return the base without its document (if any) and append relative after its scheme. */
4825 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR));
4826 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1;
4827 if (*work++ != '/')
4828 *(work++) = '/';
4829 lstrcpyW(work, relative.pszSuffix);
4830 break;
4832 default:
4833 FIXME("Unexpected case %ld.\n", process_case);
4834 hr = E_INVALIDARG;
4837 if (hr == S_OK)
4839 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
4840 if (*combined_len == 0)
4841 *combined_len = 1;
4842 hr = UrlCanonicalizeW(preliminary, mrelative, combined_len, flags & ~URL_FILE_USE_PATHURL);
4843 if (SUCCEEDED(hr) && combined)
4844 lstrcpyW(combined, mrelative);
4846 TRACE("return-%ld len=%ld, %s\n", process_case, *combined_len, debugstr_w(combined));
4849 heap_free(preliminary);
4850 return hr;
4853 HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len)
4855 INT src_count = src_len - 1, dest_count = dest_len - 1;
4857 if (!src || !dest)
4858 return E_INVALIDARG;
4860 while (dest_count >= 0)
4862 dest[dest_count] = (dest_count & 0xff);
4863 dest_count--;
4866 while (src_count >= 0)
4868 dest_count = dest_len - 1;
4869 while (dest_count >= 0)
4871 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]];
4872 dest_count--;
4874 src_count--;
4877 return S_OK;
4880 HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len)
4882 __TRY
4884 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len);
4886 __EXCEPT_PAGE_FAULT
4888 return E_INVALIDARG;
4890 __ENDTRY
4891 return S_OK;
4894 HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len)
4896 char urlA[MAX_PATH];
4898 TRACE("%s, %p, %ld\n", debugstr_w(url), dest, dest_len);
4900 __TRY
4902 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL);
4903 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len);
4905 __EXCEPT_PAGE_FAULT
4907 return E_INVALIDARG;
4909 __ENDTRY
4910 return S_OK;
4913 BOOL WINAPI IsInternetESCEnabled(void)
4915 FIXME(": stub\n");
4916 return FALSE;