mfreadwrite/reader: Add missing allocation check (Coverity).
[wine/zf.git] / dlls / kernelbase / path.c
blobf0c79bcc94ca89bdefaa066512a0ee85d45b3c27
1 /*
2 * Copyright 2018 Nikolay Sivov
3 * Copyright 2018 Zhiyi Zhang
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 #include <stdarg.h>
21 #include <string.h>
23 #include "windef.h"
24 #include "winbase.h"
25 #include "pathcch.h"
26 #include "strsafe.h"
27 #include "shlwapi.h"
28 #include "wininet.h"
29 #include "intshcut.h"
30 #include "winternl.h"
32 #include "kernelbase.h"
33 #include "wine/exception.h"
34 #include "wine/debug.h"
35 #include "wine/heap.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(path);
39 #define isalnum(ch) (((ch) >= '0' && (ch) <= '9') || \
40 ((ch) >= 'A' && (ch) <= 'Z') || \
41 ((ch) >= 'a' && (ch) <= 'z'))
42 #define isxdigit(ch) (((ch) >= '0' && (ch) <= '9') || \
43 ((ch) >= 'A' && (ch) <= 'F') || \
44 ((ch) >= 'a' && (ch) <= 'f'))
46 static const char hexDigits[] = "0123456789ABCDEF";
48 static const unsigned char hashdata_lookup[256] =
50 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33,
51 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41,
52 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c,
53 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90,
54 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe,
55 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd,
56 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d,
57 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd,
58 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2,
59 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b,
60 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70,
61 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b,
62 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47,
63 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d,
64 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8,
65 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1,
68 struct parsed_url
70 const WCHAR *scheme; /* [out] start of scheme */
71 DWORD scheme_len; /* [out] size of scheme (until colon) */
72 const WCHAR *username; /* [out] start of Username */
73 DWORD username_len; /* [out] size of Username (until ":" or "@") */
74 const WCHAR *password; /* [out] start of Password */
75 DWORD password_len; /* [out] size of Password (until "@") */
76 const WCHAR *hostname; /* [out] start of Hostname */
77 DWORD hostname_len; /* [out] size of Hostname (until ":" or "/") */
78 const WCHAR *port; /* [out] start of Port */
79 DWORD port_len; /* [out] size of Port (until "/" or eos) */
80 const WCHAR *query; /* [out] start of Query */
81 DWORD query_len; /* [out] size of Query (until eos) */
84 enum url_scan_type
86 SCHEME,
87 HOST,
88 PORT,
89 USERPASS,
92 static WCHAR *heap_strdupAtoW(const char *str)
94 WCHAR *ret = NULL;
96 if (str)
98 DWORD len;
100 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
101 ret = heap_alloc(len * sizeof(WCHAR));
102 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
105 return ret;
108 static SIZE_T strnlenW(const WCHAR *string, SIZE_T maxlen)
110 SIZE_T i;
112 for (i = 0; i < maxlen; i++)
113 if (!string[i]) break;
114 return i;
117 static BOOL is_drive_spec( const WCHAR *str )
119 return ((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z')) && str[1] == ':';
122 static BOOL is_escaped_drive_spec( const WCHAR *str )
124 return ((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z')) &&
125 (str[1] == ':' || str[1] == '|');
128 static BOOL is_prefixed_unc(const WCHAR *string)
130 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 );
133 static BOOL is_prefixed_disk(const WCHAR *string)
135 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 );
138 static BOOL is_prefixed_volume(const WCHAR *string)
140 const WCHAR *guid;
141 INT i = 0;
143 if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE;
145 guid = string + 10;
147 while (i <= 37)
149 switch (i)
151 case 0:
152 if (guid[i] != '{') return FALSE;
153 break;
154 case 9:
155 case 14:
156 case 19:
157 case 24:
158 if (guid[i] != '-') return FALSE;
159 break;
160 case 37:
161 if (guid[i] != '}') return FALSE;
162 break;
163 default:
164 if (!isxdigit(guid[i])) return FALSE;
165 break;
167 i++;
170 return TRUE;
173 /* Get the next character beyond end of the segment.
174 Return TRUE if the last segment ends with a backslash */
175 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment)
177 while (*next && *next != '\\') next++;
178 if (*next == '\\')
180 *next_segment = next + 1;
181 return TRUE;
183 else
185 *next_segment = next;
186 return FALSE;
190 /* Find the last character of the root in a path, if there is one, without any segments */
191 static const WCHAR *get_root_end(const WCHAR *path)
193 /* Find path root */
194 if (is_prefixed_volume(path))
195 return path[48] == '\\' ? path + 48 : path + 47;
196 else if (is_prefixed_unc(path))
197 return path + 7;
198 else if (is_prefixed_disk(path))
199 return path[6] == '\\' ? path + 6 : path + 5;
200 /* \\ */
201 else if (path[0] == '\\' && path[1] == '\\')
202 return path + 1;
203 /* \ */
204 else if (path[0] == '\\')
205 return path;
206 /* X:\ */
207 else if (is_drive_spec( path ))
208 return path[2] == '\\' ? path + 2 : path + 1;
209 else
210 return NULL;
213 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
215 WCHAR *buffer, *dst;
216 const WCHAR *src;
217 const WCHAR *root_end;
218 SIZE_T buffer_size, length;
220 TRACE("%s %#x %p\n", debugstr_w(path_in), flags, path_out);
222 if (!path_in || !path_out
223 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS))
224 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)
225 && !(flags & PATHCCH_ALLOW_LONG_PATHS))
226 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS)))
228 if (path_out) *path_out = NULL;
229 return E_INVALIDARG;
232 length = lstrlenW(path_in);
233 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)))
234 || (length + 1 > PATHCCH_MAX_CCH))
236 *path_out = NULL;
237 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
240 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
241 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
243 /* path length + possible \\?\ addition + possible \ addition + NUL */
244 buffer_size = (length + 6) * sizeof(WCHAR);
245 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size);
246 if (!buffer)
248 *path_out = NULL;
249 return E_OUTOFMEMORY;
252 src = path_in;
253 dst = buffer;
255 root_end = get_root_end(path_in);
256 if (root_end) root_end = buffer + (root_end - path_in);
258 /* Copy path root */
259 if (root_end)
261 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR));
262 src += root_end - buffer + 1;
263 if(PathCchStripPrefix(dst, length + 6) == S_OK)
265 /* Fill in \ in X:\ if the \ is missing */
266 if (is_drive_spec( dst ) && dst[2]!= '\\')
268 dst[2] = '\\';
269 dst[3] = 0;
271 dst = buffer + lstrlenW(buffer);
272 root_end = dst;
274 else
275 dst += root_end - buffer + 1;
278 while (*src)
280 if (src[0] == '.')
282 if (src[1] == '.')
284 /* Keep one . after * */
285 if (dst > buffer && dst[-1] == '*')
287 *dst++ = *src++;
288 continue;
291 /* Keep the .. if not surrounded by \ */
292 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\'))
294 *dst++ = *src++;
295 *dst++ = *src++;
296 continue;
299 /* Remove the \ before .. if the \ is not part of root */
300 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
302 *--dst = '\0';
303 /* Remove characters until a \ is encountered */
304 while (dst > buffer)
306 if (dst[-1] == '\\')
308 *--dst = 0;
309 break;
311 else
312 *--dst = 0;
315 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
316 else if (src[2] == '\\')
317 src++;
319 src += 2;
321 else
323 /* Keep the . if not surrounded by \ */
324 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\'))
326 *dst++ = *src++;
327 continue;
330 /* Remove the \ before . if the \ is not part of root */
331 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--;
332 /* Remove the extra \ after . if the \ before . wasn't deleted */
333 else if (src[1] == '\\')
334 src++;
336 src++;
339 /* If X:\ is not complete, then complete it */
340 if (is_drive_spec( buffer ) && buffer[2] != '\\')
342 root_end = buffer + 2;
343 dst = buffer + 3;
344 buffer[2] = '\\';
345 /* If next character is \, use the \ to fill in */
346 if (src[0] == '\\') src++;
349 /* Copy over */
350 else
351 *dst++ = *src++;
353 /* End the path */
354 *dst = 0;
356 /* Strip multiple trailing . */
357 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
359 while (dst > buffer && dst[-1] == '.')
361 /* Keep a . after * */
362 if (dst - 1 > buffer && dst[-2] == '*')
363 break;
364 /* If . follow a : at the second character, remove the . and add a \ */
365 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1)
366 *--dst = '\\';
367 else
368 *--dst = 0;
372 /* If result path is empty, fill in \ */
373 if (!*buffer)
375 buffer[0] = '\\';
376 buffer[1] = 0;
379 /* Extend the path if needed */
380 length = lstrlenW(buffer);
381 if (((length + 1 > MAX_PATH && is_drive_spec( buffer ))
382 || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))
383 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS))
385 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR));
386 buffer[0] = '\\';
387 buffer[1] = '\\';
388 buffer[2] = '?';
389 buffer[3] = '\\';
392 /* Add a trailing backslash to the path if needed */
393 if (flags & PATHCCH_ENSURE_TRAILING_SLASH)
394 PathCchAddBackslash(buffer, buffer_size);
396 *path_out = buffer;
397 return S_OK;
400 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out)
402 SIZE_T combined_length, length2;
403 WCHAR *combined_path;
404 BOOL from_path2 = FALSE;
405 HRESULT hr;
407 TRACE("%s %s %#x %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out);
409 if ((!path1 && !path2) || !out)
411 if (out) *out = NULL;
412 return E_INVALIDARG;
415 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out);
417 /* If path2 is fully qualified, use path2 only */
418 if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\'))
420 path1 = path2;
421 path2 = NULL;
422 from_path2 = TRUE;
425 length2 = path2 ? lstrlenW(path2) : 0;
426 /* path1 length + path2 length + possible backslash + NULL */
427 combined_length = lstrlenW(path1) + length2 + 2;
429 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR));
430 if (!combined_path)
432 *out = NULL;
433 return E_OUTOFMEMORY;
436 lstrcpyW(combined_path, path1);
437 PathCchStripPrefix(combined_path, combined_length);
438 if (from_path2) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
440 if (path2 && path2[0])
442 if (path2[0] == '\\' && path2[1] != '\\')
444 PathCchStripToRoot(combined_path, combined_length);
445 path2++;
448 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
449 lstrcatW(combined_path, path2);
452 hr = PathAllocCanonicalize(combined_path, flags, out);
453 HeapFree(GetProcessHeap(), 0, combined_path);
454 return hr;
457 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size)
459 return PathCchAddBackslashEx(path, size, NULL, NULL);
462 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining)
464 BOOL needs_termination;
465 SIZE_T length;
467 TRACE("%s, %lu, %p, %p\n", debugstr_w(path), size, endptr, remaining);
469 length = lstrlenW(path);
470 needs_termination = size && length && path[length - 1] != '\\';
472 if (length >= (needs_termination ? size - 1 : size))
474 if (endptr) *endptr = NULL;
475 if (remaining) *remaining = 0;
476 return STRSAFE_E_INSUFFICIENT_BUFFER;
479 if (!needs_termination)
481 if (endptr) *endptr = path + length;
482 if (remaining) *remaining = size - length;
483 return S_FALSE;
486 path[length++] = '\\';
487 path[length] = 0;
489 if (endptr) *endptr = path + length;
490 if (remaining) *remaining = size - length;
492 return S_OK;
495 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
497 const WCHAR *existing_extension, *next;
498 SIZE_T path_length, extension_length, dot_length;
499 BOOL has_dot;
500 HRESULT hr;
502 TRACE("%s %lu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
504 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG;
506 next = extension;
507 while (*next)
509 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG;
510 next++;
513 has_dot = extension[0] == '.';
515 hr = PathCchFindExtension(path, size, &existing_extension);
516 if (FAILED(hr)) return hr;
517 if (*existing_extension) return S_FALSE;
519 path_length = strnlenW(path, size);
520 dot_length = has_dot ? 0 : 1;
521 extension_length = lstrlenW(extension);
523 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER;
525 /* If extension is empty or only dot, return S_OK with path unchanged */
526 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK;
528 if (!has_dot)
530 path[path_length] = '.';
531 path_length++;
534 lstrcpyW(path + path_length, extension);
535 return S_OK;
538 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2)
540 TRACE("%s %lu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2));
542 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE);
545 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags)
547 HRESULT hr;
548 WCHAR *result;
550 TRACE("%s %lu %s %#x\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags);
552 if (!path1 || !size) return E_INVALIDARG;
554 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
555 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
556 * buffer for PathCchCombineEx */
557 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
558 if (!result) return E_OUTOFMEMORY;
560 /* Avoid the single backslash behavior with PathCchCombineEx when appending */
561 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++;
563 hr = PathCchCombineEx(result, size, path1, path2, flags);
564 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR));
566 HeapFree(GetProcessHeap(), 0, result);
567 return hr;
570 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in)
572 TRACE("%p %lu %s\n", out, size, wine_dbgstr_w(in));
574 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
575 if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\'))
576 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
578 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE);
581 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags)
583 WCHAR *buffer;
584 SIZE_T length;
585 HRESULT hr;
587 TRACE("%p %lu %s %#x\n", out, size, wine_dbgstr_w(in), flags);
589 if (!size) return E_INVALIDARG;
591 hr = PathAllocCanonicalize(in, flags, &buffer);
592 if (FAILED(hr)) return hr;
594 length = lstrlenW(buffer);
595 if (size < length + 1)
597 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
598 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\')))
599 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
600 else
601 hr = STRSAFE_E_INSUFFICIENT_BUFFER;
604 if (SUCCEEDED(hr))
606 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
608 /* Fill a backslash at the end of X: */
609 if (is_drive_spec( out ) && !out[2] && size > 3)
611 out[2] = '\\';
612 out[3] = 0;
616 LocalFree(buffer);
617 return hr;
620 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2)
622 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2));
624 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE);
627 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags)
629 HRESULT hr;
630 WCHAR *buffer;
631 SIZE_T length;
633 TRACE("%p %s %s %#x\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags);
635 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
637 hr = PathAllocCombine(path1, path2, flags, &buffer);
638 if (FAILED(hr))
640 out[0] = 0;
641 return hr;
644 length = lstrlenW(buffer);
645 if (length + 1 > size)
647 out[0] = 0;
648 LocalFree(buffer);
649 return STRSAFE_E_INSUFFICIENT_BUFFER;
651 else
653 memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
654 LocalFree(buffer);
655 return S_OK;
659 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension)
661 const WCHAR *lastpoint = NULL;
662 SIZE_T counter = 0;
664 TRACE("%s %lu %p\n", wine_dbgstr_w(path), size, extension);
666 if (!path || !size || size > PATHCCH_MAX_CCH)
668 *extension = NULL;
669 return E_INVALIDARG;
672 while (*path)
674 if (*path == '\\' || *path == ' ')
675 lastpoint = NULL;
676 else if (*path == '.')
677 lastpoint = path;
679 path++;
680 counter++;
681 if (counter == size || counter == PATHCCH_MAX_CCH)
683 *extension = NULL;
684 return E_INVALIDARG;
688 *extension = lastpoint ? lastpoint : path;
689 return S_OK;
692 BOOL WINAPI PathCchIsRoot(const WCHAR *path)
694 const WCHAR *root_end;
695 const WCHAR *next;
696 BOOL is_unc;
698 TRACE("%s\n", wine_dbgstr_w(path));
700 if (!path || !*path) return FALSE;
702 root_end = get_root_end(path);
703 if (!root_end) return FALSE;
705 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
707 next = root_end + 1;
708 /* No extra segments */
709 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE;
711 /* Has first segment with an ending backslash but no remaining characters */
712 if (get_next_segment(next, &next) && !*next) return FALSE;
713 /* Has first segment with no ending backslash */
714 else if (!*next)
715 return TRUE;
716 /* Has first segment with an ending backslash and has remaining characters*/
717 else
719 next++;
720 /* Second segment must have no backslash and no remaining characters */
721 return !get_next_segment(next, &next) && !*next;
724 else if (*root_end == '\\' && !root_end[1])
725 return TRUE;
726 else
727 return FALSE;
730 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size)
732 WCHAR *path_end;
733 SIZE_T free_size;
735 TRACE("%s %lu\n", debugstr_w(path), path_size);
737 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size);
740 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size)
742 const WCHAR *root_end;
743 SIZE_T path_length;
745 TRACE("%s %lu %p %p\n", debugstr_w(path), path_size, path_end, free_size);
747 if (!path_size || !path_end || !free_size)
749 if (path_end) *path_end = NULL;
750 if (free_size) *free_size = 0;
751 return E_INVALIDARG;
754 path_length = strnlenW(path, path_size);
755 if (path_length == path_size && !path[path_length]) return E_INVALIDARG;
757 root_end = get_root_end(path);
758 if (path_length > 0 && path[path_length - 1] == '\\')
760 *path_end = path + path_length - 1;
761 *free_size = path_size - path_length + 1;
762 /* If the last character is beyond end of root */
763 if (!root_end || path + path_length - 1 > root_end)
765 path[path_length - 1] = 0;
766 return S_OK;
768 else
769 return S_FALSE;
771 else
773 *path_end = path + path_length;
774 *free_size = path_size - path_length;
775 return S_FALSE;
779 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size)
781 const WCHAR *extension;
782 WCHAR *next;
783 HRESULT hr;
785 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
787 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
789 hr = PathCchFindExtension(path, size, &extension);
790 if (FAILED(hr)) return hr;
792 next = path + (extension - path);
793 while (next - path < size && *next) *next++ = 0;
795 return next == extension ? S_FALSE : S_OK;
798 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size)
800 const WCHAR *root_end = NULL;
801 SIZE_T length;
802 WCHAR *last;
804 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
806 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
808 if (PathCchIsRoot(path)) return S_FALSE;
810 PathCchSkipRoot(path, &root_end);
812 /* The backslash at the end of UNC and \\* are not considered part of root in this case */
813 if (root_end && root_end > path && root_end[-1] == '\\'
814 && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')))
815 root_end--;
817 length = lstrlenW(path);
818 last = path + length - 1;
819 while (last >= path && (!root_end || last >= root_end))
821 if (last - path >= size) return E_INVALIDARG;
823 if (*last == '\\')
825 *last-- = 0;
826 break;
829 *last-- = 0;
832 return last != path + length - 1 ? S_OK : S_FALSE;
835 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
837 HRESULT hr;
839 TRACE("%s %lu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
841 hr = PathCchRemoveExtension(path, size);
842 if (FAILED(hr)) return hr;
844 hr = PathCchAddExtension(path, size, extension);
845 return FAILED(hr) ? hr : S_OK;
848 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end)
850 TRACE("%s %p\n", debugstr_w(path), root_end);
852 if (!path || !path[0] || !root_end
853 || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path)
854 && !is_prefixed_disk(path)))
855 return E_INVALIDARG;
857 *root_end = get_root_end(path);
858 if (*root_end)
860 (*root_end)++;
861 if (is_prefixed_unc(path))
863 get_next_segment(*root_end, root_end);
864 get_next_segment(*root_end, root_end);
866 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
868 /* Skip share server */
869 get_next_segment(*root_end, root_end);
870 /* If mount point is empty, don't skip over mount point */
871 if (**root_end != '\\') get_next_segment(*root_end, root_end);
875 return *root_end ? S_OK : E_INVALIDARG;
878 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size)
880 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
882 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
884 if (is_prefixed_unc(path))
886 /* \\?\UNC\a -> \\a */
887 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG;
888 lstrcpyW(path + 2, path + 8);
889 return S_OK;
891 else if (is_prefixed_disk(path))
893 /* \\?\C:\ -> C:\ */
894 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG;
895 lstrcpyW(path, path + 4);
896 return S_OK;
898 else
899 return S_FALSE;
902 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size)
904 const WCHAR *root_end;
905 WCHAR *segment_end;
906 BOOL is_unc;
908 TRACE("%s %lu\n", wine_dbgstr_w(path), size);
910 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
912 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
913 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
914 * \\\\a\\b\\c -> \\\\a\\b */
915 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
917 root_end = is_unc ? path + 8 : path + 3;
918 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
919 if (!get_next_segment(root_end, &root_end)) return S_FALSE;
921 if (root_end - path >= size) return E_INVALIDARG;
923 segment_end = path + (root_end - path) - 1;
924 *segment_end = 0;
925 return S_OK;
927 else if (PathCchSkipRoot(path, &root_end) == S_OK)
929 if (root_end - path >= size) return E_INVALIDARG;
931 segment_end = path + (root_end - path);
932 if (!*segment_end) return S_FALSE;
934 *segment_end = 0;
935 return S_OK;
937 else
938 return E_INVALIDARG;
941 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server)
943 const WCHAR *result = NULL;
945 TRACE("%s %p\n", wine_dbgstr_w(path), server);
947 if (is_prefixed_unc(path))
948 result = path + 8;
949 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
950 result = path + 2;
952 if (server) *server = result;
953 return !!result;
956 BOOL WINAPI PathIsUNCA(const char *path)
958 TRACE("%s\n", wine_dbgstr_a(path));
960 return path && (path[0] == '\\') && (path[1] == '\\');
963 BOOL WINAPI PathIsUNCW(const WCHAR *path)
965 TRACE("%s\n", wine_dbgstr_w(path));
967 return path && (path[0] == '\\') && (path[1] == '\\');
970 BOOL WINAPI PathIsRelativeA(const char *path)
972 TRACE("%s\n", wine_dbgstr_a(path));
974 if (!path || !*path || IsDBCSLeadByte(*path))
975 return TRUE;
977 return !(*path == '\\' || (*path && path[1] == ':'));
980 BOOL WINAPI PathIsRelativeW(const WCHAR *path)
982 TRACE("%s\n", wine_dbgstr_w(path));
984 if (!path || !*path)
985 return TRUE;
987 return !(*path == '\\' || (*path && path[1] == ':'));
990 BOOL WINAPI PathIsUNCServerShareA(const char *path)
992 BOOL seen_slash = FALSE;
994 TRACE("%s\n", wine_dbgstr_a(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 = CharNextA(path);
1011 return seen_slash;
1014 BOOL WINAPI PathIsUNCServerShareW(const WCHAR *path)
1016 BOOL seen_slash = FALSE;
1018 TRACE("%s\n", wine_dbgstr_w(path));
1020 if (path && *path++ == '\\' && *path++ == '\\')
1022 while (*path)
1024 if (*path == '\\')
1026 if (seen_slash)
1027 return FALSE;
1028 seen_slash = TRUE;
1031 path++;
1035 return seen_slash;
1038 BOOL WINAPI PathIsRootA(const char *path)
1040 TRACE("%s\n", wine_dbgstr_a(path));
1042 if (!path || !*path)
1043 return FALSE;
1045 if (*path == '\\')
1047 if (!path[1])
1048 return TRUE; /* \ */
1049 else if (path[1] == '\\')
1051 BOOL seen_slash = FALSE;
1052 path += 2;
1054 /* Check for UNC root path */
1055 while (*path)
1057 if (*path == '\\')
1059 if (seen_slash)
1060 return FALSE;
1061 seen_slash = TRUE;
1064 path = CharNextA(path);
1067 return TRUE;
1070 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1071 return TRUE; /* X:\ */
1073 return FALSE;
1076 BOOL WINAPI PathIsRootW(const WCHAR *path)
1078 TRACE("%s\n", wine_dbgstr_w(path));
1080 if (!path || !*path)
1081 return FALSE;
1083 if (*path == '\\')
1085 if (!path[1])
1086 return TRUE; /* \ */
1087 else if (path[1] == '\\')
1089 BOOL seen_slash = FALSE;
1091 path += 2;
1092 /* Check for UNC root path */
1093 while (*path)
1095 if (*path == '\\')
1097 if (seen_slash)
1098 return FALSE;
1099 seen_slash = TRUE;
1101 path++;
1104 return TRUE;
1107 else if (path[1] == ':' && path[2] == '\\' && path[3] == '\0')
1108 return TRUE; /* X:\ */
1110 return FALSE;
1113 BOOL WINAPI PathRemoveFileSpecA(char *path)
1115 char *filespec = path;
1116 BOOL modified = FALSE;
1118 TRACE("%s\n", wine_dbgstr_a(path));
1120 if (!path)
1121 return FALSE;
1123 /* Skip directory or UNC path */
1124 if (*path == '\\')
1125 filespec = ++path;
1126 if (*path == '\\')
1127 filespec = ++path;
1129 while (*path)
1131 if (*path == '\\')
1132 filespec = path; /* Skip dir */
1133 else if (*path == ':')
1135 filespec = ++path; /* Skip drive */
1136 if (*path == '\\')
1137 filespec++;
1139 if (!(path = CharNextA(path)))
1140 break;
1143 if (*filespec)
1145 *filespec = '\0';
1146 modified = TRUE;
1149 return modified;
1152 BOOL WINAPI PathRemoveFileSpecW(WCHAR *path)
1154 WCHAR *filespec = path;
1155 BOOL modified = FALSE;
1157 TRACE("%s\n", wine_dbgstr_w(path));
1159 if (!path)
1160 return FALSE;
1162 /* Skip directory or UNC path */
1163 if (*path == '\\')
1164 filespec = ++path;
1165 if (*path == '\\')
1166 filespec = ++path;
1168 while (*path)
1170 if (*path == '\\')
1171 filespec = path; /* Skip dir */
1172 else if (*path == ':')
1174 filespec = ++path; /* Skip drive */
1175 if (*path == '\\')
1176 filespec++;
1179 path++;
1182 if (*filespec)
1184 *filespec = '\0';
1185 modified = TRUE;
1188 return modified;
1191 BOOL WINAPI PathStripToRootA(char *path)
1193 TRACE("%s\n", wine_dbgstr_a(path));
1195 if (!path)
1196 return FALSE;
1198 while (!PathIsRootA(path))
1199 if (!PathRemoveFileSpecA(path))
1200 return FALSE;
1202 return TRUE;
1205 BOOL WINAPI PathStripToRootW(WCHAR *path)
1207 TRACE("%s\n", wine_dbgstr_w(path));
1209 if (!path)
1210 return FALSE;
1212 while (!PathIsRootW(path))
1213 if (!PathRemoveFileSpecW(path))
1214 return FALSE;
1216 return TRUE;
1219 LPSTR WINAPI PathAddBackslashA(char *path)
1221 unsigned int len;
1222 char *prev = path;
1224 TRACE("%s\n", wine_dbgstr_a(path));
1226 if (!path || (len = strlen(path)) >= MAX_PATH)
1227 return NULL;
1229 if (len)
1233 path = CharNextA(prev);
1234 if (*path)
1235 prev = path;
1236 } while (*path);
1238 if (*prev != '\\')
1240 *path++ = '\\';
1241 *path = '\0';
1245 return path;
1248 LPWSTR WINAPI PathAddBackslashW(WCHAR *path)
1250 unsigned int len;
1252 TRACE("%s\n", wine_dbgstr_w(path));
1254 if (!path || (len = lstrlenW(path)) >= MAX_PATH)
1255 return NULL;
1257 if (len)
1259 path += len;
1260 if (path[-1] != '\\')
1262 *path++ = '\\';
1263 *path = '\0';
1267 return path;
1270 LPSTR WINAPI PathFindExtensionA(const char *path)
1272 const char *lastpoint = NULL;
1274 TRACE("%s\n", wine_dbgstr_a(path));
1276 if (path)
1278 while (*path)
1280 if (*path == '\\' || *path == ' ')
1281 lastpoint = NULL;
1282 else if (*path == '.')
1283 lastpoint = path;
1284 path = CharNextA(path);
1288 return (LPSTR)(lastpoint ? lastpoint : path);
1291 LPWSTR WINAPI PathFindExtensionW(const WCHAR *path)
1293 const WCHAR *lastpoint = NULL;
1295 TRACE("%s\n", wine_dbgstr_w(path));
1297 if (path)
1299 while (*path)
1301 if (*path == '\\' || *path == ' ')
1302 lastpoint = NULL;
1303 else if (*path == '.')
1304 lastpoint = path;
1305 path++;
1309 return (LPWSTR)(lastpoint ? lastpoint : path);
1312 BOOL WINAPI PathAddExtensionA(char *path, const char *ext)
1314 unsigned int len;
1316 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1318 if (!path || !ext || *(PathFindExtensionA(path)))
1319 return FALSE;
1321 len = strlen(path);
1322 if (len + strlen(ext) >= MAX_PATH)
1323 return FALSE;
1325 strcpy(path + len, ext);
1326 return TRUE;
1329 BOOL WINAPI PathAddExtensionW(WCHAR *path, const WCHAR *ext)
1331 unsigned int len;
1333 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1335 if (!path || !ext || *(PathFindExtensionW(path)))
1336 return FALSE;
1338 len = lstrlenW(path);
1339 if (len + lstrlenW(ext) >= MAX_PATH)
1340 return FALSE;
1342 lstrcpyW(path + len, ext);
1343 return TRUE;
1346 BOOL WINAPI PathCanonicalizeW(WCHAR *buffer, const WCHAR *path)
1348 const WCHAR *src = path;
1349 WCHAR *dst = buffer;
1351 TRACE("%p, %s\n", buffer, wine_dbgstr_w(path));
1353 if (dst)
1354 *dst = '\0';
1356 if (!dst || !path)
1358 SetLastError(ERROR_INVALID_PARAMETER);
1359 return FALSE;
1362 if (!*path)
1364 *buffer++ = '\\';
1365 *buffer = '\0';
1366 return TRUE;
1369 /* Copy path root */
1370 if (*src == '\\')
1372 *dst++ = *src++;
1374 else if (*src && src[1] == ':')
1376 /* X:\ */
1377 *dst++ = *src++;
1378 *dst++ = *src++;
1379 if (*src == '\\')
1380 *dst++ = *src++;
1383 /* Canonicalize the rest of the path */
1384 while (*src)
1386 if (*src == '.')
1388 if (src[1] == '\\' && (src == path || src[-1] == '\\' || src[-1] == ':'))
1390 src += 2; /* Skip .\ */
1392 else if (src[1] == '.' && (dst == buffer || dst[-1] == '\\'))
1394 /* \.. backs up a directory, over the root if it has no \ following X:.
1395 * .. is ignored if it would remove a UNC server name or initial \\
1397 if (dst != buffer)
1399 *dst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */
1400 if (dst > buffer + 1 && dst[-1] == '\\' && (dst[-2] != '\\' || dst > buffer + 2))
1402 if (dst[-2] == ':' && (dst > buffer + 3 || dst[-3] == ':'))
1404 dst -= 2;
1405 while (dst > buffer && *dst != '\\')
1406 dst--;
1407 if (*dst == '\\')
1408 dst++; /* Reset to last '\' */
1409 else
1410 dst = buffer; /* Start path again from new root */
1412 else if (dst[-2] != ':' && !PathIsUNCServerShareW(buffer))
1413 dst -= 2;
1415 while (dst > buffer && *dst != '\\')
1416 dst--;
1417 if (dst == buffer)
1419 *dst++ = '\\';
1420 src++;
1423 src += 2; /* Skip .. in src path */
1425 else
1426 *dst++ = *src++;
1428 else
1429 *dst++ = *src++;
1432 /* Append \ to naked drive specs */
1433 if (dst - buffer == 2 && dst[-1] == ':')
1434 *dst++ = '\\';
1435 *dst++ = '\0';
1436 return TRUE;
1439 BOOL WINAPI PathCanonicalizeA(char *buffer, const char *path)
1441 WCHAR pathW[MAX_PATH], bufferW[MAX_PATH];
1442 BOOL ret;
1443 int len;
1445 TRACE("%p, %s\n", buffer, wine_dbgstr_a(path));
1447 if (buffer)
1448 *buffer = '\0';
1450 if (!buffer || !path)
1452 SetLastError(ERROR_INVALID_PARAMETER);
1453 return FALSE;
1456 len = MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, ARRAY_SIZE(pathW));
1457 if (!len)
1458 return FALSE;
1460 ret = PathCanonicalizeW(bufferW, pathW);
1461 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, 0, 0);
1463 return ret;
1466 WCHAR * WINAPI PathCombineW(WCHAR *dst, const WCHAR *dir, const WCHAR *file)
1468 BOOL use_both = FALSE, strip = FALSE;
1469 WCHAR tmp[MAX_PATH];
1471 TRACE("%p, %s, %s\n", dst, wine_dbgstr_w(dir), wine_dbgstr_w(file));
1473 /* Invalid parameters */
1474 if (!dst)
1475 return NULL;
1477 if (!dir && !file)
1479 dst[0] = 0;
1480 return NULL;
1483 if ((!file || !*file) && dir)
1485 /* Use dir only */
1486 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1488 else if (!dir || !*dir || !PathIsRelativeW(file))
1490 if (!dir || !*dir || *file != '\\' || PathIsUNCW(file))
1492 /* Use file only */
1493 lstrcpynW(tmp, file, ARRAY_SIZE(tmp));
1495 else
1497 use_both = TRUE;
1498 strip = TRUE;
1501 else
1502 use_both = TRUE;
1504 if (use_both)
1506 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp));
1507 if (strip)
1509 PathStripToRootW(tmp);
1510 file++; /* Skip '\' */
1513 if (!PathAddBackslashW(tmp) || lstrlenW(tmp) + lstrlenW(file) >= MAX_PATH)
1515 dst[0] = 0;
1516 return NULL;
1519 lstrcatW(tmp, file);
1522 PathCanonicalizeW(dst, tmp);
1523 return dst;
1526 LPSTR WINAPI PathCombineA(char *dst, const char *dir, const char *file)
1528 WCHAR dstW[MAX_PATH], dirW[MAX_PATH], fileW[MAX_PATH];
1530 TRACE("%p, %s, %s\n", dst, wine_dbgstr_a(dir), wine_dbgstr_a(file));
1532 /* Invalid parameters */
1533 if (!dst)
1534 return NULL;
1536 if (!dir && !file)
1537 goto fail;
1539 if (dir && !MultiByteToWideChar(CP_ACP, 0, dir, -1, dirW, ARRAY_SIZE(dirW)))
1540 goto fail;
1542 if (file && !MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW)))
1543 goto fail;
1545 if (PathCombineW(dstW, dir ? dirW : NULL, file ? fileW : NULL))
1546 if (WideCharToMultiByte(CP_ACP, 0, dstW, -1, dst, MAX_PATH, 0, 0))
1547 return dst;
1548 fail:
1549 dst[0] = 0;
1550 return NULL;
1553 BOOL WINAPI PathAppendA(char *path, const char *append)
1555 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(append));
1557 if (path && append)
1559 if (!PathIsUNCA(append))
1560 while (*append == '\\')
1561 append++;
1563 if (PathCombineA(path, path, append))
1564 return TRUE;
1567 return FALSE;
1570 BOOL WINAPI PathAppendW(WCHAR *path, const WCHAR *append)
1572 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(append));
1574 if (path && append)
1576 if (!PathIsUNCW(append))
1577 while (*append == '\\')
1578 append++;
1580 if (PathCombineW(path, path, append))
1581 return TRUE;
1584 return FALSE;
1587 int WINAPI PathCommonPrefixA(const char *file1, const char *file2, char *path)
1589 const char *iter1 = file1;
1590 const char *iter2 = file2;
1591 unsigned int len = 0;
1593 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1), wine_dbgstr_a(file2), path);
1595 if (path)
1596 *path = '\0';
1598 if (!file1 || !file2)
1599 return 0;
1601 /* Handle roots first */
1602 if (PathIsUNCA(file1))
1604 if (!PathIsUNCA(file2))
1605 return 0;
1606 iter1 += 2;
1607 iter2 += 2;
1609 else if (PathIsUNCA(file2))
1610 return 0;
1612 for (;;)
1614 /* Update len */
1615 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1616 len = iter1 - file1; /* Common to this point */
1618 if (!*iter1 || (tolower(*iter1) != tolower(*iter2)))
1619 break; /* Strings differ at this point */
1621 iter1++;
1622 iter2++;
1625 if (len == 2)
1626 len++; /* Feature/Bug compatible with Win32 */
1628 if (len && path)
1630 memcpy(path, file1, len);
1631 path[len] = '\0';
1634 return len;
1637 int WINAPI PathCommonPrefixW(const WCHAR *file1, const WCHAR *file2, WCHAR *path)
1639 const WCHAR *iter1 = file1;
1640 const WCHAR *iter2 = file2;
1641 unsigned int len = 0;
1643 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1), wine_dbgstr_w(file2), path);
1645 if (path)
1646 *path = '\0';
1648 if (!file1 || !file2)
1649 return 0;
1651 /* Handle roots first */
1652 if (PathIsUNCW(file1))
1654 if (!PathIsUNCW(file2))
1655 return 0;
1656 iter1 += 2;
1657 iter2 += 2;
1659 else if (PathIsUNCW(file2))
1660 return 0;
1662 for (;;)
1664 /* Update len */
1665 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\'))
1666 len = iter1 - file1; /* Common to this point */
1668 if (!*iter1 || (towupper(*iter1) != towupper(*iter2)))
1669 break; /* Strings differ at this point */
1671 iter1++;
1672 iter2++;
1675 if (len == 2)
1676 len++; /* Feature/Bug compatible with Win32 */
1678 if (len && path)
1680 memcpy(path, file1, len * sizeof(WCHAR));
1681 path[len] = '\0';
1684 return len;
1687 BOOL WINAPI PathIsPrefixA(const char *prefix, const char *path)
1689 TRACE("%s, %s\n", wine_dbgstr_a(prefix), wine_dbgstr_a(path));
1691 return prefix && path && PathCommonPrefixA(path, prefix, NULL) == (int)strlen(prefix);
1694 BOOL WINAPI PathIsPrefixW(const WCHAR *prefix, const WCHAR *path)
1696 TRACE("%s, %s\n", wine_dbgstr_w(prefix), wine_dbgstr_w(path));
1698 return prefix && path && PathCommonPrefixW(path, prefix, NULL) == (int)lstrlenW(prefix);
1701 char * WINAPI PathFindFileNameA(const char *path)
1703 const char *last_slash = path;
1705 TRACE("%s\n", wine_dbgstr_a(path));
1707 while (path && *path)
1709 if ((*path == '\\' || *path == '/' || *path == ':') &&
1710 path[1] && path[1] != '\\' && path[1] != '/')
1711 last_slash = path + 1;
1712 path = CharNextA(path);
1715 return (char *)last_slash;
1718 WCHAR * WINAPI PathFindFileNameW(const WCHAR *path)
1720 const WCHAR *last_slash = path;
1722 TRACE("%s\n", wine_dbgstr_w(path));
1724 while (path && *path)
1726 if ((*path == '\\' || *path == '/' || *path == ':') &&
1727 path[1] && path[1] != '\\' && path[1] != '/')
1728 last_slash = path + 1;
1729 path++;
1732 return (WCHAR *)last_slash;
1735 char * WINAPI PathGetArgsA(const char *path)
1737 BOOL seen_quote = FALSE;
1739 TRACE("%s\n", wine_dbgstr_a(path));
1741 if (!path)
1742 return NULL;
1744 while (*path)
1746 if (*path == ' ' && !seen_quote)
1747 return (char *)path + 1;
1749 if (*path == '"')
1750 seen_quote = !seen_quote;
1751 path = CharNextA(path);
1754 return (char *)path;
1757 WCHAR * WINAPI PathGetArgsW(const WCHAR *path)
1759 BOOL seen_quote = FALSE;
1761 TRACE("%s\n", wine_dbgstr_w(path));
1763 if (!path)
1764 return NULL;
1766 while (*path)
1768 if (*path == ' ' && !seen_quote)
1769 return (WCHAR *)path + 1;
1771 if (*path == '"')
1772 seen_quote = !seen_quote;
1773 path++;
1776 return (WCHAR *)path;
1779 UINT WINAPI PathGetCharTypeW(WCHAR ch)
1781 UINT flags = 0;
1783 TRACE("%#x\n", ch);
1785 if (!ch || ch < ' ' || ch == '<' || ch == '>' || ch == '"' || ch == '|' || ch == '/')
1786 flags = GCT_INVALID; /* Invalid */
1787 else if (ch == '*' || ch == '?')
1788 flags = GCT_WILD; /* Wildchars */
1789 else if (ch == '\\' || ch == ':')
1790 return GCT_SEPARATOR; /* Path separators */
1791 else
1793 if (ch < 126)
1795 if (((ch & 0x1) && ch != ';') || !ch || isalnum(ch) || ch == '$' || ch == '&' || ch == '(' ||
1796 ch == '.' || ch == '@' || ch == '^' || ch == '\'' || ch == '`')
1798 flags |= GCT_SHORTCHAR; /* All these are valid for DOS */
1801 else
1802 flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */
1804 flags |= GCT_LFNCHAR; /* Valid for long file names */
1807 return flags;
1810 UINT WINAPI PathGetCharTypeA(UCHAR ch)
1812 return PathGetCharTypeW(ch);
1815 int WINAPI PathGetDriveNumberA(const char *path)
1817 TRACE("%s\n", wine_dbgstr_a(path));
1819 if (path && *path && path[1] == ':')
1821 if (*path >= 'a' && *path <= 'z') return *path - 'a';
1822 if (*path >= 'A' && *path <= 'Z') return *path - 'A';
1824 return -1;
1827 int WINAPI PathGetDriveNumberW(const WCHAR *path)
1829 TRACE("%s\n", wine_dbgstr_w(path));
1831 if (!path)
1832 return -1;
1834 if (!wcsncmp(path, L"\\\\?\\", 4)) path += 4;
1836 if (!path[0] || path[1] != ':') return -1;
1837 if (path[0] >= 'A' && path[0] <= 'Z') return path[0] - 'A';
1838 if (path[0] >= 'a' && path[0] <= 'z') return path[0] - 'a';
1839 return -1;
1842 BOOL WINAPI PathIsFileSpecA(const char *path)
1844 TRACE("%s\n", wine_dbgstr_a(path));
1846 if (!path)
1847 return FALSE;
1849 while (*path)
1851 if (*path == '\\' || *path == ':')
1852 return FALSE;
1853 path = CharNextA(path);
1856 return TRUE;
1859 BOOL WINAPI PathIsFileSpecW(const WCHAR *path)
1861 TRACE("%s\n", wine_dbgstr_w(path));
1863 if (!path)
1864 return FALSE;
1866 while (*path)
1868 if (*path == '\\' || *path == ':')
1869 return FALSE;
1870 path++;
1873 return TRUE;
1876 BOOL WINAPI PathIsUNCServerA(const char *path)
1878 TRACE("%s\n", wine_dbgstr_a(path));
1880 if (!(path && path[0] == '\\' && path[1] == '\\'))
1881 return FALSE;
1883 while (*path)
1885 if (*path == '\\')
1886 return FALSE;
1887 path = CharNextA(path);
1890 return TRUE;
1893 BOOL WINAPI PathIsUNCServerW(const WCHAR *path)
1895 TRACE("%s\n", wine_dbgstr_w(path));
1897 if (!(path && path[0] == '\\' && path[1] == '\\'))
1898 return FALSE;
1900 return !wcschr(path + 2, '\\');
1903 void WINAPI PathRemoveBlanksA(char *path)
1905 char *start;
1907 TRACE("%s\n", wine_dbgstr_a(path));
1909 if (!path || !*path)
1910 return;
1912 start = path;
1914 while (*path == ' ')
1915 path = CharNextA(path);
1917 while (*path)
1918 *start++ = *path++;
1920 if (start != path)
1921 while (start[-1] == ' ')
1922 start--;
1924 *start = '\0';
1927 void WINAPI PathRemoveBlanksW(WCHAR *path)
1929 WCHAR *start = path;
1931 TRACE("%s\n", wine_dbgstr_w(path));
1933 if (!path || !*path)
1934 return;
1936 while (*path == ' ')
1937 path++;
1939 while (*path)
1940 *start++ = *path++;
1942 if (start != path)
1943 while (start[-1] == ' ')
1944 start--;
1946 *start = '\0';
1949 void WINAPI PathRemoveExtensionA(char *path)
1951 TRACE("%s\n", wine_dbgstr_a(path));
1953 if (!path)
1954 return;
1956 path = PathFindExtensionA(path);
1957 if (path && *path)
1958 *path = '\0';
1961 void WINAPI PathRemoveExtensionW(WCHAR *path)
1963 TRACE("%s\n", wine_dbgstr_w(path));
1965 if (!path)
1966 return;
1968 path = PathFindExtensionW(path);
1969 if (path && *path)
1970 *path = '\0';
1973 BOOL WINAPI PathRenameExtensionA(char *path, const char *ext)
1975 char *extension;
1977 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1979 extension = PathFindExtensionA(path);
1981 if (!extension || (extension - path + strlen(ext) >= MAX_PATH))
1982 return FALSE;
1984 strcpy(extension, ext);
1985 return TRUE;
1988 BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext)
1990 WCHAR *extension;
1992 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1994 extension = PathFindExtensionW(path);
1996 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH))
1997 return FALSE;
1999 lstrcpyW(extension, ext);
2000 return TRUE;
2003 void WINAPI PathUnquoteSpacesA(char *path)
2005 unsigned int len;
2007 TRACE("%s\n", wine_dbgstr_a(path));
2009 if (!path || *path != '"')
2010 return;
2012 len = strlen(path) - 1;
2013 if (path[len] == '"')
2015 path[len] = '\0';
2016 for (; *path; path++)
2017 *path = path[1];
2021 void WINAPI PathUnquoteSpacesW(WCHAR *path)
2023 unsigned int len;
2025 TRACE("%s\n", wine_dbgstr_w(path));
2027 if (!path || *path != '"')
2028 return;
2030 len = lstrlenW(path) - 1;
2031 if (path[len] == '"')
2033 path[len] = '\0';
2034 for (; *path; path++)
2035 *path = path[1];
2039 char * WINAPI PathRemoveBackslashA(char *path)
2041 char *ptr;
2043 TRACE("%s\n", wine_dbgstr_a(path));
2045 if (!path)
2046 return NULL;
2048 ptr = CharPrevA(path, path + strlen(path));
2049 if (!PathIsRootA(path) && *ptr == '\\')
2050 *ptr = '\0';
2052 return ptr;
2055 WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path)
2057 WCHAR *ptr;
2059 TRACE("%s\n", wine_dbgstr_w(path));
2061 if (!path)
2062 return NULL;
2064 ptr = path + lstrlenW(path);
2065 if (ptr > path) ptr--;
2066 if (!PathIsRootW(path) && *ptr == '\\')
2067 *ptr = '\0';
2069 return ptr;
2072 BOOL WINAPI PathIsLFNFileSpecA(const char *path)
2074 unsigned int name_len = 0, ext_len = 0;
2076 TRACE("%s\n", wine_dbgstr_a(path));
2078 if (!path)
2079 return FALSE;
2081 while (*path)
2083 if (*path == ' ')
2084 return TRUE; /* DOS names cannot have spaces */
2085 if (*path == '.')
2087 if (ext_len)
2088 return TRUE; /* DOS names have only one dot */
2089 ext_len = 1;
2091 else if (ext_len)
2093 ext_len++;
2094 if (ext_len > 4)
2095 return TRUE; /* DOS extensions are <= 3 chars*/
2097 else
2099 name_len++;
2100 if (name_len > 8)
2101 return TRUE; /* DOS names are <= 8 chars */
2103 path = CharNextA(path);
2106 return FALSE; /* Valid DOS path */
2109 BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path)
2111 unsigned int name_len = 0, ext_len = 0;
2113 TRACE("%s\n", wine_dbgstr_w(path));
2115 if (!path)
2116 return FALSE;
2118 while (*path)
2120 if (*path == ' ')
2121 return TRUE; /* DOS names cannot have spaces */
2122 if (*path == '.')
2124 if (ext_len)
2125 return TRUE; /* DOS names have only one dot */
2126 ext_len = 1;
2128 else if (ext_len)
2130 ext_len++;
2131 if (ext_len > 4)
2132 return TRUE; /* DOS extensions are <= 3 chars*/
2134 else
2136 name_len++;
2137 if (name_len > 8)
2138 return TRUE; /* DOS names are <= 8 chars */
2140 path++;
2143 return FALSE; /* Valid DOS path */
2146 #define PATH_CHAR_CLASS_LETTER 0x00000001
2147 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2148 #define PATH_CHAR_CLASS_DOT 0x00000004
2149 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2150 #define PATH_CHAR_CLASS_COLON 0x00000010
2151 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2152 #define PATH_CHAR_CLASS_COMMA 0x00000040
2153 #define PATH_CHAR_CLASS_SPACE 0x00000080
2154 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2155 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2157 #define PATH_CHAR_CLASS_INVALID 0x00000000
2158 #define PATH_CHAR_CLASS_ANY 0xffffffff
2160 static const DWORD path_charclass[] =
2162 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID,
2163 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID,
2164 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID,
2165 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID,
2166 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID,
2167 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID,
2168 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID,
2169 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID,
2170 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID,
2171 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID,
2172 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID,
2173 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID,
2174 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID,
2175 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID,
2176 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID,
2177 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID,
2178 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID,
2179 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID,
2180 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID,
2181 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID,
2182 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID,
2183 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID,
2184 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID,
2185 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID,
2186 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID,
2187 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID,
2188 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID,
2189 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID,
2190 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID,
2191 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON,
2192 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID,
2193 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER,
2194 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY,
2195 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY,
2196 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY,
2197 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY,
2198 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY,
2199 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY,
2200 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY,
2201 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY,
2202 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY,
2203 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY,
2204 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY,
2205 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY,
2206 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY,
2207 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID,
2208 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID,
2209 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID,
2210 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY,
2211 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY,
2212 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY,
2213 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY,
2214 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY,
2215 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY,
2216 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY,
2217 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY,
2218 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY,
2219 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY,
2220 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY,
2221 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY,
2222 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY,
2223 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID,
2224 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID,
2225 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2228 BOOL WINAPI PathIsValidCharA(char c, DWORD class)
2230 if ((unsigned)c > 0x7e)
2231 return class & PATH_CHAR_CLASS_OTHER_VALID;
2233 return class & path_charclass[(unsigned)c];
2236 BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class)
2238 if (c > 0x7e)
2239 return class & PATH_CHAR_CLASS_OTHER_VALID;
2241 return class & path_charclass[c];
2244 char * WINAPI PathFindNextComponentA(const char *path)
2246 char *slash;
2248 TRACE("%s\n", wine_dbgstr_a(path));
2250 if (!path || !*path)
2251 return NULL;
2253 if ((slash = StrChrA(path, '\\')))
2255 if (slash[1] == '\\')
2256 slash++;
2257 return slash + 1;
2260 return (char *)path + strlen(path);
2263 WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path)
2265 WCHAR *slash;
2267 TRACE("%s\n", wine_dbgstr_w(path));
2269 if (!path || !*path)
2270 return NULL;
2272 if ((slash = StrChrW(path, '\\')))
2274 if (slash[1] == '\\')
2275 slash++;
2276 return slash + 1;
2279 return (WCHAR *)path + lstrlenW(path);
2282 char * WINAPI PathSkipRootA(const char *path)
2284 TRACE("%s\n", wine_dbgstr_a(path));
2286 if (!path || !*path)
2287 return NULL;
2289 if (*path == '\\' && path[1] == '\\')
2291 /* Network share: skip share server and mount point */
2292 path += 2;
2293 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\')))
2294 path++;
2295 return (char *)path;
2298 if (IsDBCSLeadByte(*path))
2299 return NULL;
2301 /* Check x:\ */
2302 if (path[0] && path[1] == ':' && path[2] == '\\')
2303 return (char *)path + 3;
2305 return NULL;
2308 WCHAR * WINAPI PathSkipRootW(const WCHAR *path)
2310 TRACE("%s\n", wine_dbgstr_w(path));
2312 if (!path || !*path)
2313 return NULL;
2315 if (*path == '\\' && path[1] == '\\')
2317 /* Network share: skip share server and mount point */
2318 path += 2;
2319 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\')))
2320 path++;
2321 return (WCHAR *)path;
2324 /* Check x:\ */
2325 if (path[0] && path[1] == ':' && path[2] == '\\')
2326 return (WCHAR *)path + 3;
2328 return NULL;
2331 void WINAPI PathStripPathA(char *path)
2333 TRACE("%s\n", wine_dbgstr_a(path));
2335 if (path)
2337 char *filename = PathFindFileNameA(path);
2338 if (filename != path)
2339 RtlMoveMemory(path, filename, strlen(filename) + 1);
2343 void WINAPI PathStripPathW(WCHAR *path)
2345 WCHAR *filename;
2347 TRACE("%s\n", wine_dbgstr_w(path));
2348 filename = PathFindFileNameW(path);
2349 if (filename != path)
2350 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR));
2353 BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length)
2355 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length);
2357 if (SearchPathA(NULL, path, NULL, length, buffer, NULL))
2358 return TRUE;
2360 return !!GetFullPathNameA(path, length, buffer, NULL);
2363 BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length)
2365 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length);
2367 if (SearchPathW(NULL, path, NULL, length, buffer, NULL))
2368 return TRUE;
2369 return !!GetFullPathNameW(path, length, buffer, NULL);
2372 BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to,
2373 DWORD attributes_to)
2375 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH];
2376 BOOL ret;
2378 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to);
2380 if (!path || !from || !to)
2381 return FALSE;
2383 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW));
2384 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW));
2385 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to);
2386 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0);
2388 return ret;
2391 BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to,
2392 DWORD attributes_to)
2394 WCHAR fromW[MAX_PATH], toW[MAX_PATH];
2395 DWORD len;
2397 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to);
2399 if (!path || !from || !to)
2400 return FALSE;
2402 *path = '\0';
2403 lstrcpynW(fromW, from, ARRAY_SIZE(fromW));
2404 lstrcpynW(toW, to, ARRAY_SIZE(toW));
2406 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY))
2407 PathRemoveFileSpecW(fromW);
2408 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY))
2409 PathRemoveFileSpecW(toW);
2411 /* Paths can only be relative if they have a common root */
2412 if (!(len = PathCommonPrefixW(fromW, toW, 0)))
2413 return FALSE;
2415 /* Strip off 'from' components to the root, by adding "..\" */
2416 from = fromW + len;
2417 if (!*from)
2419 path[0] = '.';
2420 path[1] = '\0';
2422 if (*from == '\\')
2423 from++;
2425 while (*from)
2427 from = PathFindNextComponentW(from);
2428 lstrcatW(path, *from ? L"..\\" : L"..");
2431 /* From the root add the components of 'to' */
2432 to += len;
2433 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2434 if (*to && to[-1])
2436 if (*to != '\\')
2437 to--;
2438 len = lstrlenW(path);
2439 if (len + lstrlenW(to) >= MAX_PATH)
2441 *path = '\0';
2442 return FALSE;
2444 lstrcpyW(path + len, to);
2447 return TRUE;
2450 BOOL WINAPI PathMatchSpecA(const char *path, const char *mask)
2452 WCHAR *pathW, *maskW;
2453 BOOL ret;
2455 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask));
2457 if (!lstrcmpA(mask, "*.*"))
2458 return TRUE; /* Matches every path */
2460 pathW = heap_strdupAtoW( path );
2461 maskW = heap_strdupAtoW( mask );
2462 ret = PathMatchSpecW( pathW, maskW );
2463 heap_free( pathW );
2464 heap_free( maskW );
2465 return ret;
2468 static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask)
2470 while (*name && *mask && *mask != ';')
2472 if (*mask == '*')
2476 if (path_match_maskW(name, mask + 1))
2477 return TRUE; /* try substrings */
2478 } while (*name++);
2479 return FALSE;
2482 if (towupper(*mask) != towupper(*name) && *mask != '?')
2483 return FALSE;
2485 name++;
2486 mask++;
2489 if (!*name)
2491 while (*mask == '*')
2492 mask++;
2493 if (!*mask || *mask == ';')
2494 return TRUE;
2497 return FALSE;
2500 BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask)
2502 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask));
2504 if (!lstrcmpW(mask, L"*.*"))
2505 return TRUE; /* Matches every path */
2507 while (*mask)
2509 while (*mask == ' ')
2510 mask++; /* Eat leading spaces */
2512 if (path_match_maskW(path, mask))
2513 return TRUE; /* Matches the current path */
2515 while (*mask && *mask != ';')
2516 mask++; /* masks separated by ';' */
2518 if (*mask == ';')
2519 mask++;
2522 return FALSE;
2525 void WINAPI PathQuoteSpacesA(char *path)
2527 TRACE("%s\n", wine_dbgstr_a(path));
2529 if (path && StrChrA(path, ' '))
2531 size_t len = strlen(path) + 1;
2533 if (len + 2 < MAX_PATH)
2535 memmove(path + 1, path, len);
2536 path[0] = '"';
2537 path[len] = '"';
2538 path[len + 1] = '\0';
2543 void WINAPI PathQuoteSpacesW(WCHAR *path)
2545 TRACE("%s\n", wine_dbgstr_w(path));
2547 if (path && StrChrW(path, ' '))
2549 int len = lstrlenW(path) + 1;
2551 if (len + 2 < MAX_PATH)
2553 memmove(path + 1, path, len * sizeof(WCHAR));
2554 path[0] = '"';
2555 path[len] = '"';
2556 path[len + 1] = '\0';
2561 BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2)
2563 const char *start;
2564 int len;
2566 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2));
2568 if (!path1 || !path2 || !(start = PathSkipRootA(path1)))
2569 return FALSE;
2571 len = PathCommonPrefixA(path1, path2, NULL) + 1;
2572 return start - path1 <= len;
2575 BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2)
2577 const WCHAR *start;
2578 int len;
2580 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2));
2582 if (!path1 || !path2 || !(start = PathSkipRootW(path1)))
2583 return FALSE;
2585 len = PathCommonPrefixW(path1, path2, NULL) + 1;
2586 return start - path1 <= len;
2589 BOOL WINAPI PathFileExistsA(const char *path)
2591 UINT prev_mode;
2592 DWORD attrs;
2594 TRACE("%s\n", wine_dbgstr_a(path));
2596 if (!path)
2597 return FALSE;
2599 /* Prevent a dialog box if path is on a disk that has been ejected. */
2600 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2601 attrs = GetFileAttributesA(path);
2602 SetErrorMode(prev_mode);
2603 return attrs != INVALID_FILE_ATTRIBUTES;
2606 BOOL WINAPI PathFileExistsW(const WCHAR *path)
2608 UINT prev_mode;
2609 DWORD attrs;
2611 TRACE("%s\n", wine_dbgstr_w(path));
2613 if (!path)
2614 return FALSE;
2616 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2617 attrs = GetFileAttributesW(path);
2618 SetErrorMode(prev_mode);
2619 return attrs != INVALID_FILE_ATTRIBUTES;
2622 int WINAPI PathParseIconLocationA(char *path)
2624 int ret = 0;
2625 char *comma;
2627 TRACE("%s\n", debugstr_a(path));
2629 if (!path)
2630 return 0;
2632 if ((comma = strchr(path, ',')))
2634 *comma++ = '\0';
2635 ret = StrToIntA(comma);
2637 PathUnquoteSpacesA(path);
2638 PathRemoveBlanksA(path);
2640 return ret;
2643 int WINAPI PathParseIconLocationW(WCHAR *path)
2645 WCHAR *comma;
2646 int ret = 0;
2648 TRACE("%s\n", debugstr_w(path));
2650 if (!path)
2651 return 0;
2653 if ((comma = StrChrW(path, ',')))
2655 *comma++ = '\0';
2656 ret = StrToIntW(comma);
2658 PathUnquoteSpacesW(path);
2659 PathRemoveBlanksW(path);
2661 return ret;
2664 BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len)
2666 WCHAR bufferW[MAX_PATH], *pathW;
2667 DWORD len;
2668 BOOL ret;
2670 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len);
2672 pathW = heap_strdupAtoW(path);
2673 if (!pathW) return FALSE;
2675 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
2676 HeapFree(GetProcessHeap(), 0, pathW);
2677 if (!ret) return FALSE;
2679 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
2680 if (buf_len < len + 1) return FALSE;
2682 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
2683 return TRUE;
2686 struct envvars_map
2688 const WCHAR *var;
2689 WCHAR path[MAX_PATH];
2690 DWORD len;
2693 static void init_envvars_map(struct envvars_map *map)
2695 while (map->var)
2697 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path));
2698 /* exclude null from length */
2699 if (map->len) map->len--;
2700 map++;
2704 BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len)
2706 static struct envvars_map null_var = {L"", {0}, 0};
2707 struct envvars_map *match = &null_var, *cur;
2708 struct envvars_map envvars[] =
2710 { L"%ALLUSERSPROFILE%" },
2711 { L"%APPDATA%" },
2712 { L"%ProgramFiles%" },
2713 { L"%SystemRoot%" },
2714 { L"%SystemDrive%" },
2715 { L"%USERPROFILE%" },
2716 { NULL }
2718 DWORD pathlen;
2719 UINT needed;
2721 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len);
2723 pathlen = lstrlenW(path);
2724 init_envvars_map(envvars);
2725 cur = envvars;
2726 while (cur->var)
2728 /* path can't contain expanded value or value wasn't retrieved */
2729 if (cur->len == 0 || cur->len > pathlen ||
2730 CompareStringOrdinal( cur->path, cur->len, path, cur->len, TRUE ) != CSTR_EQUAL)
2732 cur++;
2733 continue;
2736 if (cur->len > match->len)
2737 match = cur;
2738 cur++;
2741 needed = lstrlenW(match->var) + 1 + pathlen - match->len;
2742 if (match->len == 0 || needed > buf_len) return FALSE;
2744 lstrcpyW(buffer, match->var);
2745 lstrcatW(buffer, &path[match->len]);
2746 TRACE("ret %s\n", debugstr_w(buffer));
2748 return TRUE;
2751 static const struct
2753 URL_SCHEME scheme_number;
2754 const WCHAR *scheme_name;
2756 url_schemes[] =
2758 { URL_SCHEME_FTP, L"ftp"},
2759 { URL_SCHEME_HTTP, L"http"},
2760 { URL_SCHEME_GOPHER, L"gopher"},
2761 { URL_SCHEME_MAILTO, L"mailto"},
2762 { URL_SCHEME_NEWS, L"news"},
2763 { URL_SCHEME_NNTP, L"nntp"},
2764 { URL_SCHEME_TELNET, L"telnet"},
2765 { URL_SCHEME_WAIS, L"wais"},
2766 { URL_SCHEME_FILE, L"file"},
2767 { URL_SCHEME_MK, L"mk"},
2768 { URL_SCHEME_HTTPS, L"https"},
2769 { URL_SCHEME_SHELL, L"shell"},
2770 { URL_SCHEME_SNEWS, L"snews"},
2771 { URL_SCHEME_LOCAL, L"local"},
2772 { URL_SCHEME_JAVASCRIPT, L"javascript"},
2773 { URL_SCHEME_VBSCRIPT, L"vbscript"},
2774 { URL_SCHEME_ABOUT, L"about"},
2775 { URL_SCHEME_RES, L"res"},
2778 static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len)
2780 unsigned int i;
2782 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i)
2784 if (scheme_len == lstrlenW(url_schemes[i].scheme_name)
2785 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len))
2786 return url_schemes[i].scheme_number;
2789 return URL_SCHEME_UNKNOWN;
2792 HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result)
2794 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
2795 const char *ptr = url;
2796 int len;
2798 TRACE("%s, %p\n", wine_dbgstr_a(url), result);
2800 if (result->cbSize != sizeof(*result))
2801 return E_INVALIDARG;
2803 while (*ptr && ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') ||
2804 (*ptr >= '0' && *ptr <= '9') || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2805 ptr++;
2807 if (*ptr != ':' || ptr <= url + 1)
2809 result->pszProtocol = NULL;
2810 return URL_E_INVALID_SYNTAX;
2813 result->pszProtocol = url;
2814 result->cchProtocol = ptr - url;
2815 result->pszSuffix = ptr + 1;
2816 result->cchSuffix = strlen(result->pszSuffix);
2818 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme));
2819 result->nScheme = get_scheme_code(scheme, len);
2821 return S_OK;
2824 HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result)
2826 const WCHAR *ptr = url;
2828 TRACE("%s, %p\n", wine_dbgstr_w(url), result);
2830 if (result->cbSize != sizeof(*result))
2831 return E_INVALIDARG;
2833 while (*ptr && (isalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2834 ptr++;
2836 if (*ptr != ':' || ptr <= url + 1)
2838 result->pszProtocol = NULL;
2839 return URL_E_INVALID_SYNTAX;
2842 result->pszProtocol = url;
2843 result->cchProtocol = ptr - url;
2844 result->pszSuffix = ptr + 1;
2845 result->cchSuffix = lstrlenW(result->pszSuffix);
2846 result->nScheme = get_scheme_code(url, ptr - url);
2848 return S_OK;
2851 HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags)
2853 BOOL stop_unescaping = FALSE;
2854 const char *src;
2855 char *dst, next;
2856 DWORD needed;
2857 HRESULT hr;
2859 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags);
2861 if (!url)
2862 return E_INVALIDARG;
2864 if (flags & URL_UNESCAPE_INPLACE)
2865 dst = url;
2866 else
2868 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2869 dst = unescaped;
2872 for (src = url, needed = 0; *src; src++, needed++)
2874 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2876 stop_unescaping = TRUE;
2877 next = *src;
2879 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2881 INT ih;
2882 char buf[3];
2883 memcpy(buf, src + 1, 2);
2884 buf[2] = '\0';
2885 ih = strtol(buf, NULL, 16);
2886 next = (CHAR) ih;
2887 src += 2; /* Advance to end of escape */
2889 else
2890 next = *src;
2892 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2893 *dst++ = next;
2896 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2898 *dst = '\0';
2899 hr = S_OK;
2901 else
2903 needed++; /* add one for the '\0' */
2904 hr = E_POINTER;
2907 if (!(flags & URL_UNESCAPE_INPLACE))
2908 *unescaped_len = needed;
2910 if (hr == S_OK)
2911 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped));
2913 return hr;
2916 HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags)
2918 BOOL stop_unescaping = FALSE;
2919 const WCHAR *src;
2920 WCHAR *dst, next;
2921 DWORD needed;
2922 HRESULT hr;
2924 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags);
2926 if (!url)
2927 return E_INVALIDARG;
2929 if (flags & URL_UNESCAPE_INPLACE)
2930 dst = url;
2931 else
2933 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2934 dst = unescaped;
2937 for (src = url, needed = 0; *src; src++, needed++)
2939 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2941 stop_unescaping = TRUE;
2942 next = *src;
2944 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2946 INT ih;
2947 WCHAR buf[5] = L"0x";
2948 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
2949 buf[4] = 0;
2950 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
2951 next = (WCHAR) ih;
2952 src += 2; /* Advance to end of escape */
2954 else
2955 next = *src;
2957 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2958 *dst++ = next;
2961 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2963 *dst = '\0';
2964 hr = S_OK;
2966 else
2968 needed++; /* add one for the '\0' */
2969 hr = E_POINTER;
2972 if (!(flags & URL_UNESCAPE_INPLACE))
2973 *unescaped_len = needed;
2975 if (hr == S_OK)
2976 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped));
2978 return hr;
2981 HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved)
2983 WCHAR bufW[MAX_PATH];
2984 WCHAR *pathW = bufW;
2985 UNICODE_STRING urlW;
2986 HRESULT ret;
2987 DWORD lenW = ARRAY_SIZE(bufW), lenA;
2989 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
2990 return E_INVALIDARG;
2992 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
2993 return E_INVALIDARG;
2994 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
2995 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2996 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
2998 if(ret == S_OK) {
2999 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
3000 if(*pcchPath > lenA) {
3001 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
3002 pszPath[lenA] = 0;
3003 *pcchPath = lenA;
3004 } else {
3005 *pcchPath = lenA + 1;
3006 ret = E_POINTER;
3009 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
3010 RtlFreeUnicodeString(&urlW);
3011 return ret;
3014 HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved)
3016 DWORD nslashes, unescape, len;
3017 const WCHAR *src;
3018 WCHAR *tpath, *dst;
3019 HRESULT hr = S_OK;
3021 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), path, pcchPath, dwReserved);
3023 if (!url || !path || !pcchPath || !*pcchPath)
3024 return E_INVALIDARG;
3026 if (wcsnicmp( url, L"file:", 5))
3027 return E_INVALIDARG;
3029 url += 5;
3031 src = url;
3032 nslashes = 0;
3033 while (*src == '/' || *src == '\\')
3035 nslashes++;
3036 src++;
3039 /* We need a temporary buffer so we can compute what size to ask for.
3040 * We know that the final string won't be longer than the current pszUrl
3041 * plus at most two backslashes. All the other transformations make it
3042 * shorter.
3044 len = 2 + lstrlenW(url) + 1;
3045 if (*pcchPath < len)
3046 tpath = heap_alloc(len * sizeof(WCHAR));
3047 else
3048 tpath = path;
3050 len = 0;
3051 dst = tpath;
3052 unescape = 1;
3053 switch (nslashes)
3055 case 0:
3056 /* 'file:' + escaped DOS path */
3057 break;
3058 case 1:
3059 /* 'file:/' + escaped DOS path */
3060 /* fall through */
3061 case 3:
3062 /* 'file:///' (implied localhost) + escaped DOS path */
3063 if (!is_escaped_drive_spec( src ))
3064 src -= 1;
3065 break;
3066 case 2:
3067 if (lstrlenW(src) >= 10 && !wcsnicmp( src, L"localhost", 9) && (src[9] == '/' || src[9] == '\\'))
3069 /* 'file://localhost/' + escaped DOS path */
3070 src += 10;
3072 else if (is_escaped_drive_spec( src ))
3074 /* 'file://' + unescaped DOS path */
3075 unescape = 0;
3077 else
3079 /* 'file://hostname:port/path' (where path is escaped)
3080 * or 'file:' + escaped UNC path (\\server\share\path)
3081 * The second form is clearly specific to Windows and it might
3082 * even be doing a network lookup to try to figure it out.
3084 while (*src && *src != '/' && *src != '\\')
3085 src++;
3086 len = src - url;
3087 StrCpyNW(dst, url, len + 1);
3088 dst += len;
3089 if (*src && is_escaped_drive_spec( src + 1 ))
3091 /* 'Forget' to add a trailing '/', just like Windows */
3092 src++;
3095 break;
3096 case 4:
3097 /* 'file://' + unescaped UNC path (\\server\share\path) */
3098 unescape = 0;
3099 if (is_escaped_drive_spec( src ))
3100 break;
3101 /* fall through */
3102 default:
3103 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3104 src -= 2;
3107 /* Copy the remainder of the path */
3108 len += lstrlenW(src);
3109 lstrcpyW(dst, src);
3111 /* First do the Windows-specific path conversions */
3112 for (dst = tpath; *dst; dst++)
3113 if (*dst == '/') *dst = '\\';
3114 if (is_escaped_drive_spec( tpath ))
3115 tpath[1] = ':'; /* c| -> c: */
3117 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3118 if (unescape)
3120 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3121 if (hr == S_OK)
3123 /* When working in-place UrlUnescapeW() does not set len */
3124 len = lstrlenW(tpath);
3128 if (*pcchPath < len + 1)
3130 hr = E_POINTER;
3131 *pcchPath = len + 1;
3133 else
3135 *pcchPath = len;
3136 if (tpath != path)
3137 lstrcpyW(path, tpath);
3139 if (tpath != path)
3140 heap_free(tpath);
3142 TRACE("Returning (%u) %s\n", *pcchPath, wine_dbgstr_w(path));
3143 return hr;
3146 HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved)
3148 WCHAR pathW[MAX_PATH];
3149 DWORD size;
3150 HRESULT hr;
3152 size = MAX_PATH;
3153 hr = PathCreateFromUrlW(url, pathW, &size, reserved);
3154 if (SUCCEEDED(hr))
3156 /* Yes, this is supposed to crash if 'path' is NULL */
3157 *path = StrDupW(pathW);
3160 return hr;
3163 BOOL WINAPI PathIsURLA(const char *path)
3165 PARSEDURLA base;
3166 HRESULT hr;
3168 TRACE("%s\n", wine_dbgstr_a(path));
3170 if (!path || !*path)
3171 return FALSE;
3173 /* get protocol */
3174 base.cbSize = sizeof(base);
3175 hr = ParseURLA(path, &base);
3176 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3179 BOOL WINAPI PathIsURLW(const WCHAR *path)
3181 PARSEDURLW base;
3182 HRESULT hr;
3184 TRACE("%s\n", wine_dbgstr_w(path));
3186 if (!path || !*path)
3187 return FALSE;
3189 /* get protocol */
3190 base.cbSize = sizeof(base);
3191 hr = ParseURLW(path, &base);
3192 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3195 #define WINE_URL_BASH_AS_SLASH 0x01
3196 #define WINE_URL_COLLAPSE_SLASHES 0x02
3197 #define WINE_URL_ESCAPE_SLASH 0x04
3198 #define WINE_URL_ESCAPE_HASH 0x08
3199 #define WINE_URL_ESCAPE_QUESTION 0x10
3200 #define WINE_URL_STOP_ON_HASH 0x20
3201 #define WINE_URL_STOP_ON_QUESTION 0x40
3203 static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags)
3205 if (flags & URL_ESCAPE_SPACES_ONLY)
3206 return ch == ' ';
3208 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%'))
3209 return TRUE;
3211 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80))
3212 return TRUE;
3214 if (ch <= 31 || (ch >= 127 && ch <= 255) )
3215 return TRUE;
3217 if (isalnum(ch))
3218 return FALSE;
3220 switch (ch) {
3221 case ' ':
3222 case '<':
3223 case '>':
3224 case '\"':
3225 case '{':
3226 case '}':
3227 case '|':
3228 case '\\':
3229 case '^':
3230 case ']':
3231 case '[':
3232 case '`':
3233 case '&':
3234 return TRUE;
3235 case '/':
3236 return !!(int_flags & WINE_URL_ESCAPE_SLASH);
3237 case '?':
3238 return !!(int_flags & WINE_URL_ESCAPE_QUESTION);
3239 case '#':
3240 return !!(int_flags & WINE_URL_ESCAPE_HASH);
3241 default:
3242 return FALSE;
3246 HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags)
3248 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
3249 WCHAR *escapedW = bufW;
3250 UNICODE_STRING urlW;
3251 HRESULT hr;
3252 DWORD lenW = ARRAY_SIZE(bufW), lenA;
3254 if (!escaped || !escaped_len || !*escaped_len)
3255 return E_INVALIDARG;
3257 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url))
3258 return E_INVALIDARG;
3260 if (flags & URL_ESCAPE_AS_UTF8)
3262 RtlFreeUnicodeString(&urlW);
3263 return E_NOTIMPL;
3266 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER)
3268 escapedW = heap_alloc(lenW * sizeof(WCHAR));
3269 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags);
3272 if (hr == S_OK)
3274 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
3275 if (*escaped_len > lenA)
3277 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
3278 escaped[lenA] = 0;
3279 *escaped_len = lenA;
3281 else
3283 *escaped_len = lenA + 1;
3284 hr = E_POINTER;
3287 if (escapedW != bufW)
3288 heap_free(escapedW);
3289 RtlFreeUnicodeString(&urlW);
3290 return hr;
3293 HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags)
3295 DWORD needed = 0, slashes = 0, int_flags;
3296 WCHAR next[12], *dst, *dst_ptr;
3297 BOOL stop_escaping = FALSE;
3298 PARSEDURLW parsed_url;
3299 const WCHAR *src;
3300 INT i, len;
3301 HRESULT hr;
3303 TRACE("%p, %s, %p, %p, %#x\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags);
3305 if (!url || !escaped_len || !escaped || *escaped_len == 0)
3306 return E_INVALIDARG;
3308 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO |
3309 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8))
3311 FIXME("Unimplemented flags: %08x\n", flags);
3314 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR));
3315 if (!dst_ptr)
3316 return E_OUTOFMEMORY;
3318 /* fix up flags */
3319 if (flags & URL_ESCAPE_SPACES_ONLY)
3320 /* if SPACES_ONLY specified, reset the other controls */
3321 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY);
3322 else
3323 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3324 flags |= URL_DONT_ESCAPE_EXTRA_INFO;
3326 int_flags = 0;
3327 if (flags & URL_ESCAPE_SEGMENT_ONLY)
3328 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
3329 else
3331 parsed_url.cbSize = sizeof(parsed_url);
3332 if (ParseURLW(url, &parsed_url) != S_OK)
3333 parsed_url.nScheme = URL_SCHEME_INVALID;
3335 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
3337 if (flags & URL_DONT_ESCAPE_EXTRA_INFO)
3338 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
3340 switch(parsed_url.nScheme) {
3341 case URL_SCHEME_FILE:
3342 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
3343 int_flags &= ~WINE_URL_STOP_ON_HASH;
3344 break;
3346 case URL_SCHEME_HTTP:
3347 case URL_SCHEME_HTTPS:
3348 int_flags |= WINE_URL_BASH_AS_SLASH;
3349 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
3350 int_flags |= WINE_URL_ESCAPE_SLASH;
3351 break;
3353 case URL_SCHEME_MAILTO:
3354 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
3355 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
3356 break;
3358 case URL_SCHEME_INVALID:
3359 break;
3361 case URL_SCHEME_FTP:
3362 default:
3363 if(parsed_url.pszSuffix[0] != '/')
3364 int_flags |= WINE_URL_ESCAPE_SLASH;
3365 break;
3369 for (src = url; *src; )
3371 WCHAR cur = *src;
3372 len = 0;
3374 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1)
3376 while (cur == '/' || cur == '\\')
3378 slashes++;
3379 cur = *++src;
3381 if (slashes == 2 && !wcsnicmp(src, L"localhost", 9)) { /* file://localhost/ -> file:/// */
3382 if(src[9] == '/' || src[9] == '\\') src += 10;
3383 slashes = 3;
3386 switch (slashes)
3388 case 1:
3389 case 3:
3390 next[0] = next[1] = next[2] = '/';
3391 len = 3;
3392 break;
3393 case 0:
3394 len = 0;
3395 break;
3396 default:
3397 next[0] = next[1] = '/';
3398 len = 2;
3399 break;
3402 if (len == 0)
3404 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
3405 stop_escaping = TRUE;
3407 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
3408 stop_escaping = TRUE;
3410 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
3412 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping)
3414 if (flags & URL_ESCAPE_AS_UTF8)
3416 char utf[16];
3418 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff))
3420 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL);
3421 src++;
3423 else
3424 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL);
3426 if (!len)
3428 utf[0] = 0xef;
3429 utf[1] = 0xbf;
3430 utf[2] = 0xbd;
3431 len = 3;
3434 for (i = 0; i < len; ++i)
3436 next[i*3+0] = '%';
3437 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf];
3438 next[i*3+2] = hexDigits[utf[i] & 0xf];
3440 len *= 3;
3442 else
3444 next[0] = '%';
3445 next[1] = hexDigits[(cur >> 4) & 0xf];
3446 next[2] = hexDigits[cur & 0xf];
3447 len = 3;
3450 else
3452 next[0] = cur;
3453 len = 1;
3455 src++;
3458 if (needed + len <= *escaped_len)
3460 memcpy(dst, next, len*sizeof(WCHAR));
3461 dst += len;
3463 needed += len;
3466 if (needed < *escaped_len)
3468 *dst = '\0';
3469 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR));
3470 hr = S_OK;
3472 else
3474 needed++; /* add one for the '\0' */
3475 hr = E_POINTER;
3477 *escaped_len = needed;
3479 heap_free(dst_ptr);
3480 return hr;
3483 HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags)
3485 LPWSTR url, canonical;
3486 HRESULT hr;
3488 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags);
3490 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3491 return E_INVALIDARG;
3493 url = heap_strdupAtoW(src_url);
3494 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR));
3495 if (!url || !canonical)
3497 heap_free(url);
3498 heap_free(canonical);
3499 return E_OUTOFMEMORY;
3502 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags);
3503 if (hr == S_OK)
3504 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL);
3506 heap_free(url);
3507 heap_free(canonical);
3508 return hr;
3511 HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags)
3513 WCHAR *url_copy, *url, *wk2, *mp, *mp2;
3514 DWORD nByteLen, nLen, nWkLen;
3515 const WCHAR *wk1, *root;
3516 DWORD escape_flags;
3517 WCHAR slash = '\0';
3518 HRESULT hr = S_OK;
3519 BOOL is_file_url;
3520 INT state;
3522 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags);
3524 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3525 return E_INVALIDARG;
3527 if (!*src_url)
3529 *canonicalized = 0;
3530 return S_OK;
3533 /* Remove '\t' characters from URL */
3534 nByteLen = (lstrlenW(src_url) + 1) * sizeof(WCHAR); /* length in bytes */
3535 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
3536 if(!url)
3537 return E_OUTOFMEMORY;
3539 wk1 = src_url;
3540 wk2 = url;
3543 while(*wk1 == '\t')
3544 wk1++;
3545 *wk2++ = *wk1;
3546 } while (*wk1++);
3548 /* Allocate memory for simplified URL (before escaping) */
3549 nByteLen = (wk2-url)*sizeof(WCHAR);
3550 url_copy = heap_alloc(nByteLen + sizeof(L"file:///"));
3551 if (!url_copy)
3553 heap_free(url);
3554 return E_OUTOFMEMORY;
3557 is_file_url = !wcsncmp(url, L"file:", 5);
3559 if ((nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"http:", 5)) || is_file_url)
3560 slash = '/';
3562 if ((flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
3563 slash = '\\';
3565 if (nByteLen >= 4*sizeof(WCHAR) && !wcsncmp(url, L"res:", 4))
3567 flags &= ~URL_FILE_USE_PATHURL;
3568 slash = '\0';
3572 * state =
3573 * 0 initial 1,3
3574 * 1 have 2[+] alnum 2,3
3575 * 2 have scheme (found :) 4,6,3
3576 * 3 failed (no location)
3577 * 4 have // 5,3
3578 * 5 have 1[+] alnum 6,3
3579 * 6 have location (found /) save root location
3582 wk1 = url;
3583 wk2 = url_copy;
3584 state = 0;
3586 /* Assume path */
3587 if (url[1] == ':')
3589 lstrcpyW(wk2, L"file:///");
3590 wk2 += lstrlenW(wk2);
3591 if (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
3593 slash = '\\';
3594 --wk2;
3596 else
3597 flags |= URL_ESCAPE_UNSAFE;
3598 state = 5;
3599 is_file_url = TRUE;
3601 else if (url[0] == '/')
3603 state = 5;
3604 is_file_url = TRUE;
3607 while (*wk1)
3609 switch (state)
3611 case 0:
3612 if (!isalnum(*wk1)) {state = 3; break;}
3613 *wk2++ = *wk1++;
3614 if (!isalnum(*wk1)) {state = 3; break;}
3615 *wk2++ = *wk1++;
3616 state = 1;
3617 break;
3618 case 1:
3619 *wk2++ = *wk1;
3620 if (*wk1++ == ':') state = 2;
3621 break;
3622 case 2:
3623 *wk2++ = *wk1++;
3624 if (*wk1 != '/') {state = 6; break;}
3625 *wk2++ = *wk1++;
3626 if ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 9*sizeof(WCHAR) && is_file_url
3627 && !wcsncmp(wk1, L"localhost", 9))
3629 wk1 += 9;
3630 while (*wk1 == '\\' && (flags & URL_FILE_USE_PATHURL))
3631 wk1++;
3634 if (*wk1 == '/' && (flags & URL_FILE_USE_PATHURL))
3635 wk1++;
3636 else if (is_file_url)
3638 const WCHAR *body = wk1;
3640 while (*body == '/')
3641 ++body;
3643 if (is_drive_spec( body ))
3645 if (!(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)))
3647 if (slash)
3648 *wk2++ = slash;
3649 else
3650 *wk2++ = '/';
3653 else
3655 if (flags & URL_WININET_COMPATIBILITY)
3657 if (*wk1 == '/' && *(wk1 + 1) != '/')
3659 *wk2++ = '\\';
3661 else
3663 *wk2++ = '\\';
3664 *wk2++ = '\\';
3667 else
3669 if (*wk1 == '/' && *(wk1+1) != '/')
3671 if (slash)
3672 *wk2++ = slash;
3673 else
3674 *wk2++ = '/';
3678 wk1 = body;
3680 state = 4;
3681 break;
3682 case 3:
3683 nWkLen = lstrlenW(wk1);
3684 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3685 mp = wk2;
3686 wk1 += nWkLen;
3687 wk2 += nWkLen;
3689 if (slash)
3691 while (mp < wk2)
3693 if (*mp == '/' || *mp == '\\')
3694 *mp = slash;
3695 mp++;
3698 break;
3699 case 4:
3700 if (!isalnum(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
3702 state = 3;
3703 break;
3705 while (isalnum(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
3706 *wk2++ = *wk1++;
3707 state = 5;
3708 if (!*wk1)
3710 if (slash)
3711 *wk2++ = slash;
3712 else
3713 *wk2++ = '/';
3715 break;
3716 case 5:
3717 if (*wk1 != '/' && *wk1 != '\\')
3719 state = 3;
3720 break;
3722 while (*wk1 == '/' || *wk1 == '\\')
3724 if (slash)
3725 *wk2++ = slash;
3726 else
3727 *wk2++ = *wk1;
3728 wk1++;
3730 state = 6;
3731 break;
3732 case 6:
3733 if (flags & URL_DONT_SIMPLIFY)
3735 state = 3;
3736 break;
3739 /* Now at root location, cannot back up any more. */
3740 /* "root" will point at the '/' */
3742 root = wk2-1;
3743 while (*wk1)
3745 mp = wcschr(wk1, '/');
3746 mp2 = wcschr(wk1, '\\');
3747 if (mp2 && (!mp || mp2 < mp))
3748 mp = mp2;
3749 if (!mp)
3751 nWkLen = lstrlenW(wk1);
3752 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3753 wk1 += nWkLen;
3754 wk2 += nWkLen;
3755 continue;
3757 nLen = mp - wk1;
3758 if (nLen)
3760 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
3761 wk2 += nLen;
3762 wk1 += nLen;
3764 if (slash)
3765 *wk2++ = slash;
3766 else
3767 *wk2++ = *wk1;
3768 wk1++;
3770 while (*wk1 == '.')
3772 TRACE("found '/.'\n");
3773 if (wk1[1] == '/' || wk1[1] == '\\')
3775 /* case of /./ -> skip the ./ */
3776 wk1 += 2;
3778 else if (wk1[1] == '.' && (wk1[2] == '/' || wk1[2] == '\\' || wk1[2] == '?'
3779 || wk1[2] == '#' || !wk1[2]))
3781 /* case /../ -> need to backup wk2 */
3782 TRACE("found '/../'\n");
3783 *(wk2-1) = '\0'; /* set end of string */
3784 mp = wcsrchr(root, '/');
3785 mp2 = wcsrchr(root, '\\');
3786 if (mp2 && (!mp || mp2 < mp))
3787 mp = mp2;
3788 if (mp && (mp >= root))
3790 /* found valid backup point */
3791 wk2 = mp + 1;
3792 if(wk1[2] != '/' && wk1[2] != '\\')
3793 wk1 += 2;
3794 else
3795 wk1 += 3;
3797 else
3799 /* did not find point, restore '/' */
3800 *(wk2-1) = slash;
3801 break;
3804 else
3805 break;
3808 *wk2 = '\0';
3809 break;
3810 default:
3811 FIXME("how did we get here - state=%d\n", state);
3812 heap_free(url_copy);
3813 heap_free(url);
3814 return E_INVALIDARG;
3816 *wk2 = '\0';
3817 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url), wine_dbgstr_w(url_copy));
3819 nLen = lstrlenW(url_copy);
3820 while ((nLen > 0) && ((url_copy[nLen-1] <= ' ')))
3821 url_copy[--nLen]=0;
3823 if ((flags & URL_UNESCAPE) ||
3824 ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"file:", 5)))
3826 UrlUnescapeW(url_copy, NULL, &nLen, URL_UNESCAPE_INPLACE);
3829 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT |
3830 URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_SEGMENT_ONLY);
3832 if (escape_flags)
3834 escape_flags &= ~URL_ESCAPE_UNSAFE;
3835 hr = UrlEscapeW(url_copy, canonicalized, canonicalized_len, escape_flags);
3837 else
3839 /* No escaping needed, just copy the string */
3840 nLen = lstrlenW(url_copy);
3841 if (nLen < *canonicalized_len)
3842 memcpy(canonicalized, url_copy, (nLen + 1)*sizeof(WCHAR));
3843 else
3845 hr = E_POINTER;
3846 nLen++;
3848 *canonicalized_len = nLen;
3851 heap_free(url_copy);
3852 heap_free(url);
3854 if (hr == S_OK)
3855 TRACE("result %s\n", wine_dbgstr_w(canonicalized));
3857 return hr;
3860 HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags)
3862 LPWSTR inW, outW;
3863 HRESULT hr;
3864 DWORD len;
3866 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags);
3868 if (!url || !out || !out_len)
3869 return E_INVALIDARG;
3871 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
3872 outW = inW + INTERNET_MAX_URL_LENGTH;
3874 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
3875 len = INTERNET_MAX_URL_LENGTH;
3877 hr = UrlApplySchemeW(inW, outW, &len, flags);
3878 if (hr != S_OK)
3880 heap_free(inW);
3881 return hr;
3884 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL);
3885 if (len > *out_len)
3887 hr = E_POINTER;
3888 goto cleanup;
3891 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL);
3892 len--;
3894 cleanup:
3895 *out_len = len;
3896 heap_free(inW);
3897 return hr;
3900 static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len)
3902 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH];
3903 DWORD value_len, data_len, dwType, i;
3904 WCHAR Wxx, Wyy;
3905 HKEY newkey;
3906 INT index;
3907 BOOL j;
3909 MultiByteToWideChar(CP_ACP, 0,
3910 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH);
3911 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
3912 index = 0;
3913 while (value_len = data_len = MAX_PATH,
3914 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0)
3916 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data));
3918 j = FALSE;
3919 for (i = 0; i < value_len; ++i)
3921 Wxx = url[i];
3922 Wyy = value[i];
3923 /* remember that TRUE is not-equal */
3924 j = ChrCmpIW(Wxx, Wyy);
3925 if (j) break;
3927 if ((i == value_len) && !j)
3929 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len)
3931 *out_len = lstrlenW(data) + lstrlenW(url) + 1;
3932 RegCloseKey(newkey);
3933 return E_POINTER;
3935 lstrcpyW(out, data);
3936 lstrcatW(out, url);
3937 *out_len = lstrlenW(out);
3938 TRACE("matched and set to %s\n", wine_dbgstr_w(out));
3939 RegCloseKey(newkey);
3940 return S_OK;
3942 index++;
3944 RegCloseKey(newkey);
3945 return E_FAIL;
3948 static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len)
3950 PARSEDURLW parsed_url;
3951 WCHAR *new_url;
3952 DWORD needed;
3953 HRESULT hr;
3955 parsed_url.cbSize = sizeof(parsed_url);
3956 if (ParseURLW(path, &parsed_url) == S_OK)
3958 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1)
3960 needed = lstrlenW(path);
3961 if (needed >= *url_len)
3963 *url_len = needed + 1;
3964 return E_POINTER;
3966 else
3968 *url_len = needed;
3969 return S_FALSE;
3974 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */
3975 lstrcpyW(new_url, L"file:");
3976 if (is_drive_spec( path )) lstrcatW(new_url, L"///");
3977 lstrcatW(new_url, path);
3978 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT);
3979 heap_free(new_url);
3980 return hr;
3983 static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length)
3985 DWORD data_len, dwType;
3986 WCHAR data[MAX_PATH];
3987 HKEY newkey;
3989 /* get and prepend default */
3990 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
3991 0, 1, &newkey);
3992 data_len = sizeof(data);
3993 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len);
3994 RegCloseKey(newkey);
3995 if (lstrlenW(data) + lstrlenW(url) + 1 > *length)
3997 *length = lstrlenW(data) + lstrlenW(url) + 1;
3998 return E_POINTER;
4000 lstrcpyW(out, data);
4001 lstrcatW(out, url);
4002 *length = lstrlenW(out);
4003 TRACE("used default %s\n", wine_dbgstr_w(out));
4004 return S_OK;
4007 HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags)
4009 PARSEDURLW in_scheme;
4010 DWORD res1;
4011 HRESULT hr;
4013 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags);
4015 if (!url || !out || !length)
4016 return E_INVALIDARG;
4018 if (flags & URL_APPLY_GUESSFILE)
4020 if (*length > 1 && ':' == url[1])
4022 res1 = *length;
4023 hr = url_create_from_path(url, out, &res1);
4024 if (hr == S_OK || hr == E_POINTER)
4026 *length = res1;
4027 return hr;
4029 else if (hr == S_FALSE)
4031 return hr;
4036 in_scheme.cbSize = sizeof(in_scheme);
4037 /* See if the base has a scheme */
4038 res1 = ParseURLW(url, &in_scheme);
4039 if (res1)
4041 /* no scheme in input, need to see if we need to guess */
4042 if (flags & URL_APPLY_GUESSSCHEME)
4044 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL)
4045 return hr;
4049 /* If we are here, then either invalid scheme,
4050 * or no scheme and can't/failed guess.
4052 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT))
4053 return url_apply_default_scheme(url, out, length);
4055 return S_FALSE;
4058 INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash)
4060 INT ret, len, len1, len2;
4062 if (!ignore_slash)
4063 return strcmp(url1, url2);
4064 len1 = strlen(url1);
4065 if (url1[len1-1] == '/') len1--;
4066 len2 = strlen(url2);
4067 if (url2[len2-1] == '/') len2--;
4068 if (len1 == len2)
4069 return strncmp(url1, url2, len1);
4070 len = min(len1, len2);
4071 ret = strncmp(url1, url2, len);
4072 if (ret) return ret;
4073 if (len1 > len2) return 1;
4074 return -1;
4077 INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash)
4079 size_t len, len1, len2;
4080 INT ret;
4082 if (!ignore_slash)
4083 return lstrcmpW(url1, url2);
4084 len1 = lstrlenW(url1);
4085 if (url1[len1-1] == '/') len1--;
4086 len2 = lstrlenW(url2);
4087 if (url2[len2-1] == '/') len2--;
4088 if (len1 == len2)
4089 return wcsncmp(url1, url2, len1);
4090 len = min(len1, len2);
4091 ret = wcsncmp(url1, url2, len);
4092 if (ret) return ret;
4093 if (len1 > len2) return 1;
4094 return -1;
4097 HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars)
4099 DWORD srcLen;
4101 FIXME("%s, %p, %d stub\n", wine_dbgstr_w(url), translatedUrl, maxChars);
4103 if (!url)
4104 return E_FAIL;
4106 srcLen = lstrlenW(url) + 1;
4108 /* For now just copy the URL directly */
4109 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
4111 return S_OK;
4114 const char * WINAPI UrlGetLocationA(const char *url)
4116 PARSEDURLA base;
4118 base.cbSize = sizeof(base);
4119 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */
4121 /* if scheme is file: then never return pointer */
4122 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol)))
4123 return NULL;
4125 /* Look for '#' and return its addr */
4126 return strchr(base.pszSuffix, '#');
4129 const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url)
4131 PARSEDURLW base;
4133 base.cbSize = sizeof(base);
4134 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */
4136 /* if scheme is file: then never return pointer */
4137 if (!wcsncmp(base.pszProtocol, L"file", min(4, base.cchProtocol)))
4138 return NULL;
4140 /* Look for '#' and return its addr */
4141 return wcschr(base.pszSuffix, '#');
4144 HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags)
4146 LPWSTR inW, outW;
4147 DWORD len, len2;
4148 HRESULT hr;
4150 if (!url || !out || !out_len || !*out_len)
4151 return E_INVALIDARG;
4153 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4154 outW = inW + INTERNET_MAX_URL_LENGTH;
4156 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
4158 len = INTERNET_MAX_URL_LENGTH;
4159 hr = UrlGetPartW(inW, outW, &len, part, flags);
4160 if (FAILED(hr))
4162 heap_free(inW);
4163 return hr;
4166 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len, NULL, 0, NULL, NULL);
4167 if (len2 > *out_len)
4169 *out_len = len2 + 1;
4170 heap_free(inW);
4171 return E_POINTER;
4173 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL);
4174 *out_len = len2 - 1;
4175 heap_free(inW);
4176 return hr;
4179 static const WCHAR * scan_url(const WCHAR *start, DWORD *size, enum url_scan_type type)
4181 *size = 0;
4183 switch (type)
4185 case SCHEME:
4186 while ((*start >= 'a' && *start <= 'z') || (*start >= '0' && *start <= '9') ||
4187 *start == '+' || *start == '-' || *start == '.')
4189 start++;
4190 (*size)++;
4192 if (*start != ':')
4193 *size = 0;
4194 break;
4196 case USERPASS:
4197 for (;;)
4199 if (isalnum(*start) ||
4200 /* user/password only characters */
4201 (*start == ';') ||
4202 (*start == '?') ||
4203 (*start == '&') ||
4204 (*start == '=') ||
4205 /* *extra* characters */
4206 (*start == '!') ||
4207 (*start == '*') ||
4208 (*start == '\'') ||
4209 (*start == '(') ||
4210 (*start == ')') ||
4211 (*start == ',') ||
4212 /* *safe* characters */
4213 (*start == '$') ||
4214 (*start == '_') ||
4215 (*start == '+') ||
4216 (*start == '-') ||
4217 (*start == '.') ||
4218 (*start == ' '))
4220 start++;
4221 (*size)++;
4223 else if (*start == '%' && isxdigit(start[1]) && isxdigit(start[2]))
4225 start += 3;
4226 *size += 3;
4228 else break;
4230 break;
4232 case PORT:
4233 while (*start >= '0' && *start <= '9')
4235 start++;
4236 (*size)++;
4238 break;
4240 case HOST:
4241 while (isalnum(*start) || *start == '-' || *start == '.' || *start == ' ' || *start == '*')
4243 start++;
4244 (*size)++;
4246 break;
4248 default:
4249 FIXME("unknown type %d\n", type);
4250 return L"";
4253 return start;
4256 static LONG parse_url(const WCHAR *url, struct parsed_url *pl)
4258 const WCHAR *work;
4260 memset(pl, 0, sizeof(*pl));
4261 pl->scheme = url;
4262 work = scan_url(pl->scheme, &pl->scheme_len, SCHEME);
4263 if (!*work || (*work != ':')) goto ErrorExit;
4264 work++;
4265 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
4267 pl->username = work + 2;
4268 work = scan_url(pl->username, &pl->username_len, USERPASS);
4269 if (*work == ':' )
4271 /* parse password */
4272 work++;
4273 pl->password = work;
4274 work = scan_url(pl->password, &pl->password_len, USERPASS);
4275 if (*work != '@')
4277 /* what we just parsed must be the hostname and port
4278 * so reset pointers and clear then let it parse */
4279 pl->username_len = pl->password_len = 0;
4280 work = pl->username - 1;
4281 pl->username = pl->password = 0;
4284 else if (*work == '@')
4286 /* no password */
4287 pl->password_len = 0;
4288 pl->password = 0;
4290 else if (!*work || *work == '/' || *work == '.')
4292 /* what was parsed was hostname, so reset pointers and let it parse */
4293 pl->username_len = pl->password_len = 0;
4294 work = pl->username - 1;
4295 pl->username = pl->password = 0;
4297 else goto ErrorExit;
4299 /* now start parsing hostname or hostnumber */
4300 work++;
4301 pl->hostname = work;
4302 work = scan_url(pl->hostname, &pl->hostname_len, HOST);
4303 if (*work == ':')
4305 /* parse port */
4306 work++;
4307 pl->port = work;
4308 work = scan_url(pl->port, &pl->port_len, PORT);
4310 if (*work == '/')
4312 /* see if query string */
4313 pl->query = wcschr(work, '?');
4314 if (pl->query) pl->query_len = lstrlenW(pl->query);
4316 SuccessExit:
4317 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
4318 pl->scheme, pl->scheme_len, pl->username, pl->username_len, pl->password, pl->password_len, pl->hostname,
4319 pl->hostname_len, pl->port, pl->port_len, pl->query, pl->query_len);
4321 return S_OK;
4323 ErrorExit:
4324 FIXME("failed to parse %s\n", debugstr_w(url));
4325 return E_INVALIDARG;
4328 HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags)
4330 DWORD scheme, size, schsize;
4331 LPCWSTR addr, schaddr;
4332 struct parsed_url pl;
4333 HRESULT hr;
4335 TRACE("%s, %p, %p(%d), %#x, %#x\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags);
4337 if (!url || !out || !out_len || !*out_len)
4338 return E_INVALIDARG;
4340 *out = '\0';
4342 addr = wcschr(url, ':');
4343 if (!addr)
4344 scheme = URL_SCHEME_UNKNOWN;
4345 else
4346 scheme = get_scheme_code(url, addr - url);
4348 hr = parse_url(url, &pl);
4350 switch (part)
4352 case URL_PART_SCHEME:
4353 if (!pl.scheme_len)
4355 *out_len = 0;
4356 return S_FALSE;
4358 addr = pl.scheme;
4359 size = pl.scheme_len;
4360 break;
4362 case URL_PART_HOSTNAME:
4363 switch (scheme)
4365 case URL_SCHEME_FTP:
4366 case URL_SCHEME_HTTP:
4367 case URL_SCHEME_GOPHER:
4368 case URL_SCHEME_TELNET:
4369 case URL_SCHEME_FILE:
4370 case URL_SCHEME_HTTPS:
4371 break;
4372 default:
4373 *out_len = 0;
4374 return E_FAIL;
4377 if (scheme == URL_SCHEME_FILE && (!pl.hostname_len || (pl.hostname_len == 1 && *(pl.hostname + 1) == ':')))
4379 *out_len = 0;
4380 return S_FALSE;
4383 if (!pl.hostname_len)
4385 *out_len = 0;
4386 return S_FALSE;
4388 addr = pl.hostname;
4389 size = pl.hostname_len;
4390 break;
4392 case URL_PART_USERNAME:
4393 if (!pl.username_len)
4395 *out_len = 0;
4396 return S_FALSE;
4398 addr = pl.username;
4399 size = pl.username_len;
4400 break;
4402 case URL_PART_PASSWORD:
4403 if (!pl.password_len)
4405 *out_len = 0;
4406 return S_FALSE;
4408 addr = pl.password;
4409 size = pl.password_len;
4410 break;
4412 case URL_PART_PORT:
4413 if (!pl.port_len)
4415 *out_len = 0;
4416 return S_FALSE;
4418 addr = pl.port;
4419 size = pl.port_len;
4420 break;
4422 case URL_PART_QUERY:
4423 if (!pl.query_len)
4425 *out_len = 0;
4426 return S_FALSE;
4428 addr = pl.query;
4429 size = pl.query_len;
4430 break;
4432 default:
4433 *out_len = 0;
4434 return E_INVALIDARG;
4437 if (flags == URL_PARTFLAG_KEEPSCHEME)
4439 if (!pl.scheme || !pl.scheme_len)
4441 *out_len = 0;
4442 return E_FAIL;
4444 schaddr = pl.scheme;
4445 schsize = pl.scheme_len;
4446 if (*out_len < schsize + size + 2)
4448 *out_len = schsize + size + 2;
4449 return E_POINTER;
4451 memcpy(out, schaddr, schsize*sizeof(WCHAR));
4452 out[schsize] = ':';
4453 memcpy(out + schsize+1, addr, size*sizeof(WCHAR));
4454 out[schsize+1+size] = 0;
4455 *out_len = schsize + 1 + size;
4457 else
4459 if (*out_len < size + 1)
4461 *out_len = size + 1;
4462 return E_POINTER;
4464 memcpy(out, addr, size*sizeof(WCHAR));
4465 out[size] = 0;
4466 *out_len = size;
4468 TRACE("len=%d %s\n", *out_len, wine_dbgstr_w(out));
4470 return hr;
4473 BOOL WINAPI UrlIsA(const char *url, URLIS Urlis)
4475 const char *last;
4476 PARSEDURLA base;
4478 TRACE("%s, %d\n", debugstr_a(url), Urlis);
4480 if (!url)
4481 return FALSE;
4483 switch (Urlis) {
4485 case URLIS_OPAQUE:
4486 base.cbSize = sizeof(base);
4487 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */
4488 switch (base.nScheme)
4490 case URL_SCHEME_MAILTO:
4491 case URL_SCHEME_SHELL:
4492 case URL_SCHEME_JAVASCRIPT:
4493 case URL_SCHEME_VBSCRIPT:
4494 case URL_SCHEME_ABOUT:
4495 return TRUE;
4497 return FALSE;
4499 case URLIS_FILEURL:
4500 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL);
4502 case URLIS_DIRECTORY:
4503 last = url + strlen(url) - 1;
4504 return (last >= url && (*last == '/' || *last == '\\' ));
4506 case URLIS_URL:
4507 return PathIsURLA(url);
4509 case URLIS_NOHISTORY:
4510 case URLIS_APPLIABLE:
4511 case URLIS_HASQUERY:
4512 default:
4513 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis);
4516 return FALSE;
4519 BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis)
4521 const WCHAR *last;
4522 PARSEDURLW base;
4524 TRACE("%s, %d\n", debugstr_w(url), Urlis);
4526 if (!url)
4527 return FALSE;
4529 switch (Urlis)
4531 case URLIS_OPAQUE:
4532 base.cbSize = sizeof(base);
4533 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */
4534 switch (base.nScheme)
4536 case URL_SCHEME_MAILTO:
4537 case URL_SCHEME_SHELL:
4538 case URL_SCHEME_JAVASCRIPT:
4539 case URL_SCHEME_VBSCRIPT:
4540 case URL_SCHEME_ABOUT:
4541 return TRUE;
4543 return FALSE;
4545 case URLIS_FILEURL:
4546 return !wcsnicmp( url, L"file:", 5 );
4548 case URLIS_DIRECTORY:
4549 last = url + lstrlenW(url) - 1;
4550 return (last >= url && (*last == '/' || *last == '\\'));
4552 case URLIS_URL:
4553 return PathIsURLW(url);
4555 case URLIS_NOHISTORY:
4556 case URLIS_APPLIABLE:
4557 case URLIS_HASQUERY:
4558 default:
4559 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis);
4562 return FALSE;
4565 BOOL WINAPI UrlIsOpaqueA(const char *url)
4567 return UrlIsA(url, URLIS_OPAQUE);
4570 BOOL WINAPI UrlIsOpaqueW(const WCHAR *url)
4572 return UrlIsW(url, URLIS_OPAQUE);
4575 BOOL WINAPI UrlIsNoHistoryA(const char *url)
4577 return UrlIsA(url, URLIS_NOHISTORY);
4580 BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url)
4582 return UrlIsW(url, URLIS_NOHISTORY);
4585 HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved)
4587 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
4588 DWORD lenW = ARRAY_SIZE(bufW), lenA;
4589 UNICODE_STRING pathW;
4590 WCHAR *urlW = bufW;
4591 HRESULT hr;
4593 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path))
4594 return E_INVALIDARG;
4596 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER)
4598 urlW = heap_alloc(lenW * sizeof(WCHAR));
4599 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved);
4602 if (SUCCEEDED(hr))
4604 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
4605 if (*url_len > lenA)
4607 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR));
4608 url[lenA] = 0;
4609 *url_len = lenA;
4611 else
4613 *url_len = lenA + 1;
4614 hr = E_POINTER;
4617 if (urlW != bufW)
4618 heap_free(urlW);
4619 RtlFreeUnicodeString(&pathW);
4620 return hr;
4623 HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved)
4625 HRESULT hr;
4627 TRACE("%s, %p, %p, %#x\n", debugstr_w(path), url, url_len, reserved);
4629 if (reserved || !url || !url_len)
4630 return E_INVALIDARG;
4632 hr = url_create_from_path(path, url, url_len);
4633 if (hr == S_FALSE)
4634 lstrcpyW(url, path);
4636 return hr;
4639 HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags)
4641 WCHAR *baseW, *relativeW, *combinedW;
4642 DWORD len, len2;
4643 HRESULT hr;
4645 TRACE("%s, %s, %d, %#x\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags);
4647 if (!base || !relative || !combined_len)
4648 return E_INVALIDARG;
4650 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4651 relativeW = baseW + INTERNET_MAX_URL_LENGTH;
4652 combinedW = relativeW + INTERNET_MAX_URL_LENGTH;
4654 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH);
4655 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH);
4656 len = *combined_len;
4658 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags);
4659 if (hr != S_OK)
4661 *combined_len = len;
4662 heap_free(baseW);
4663 return hr;
4666 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL);
4667 if (len2 > *combined_len)
4669 *combined_len = len2;
4670 heap_free(baseW);
4671 return E_POINTER;
4673 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL);
4674 *combined_len = len2;
4675 heap_free(baseW);
4676 return S_OK;
4679 HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags)
4681 DWORD i, len, process_case = 0, myflags, sizeloc = 0;
4682 LPWSTR work, preliminary, mbase, mrelative;
4683 PARSEDURLW base, relative;
4684 HRESULT hr;
4686 TRACE("%s, %s, %d, %#x\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags);
4688 if (!baseW || !relativeW || !combined_len)
4689 return E_INVALIDARG;
4691 base.cbSize = sizeof(base);
4692 relative.cbSize = sizeof(relative);
4694 /* Get space for duplicates of the input and the output */
4695 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4696 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
4697 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
4698 *preliminary = '\0';
4700 /* Canonicalize the base input prior to looking for the scheme */
4701 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
4702 len = INTERNET_MAX_URL_LENGTH;
4703 UrlCanonicalizeW(baseW, mbase, &len, myflags);
4705 /* Canonicalize the relative input prior to looking for the scheme */
4706 len = INTERNET_MAX_URL_LENGTH;
4707 UrlCanonicalizeW(relativeW, mrelative, &len, myflags);
4709 /* See if the base has a scheme */
4710 if (ParseURLW(mbase, &base) != S_OK)
4712 /* If base has no scheme return relative. */
4713 TRACE("no scheme detected in Base\n");
4714 process_case = 1;
4716 else do
4718 BOOL manual_search = FALSE;
4720 work = (LPWSTR)base.pszProtocol;
4721 for (i = 0; i < base.cchProtocol; ++i)
4722 work[i] = RtlDowncaseUnicodeChar(work[i]);
4724 /* mk is a special case */
4725 if (base.nScheme == URL_SCHEME_MK)
4727 WCHAR *ptr = wcsstr(base.pszSuffix, L"::");
4728 if (ptr)
4730 int delta;
4732 ptr += 2;
4733 delta = ptr-base.pszSuffix;
4734 base.cchProtocol += delta;
4735 base.pszSuffix += delta;
4736 base.cchSuffix -= delta;
4739 else
4741 /* get size of location field (if it exists) */
4742 work = (LPWSTR)base.pszSuffix;
4743 sizeloc = 0;
4744 if (*work++ == '/')
4746 if (*work++ == '/')
4748 /* At this point have start of location and
4749 * it ends at next '/' or end of string.
4751 while (*work && (*work != '/')) work++;
4752 sizeloc = (DWORD)(work - base.pszSuffix);
4757 /* If there is a '?', then the remaining part can only contain a
4758 * query string or fragment, so start looking for the last leaf
4759 * from the '?'. Otherwise, if there is a '#' and the characters
4760 * immediately preceding it are ".htm[l]", then begin looking for
4761 * the last leaf starting from the '#'. Otherwise the '#' is not
4762 * meaningful and just start looking from the end. */
4763 if ((work = wcspbrk(base.pszSuffix + sizeloc, L"#?")))
4765 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
4766 manual_search = TRUE;
4767 else if (work - base.pszSuffix > 4)
4769 if (!wcsnicmp(work - 4, L".htm", 4)) manual_search = TRUE;
4772 if (!manual_search && work - base.pszSuffix > 5)
4774 if (!wcsnicmp(work - 5, L".html", 5)) manual_search = TRUE;
4778 if (manual_search)
4780 /* search backwards starting from the current position */
4781 while (*work != '/' && work > base.pszSuffix + sizeloc)
4782 --work;
4783 base.cchSuffix = work - base.pszSuffix + 1;
4785 else
4787 /* search backwards starting from the end of the string */
4788 work = wcsrchr((base.pszSuffix+sizeloc), '/');
4789 if (work)
4791 len = (DWORD)(work - base.pszSuffix + 1);
4792 base.cchSuffix = len;
4794 else
4795 base.cchSuffix = sizeloc;
4799 * At this point:
4800 * .pszSuffix points to location (starting with '//')
4801 * .cchSuffix length of location (above) and rest less the last
4802 * leaf (if any)
4803 * sizeloc length of location (above) up to but not including
4804 * the last '/'
4807 if (ParseURLW(mrelative, &relative) != S_OK)
4809 /* No scheme in relative */
4810 TRACE("no scheme detected in Relative\n");
4811 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
4812 relative.cchSuffix = lstrlenW(mrelative);
4813 if (*relativeW == ':')
4815 /* Case that is either left alone or uses base. */
4816 if (flags & URL_PLUGGABLE_PROTOCOL)
4818 process_case = 5;
4819 break;
4821 process_case = 1;
4822 break;
4824 if (is_drive_spec( mrelative ))
4826 /* case that becomes "file:///" */
4827 lstrcpyW(preliminary, L"file:///");
4828 process_case = 1;
4829 break;
4831 if (*mrelative == '/' && *(mrelative+1) == '/')
4833 /* Relative has location and the rest. */
4834 process_case = 3;
4835 break;
4837 if (*mrelative == '/')
4839 /* Relative is root to location. */
4840 process_case = 4;
4841 break;
4843 if (*mrelative == '#')
4845 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#')))
4846 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix);
4848 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
4849 preliminary[work-base.pszProtocol] = '\0';
4850 process_case = 1;
4851 break;
4853 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
4854 break;
4856 else
4858 work = (LPWSTR)relative.pszProtocol;
4859 for (i = 0; i < relative.cchProtocol; ++i)
4860 work[i] = RtlDowncaseUnicodeChar(work[i]);
4863 /* Handle cases where relative has scheme. */
4864 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol))
4866 /* since the schemes are the same */
4867 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4869 /* Relative replaces location and what follows. */
4870 process_case = 3;
4871 break;
4873 if (*relative.pszSuffix == '/')
4875 /* Relative is root to location */
4876 process_case = 4;
4877 break;
4879 /* replace either just location if base's location starts with a
4880 * slash or otherwise everything */
4881 process_case = (*base.pszSuffix == '/') ? 5 : 1;
4882 break;
4885 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4887 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4888 process_case = 2;
4889 break;
4891 process_case = 1;
4892 break;
4893 } while (FALSE); /* a little trick to allow easy exit from nested if's */
4895 hr = S_OK;
4896 switch (process_case)
4898 case 1:
4899 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4900 lstrcatW(preliminary, mrelative);
4901 break;
4903 case 2:
4904 /* Relative replaces scheme and location */
4905 lstrcpyW(preliminary, mrelative);
4906 break;
4908 case 3:
4909 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4910 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
4911 work = preliminary + base.cchProtocol + 1;
4912 lstrcpyW(work, relative.pszSuffix);
4913 break;
4915 case 4:
4916 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4917 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
4918 work = preliminary + base.cchProtocol + 1 + sizeloc;
4919 if (flags & URL_PLUGGABLE_PROTOCOL)
4920 *(work++) = '/';
4921 lstrcpyW(work, relative.pszSuffix);
4922 break;
4924 case 5:
4925 /* Return the base without its document (if any) and append relative after its scheme. */
4926 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR));
4927 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1;
4928 if (*work++ != '/')
4929 *(work++) = '/';
4930 lstrcpyW(work, relative.pszSuffix);
4931 break;
4933 default:
4934 FIXME("Unexpected case %d.\n", process_case);
4935 hr = E_INVALIDARG;
4938 if (hr == S_OK)
4940 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
4941 if (*combined_len == 0)
4942 *combined_len = 1;
4943 hr = UrlCanonicalizeW(preliminary, mrelative, combined_len, flags & ~URL_FILE_USE_PATHURL);
4944 if (SUCCEEDED(hr) && combined)
4945 lstrcpyW(combined, mrelative);
4947 TRACE("return-%d len=%d, %s\n", process_case, *combined_len, debugstr_w(combined));
4950 heap_free(preliminary);
4951 return hr;
4954 HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len)
4956 INT src_count = src_len - 1, dest_count = dest_len - 1;
4958 if (!src || !dest)
4959 return E_INVALIDARG;
4961 while (dest_count >= 0)
4963 dest[dest_count] = (dest_count & 0xff);
4964 dest_count--;
4967 while (src_count >= 0)
4969 dest_count = dest_len - 1;
4970 while (dest_count >= 0)
4972 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]];
4973 dest_count--;
4975 src_count--;
4978 return S_OK;
4981 HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len)
4983 __TRY
4985 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len);
4987 __EXCEPT_PAGE_FAULT
4989 return E_INVALIDARG;
4991 __ENDTRY
4992 return S_OK;
4995 HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len)
4997 char urlA[MAX_PATH];
4999 TRACE("%s, %p, %d\n", debugstr_w(url), dest, dest_len);
5001 __TRY
5003 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL);
5004 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len);
5006 __EXCEPT_PAGE_FAULT
5008 return E_INVALIDARG;
5010 __ENDTRY
5011 return S_OK;
5014 BOOL WINAPI IsInternetESCEnabled(void)
5016 FIXME(": stub\n");
5017 return FALSE;