mfmediaengine: Handle B8G8R8X8 format for d3d11 texture output.
[wine.git] / dlls / kernelbase / path.c
blob9387705e963af0937ef6342ac4b51af3a5d07dbf
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, *first;
1907 TRACE("%s\n", wine_dbgstr_a(path));
1909 if (!path || !*path)
1910 return;
1912 start = first = path;
1914 while (*path == ' ')
1915 path = CharNextA(path);
1917 while (*path)
1918 *start++ = *path++;
1920 if (start != first)
1921 while (start[-1] == ' ')
1922 start--;
1924 *start = '\0';
1927 void WINAPI PathRemoveBlanksW(WCHAR *path)
1929 WCHAR *start, *first;
1931 TRACE("%s\n", wine_dbgstr_w(path));
1933 if (!path || !*path)
1934 return;
1936 start = first = path;
1938 while (*path == ' ')
1939 path++;
1941 while (*path)
1942 *start++ = *path++;
1944 if (start != first)
1945 while (start[-1] == ' ')
1946 start--;
1948 *start = '\0';
1951 void WINAPI PathRemoveExtensionA(char *path)
1953 TRACE("%s\n", wine_dbgstr_a(path));
1955 if (!path)
1956 return;
1958 path = PathFindExtensionA(path);
1959 if (path && *path)
1960 *path = '\0';
1963 void WINAPI PathRemoveExtensionW(WCHAR *path)
1965 TRACE("%s\n", wine_dbgstr_w(path));
1967 if (!path)
1968 return;
1970 path = PathFindExtensionW(path);
1971 if (path && *path)
1972 *path = '\0';
1975 BOOL WINAPI PathRenameExtensionA(char *path, const char *ext)
1977 char *extension;
1979 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext));
1981 extension = PathFindExtensionA(path);
1983 if (!extension || (extension - path + strlen(ext) >= MAX_PATH))
1984 return FALSE;
1986 strcpy(extension, ext);
1987 return TRUE;
1990 BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext)
1992 WCHAR *extension;
1994 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext));
1996 extension = PathFindExtensionW(path);
1998 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH))
1999 return FALSE;
2001 lstrcpyW(extension, ext);
2002 return TRUE;
2005 void WINAPI PathUnquoteSpacesA(char *path)
2007 unsigned int len;
2009 TRACE("%s\n", wine_dbgstr_a(path));
2011 if (!path || *path != '"')
2012 return;
2014 len = strlen(path) - 1;
2015 if (path[len] == '"')
2017 path[len] = '\0';
2018 for (; *path; path++)
2019 *path = path[1];
2023 void WINAPI PathUnquoteSpacesW(WCHAR *path)
2025 unsigned int len;
2027 TRACE("%s\n", wine_dbgstr_w(path));
2029 if (!path || *path != '"')
2030 return;
2032 len = lstrlenW(path) - 1;
2033 if (path[len] == '"')
2035 path[len] = '\0';
2036 for (; *path; path++)
2037 *path = path[1];
2041 char * WINAPI PathRemoveBackslashA(char *path)
2043 char *ptr;
2045 TRACE("%s\n", wine_dbgstr_a(path));
2047 if (!path)
2048 return NULL;
2050 ptr = CharPrevA(path, path + strlen(path));
2051 if (!PathIsRootA(path) && *ptr == '\\')
2052 *ptr = '\0';
2054 return ptr;
2057 WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path)
2059 WCHAR *ptr;
2061 TRACE("%s\n", wine_dbgstr_w(path));
2063 if (!path)
2064 return NULL;
2066 ptr = path + lstrlenW(path);
2067 if (ptr > path) ptr--;
2068 if (!PathIsRootW(path) && *ptr == '\\')
2069 *ptr = '\0';
2071 return ptr;
2074 BOOL WINAPI PathIsLFNFileSpecA(const char *path)
2076 unsigned int name_len = 0, ext_len = 0;
2078 TRACE("%s\n", wine_dbgstr_a(path));
2080 if (!path)
2081 return FALSE;
2083 while (*path)
2085 if (*path == ' ')
2086 return TRUE; /* DOS names cannot have spaces */
2087 if (*path == '.')
2089 if (ext_len)
2090 return TRUE; /* DOS names have only one dot */
2091 ext_len = 1;
2093 else if (ext_len)
2095 ext_len++;
2096 if (ext_len > 4)
2097 return TRUE; /* DOS extensions are <= 3 chars*/
2099 else
2101 name_len++;
2102 if (name_len > 8)
2103 return TRUE; /* DOS names are <= 8 chars */
2105 path = CharNextA(path);
2108 return FALSE; /* Valid DOS path */
2111 BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path)
2113 unsigned int name_len = 0, ext_len = 0;
2115 TRACE("%s\n", wine_dbgstr_w(path));
2117 if (!path)
2118 return FALSE;
2120 while (*path)
2122 if (*path == ' ')
2123 return TRUE; /* DOS names cannot have spaces */
2124 if (*path == '.')
2126 if (ext_len)
2127 return TRUE; /* DOS names have only one dot */
2128 ext_len = 1;
2130 else if (ext_len)
2132 ext_len++;
2133 if (ext_len > 4)
2134 return TRUE; /* DOS extensions are <= 3 chars*/
2136 else
2138 name_len++;
2139 if (name_len > 8)
2140 return TRUE; /* DOS names are <= 8 chars */
2142 path++;
2145 return FALSE; /* Valid DOS path */
2148 #define PATH_CHAR_CLASS_LETTER 0x00000001
2149 #define PATH_CHAR_CLASS_ASTERIX 0x00000002
2150 #define PATH_CHAR_CLASS_DOT 0x00000004
2151 #define PATH_CHAR_CLASS_BACKSLASH 0x00000008
2152 #define PATH_CHAR_CLASS_COLON 0x00000010
2153 #define PATH_CHAR_CLASS_SEMICOLON 0x00000020
2154 #define PATH_CHAR_CLASS_COMMA 0x00000040
2155 #define PATH_CHAR_CLASS_SPACE 0x00000080
2156 #define PATH_CHAR_CLASS_OTHER_VALID 0x00000100
2157 #define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200
2159 #define PATH_CHAR_CLASS_INVALID 0x00000000
2160 #define PATH_CHAR_CLASS_ANY 0xffffffff
2162 static const DWORD path_charclass[] =
2164 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID,
2165 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID,
2166 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID,
2167 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID,
2168 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID,
2169 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID,
2170 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID,
2171 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID,
2172 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID,
2173 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID,
2174 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID,
2175 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID,
2176 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID,
2177 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID,
2178 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID,
2179 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID,
2180 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID,
2181 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID,
2182 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID,
2183 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID,
2184 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID,
2185 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID,
2186 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID,
2187 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID,
2188 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID,
2189 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID,
2190 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID,
2191 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID,
2192 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID,
2193 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON,
2194 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID,
2195 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER,
2196 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY,
2197 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY,
2198 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY,
2199 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY,
2200 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY,
2201 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY,
2202 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY,
2203 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY,
2204 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY,
2205 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY,
2206 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY,
2207 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY,
2208 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY,
2209 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID,
2210 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID,
2211 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID,
2212 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY,
2213 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY,
2214 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY,
2215 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY,
2216 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY,
2217 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY,
2218 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY,
2219 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY,
2220 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY,
2221 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY,
2222 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY,
2223 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY,
2224 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY,
2225 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID,
2226 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID,
2227 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID
2230 BOOL WINAPI PathIsValidCharA(char c, DWORD class)
2232 if ((unsigned)c > 0x7e)
2233 return class & PATH_CHAR_CLASS_OTHER_VALID;
2235 return class & path_charclass[(unsigned)c];
2238 BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class)
2240 if (c > 0x7e)
2241 return class & PATH_CHAR_CLASS_OTHER_VALID;
2243 return class & path_charclass[c];
2246 char * WINAPI PathFindNextComponentA(const char *path)
2248 char *slash;
2250 TRACE("%s\n", wine_dbgstr_a(path));
2252 if (!path || !*path)
2253 return NULL;
2255 if ((slash = StrChrA(path, '\\')))
2257 if (slash[1] == '\\')
2258 slash++;
2259 return slash + 1;
2262 return (char *)path + strlen(path);
2265 WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path)
2267 WCHAR *slash;
2269 TRACE("%s\n", wine_dbgstr_w(path));
2271 if (!path || !*path)
2272 return NULL;
2274 if ((slash = StrChrW(path, '\\')))
2276 if (slash[1] == '\\')
2277 slash++;
2278 return slash + 1;
2281 return (WCHAR *)path + lstrlenW(path);
2284 char * WINAPI PathSkipRootA(const char *path)
2286 TRACE("%s\n", wine_dbgstr_a(path));
2288 if (!path || !*path)
2289 return NULL;
2291 if (*path == '\\' && path[1] == '\\')
2293 /* Network share: skip share server and mount point */
2294 path += 2;
2295 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\')))
2296 path++;
2297 return (char *)path;
2300 if (IsDBCSLeadByte(*path))
2301 return NULL;
2303 /* Check x:\ */
2304 if (path[0] && path[1] == ':' && path[2] == '\\')
2305 return (char *)path + 3;
2307 return NULL;
2310 WCHAR * WINAPI PathSkipRootW(const WCHAR *path)
2312 TRACE("%s\n", wine_dbgstr_w(path));
2314 if (!path || !*path)
2315 return NULL;
2317 if (*path == '\\' && path[1] == '\\')
2319 /* Network share: skip share server and mount point */
2320 path += 2;
2321 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\')))
2322 path++;
2323 return (WCHAR *)path;
2326 /* Check x:\ */
2327 if (path[0] && path[1] == ':' && path[2] == '\\')
2328 return (WCHAR *)path + 3;
2330 return NULL;
2333 void WINAPI PathStripPathA(char *path)
2335 TRACE("%s\n", wine_dbgstr_a(path));
2337 if (path)
2339 char *filename = PathFindFileNameA(path);
2340 if (filename != path)
2341 RtlMoveMemory(path, filename, strlen(filename) + 1);
2345 void WINAPI PathStripPathW(WCHAR *path)
2347 WCHAR *filename;
2349 TRACE("%s\n", wine_dbgstr_w(path));
2350 filename = PathFindFileNameW(path);
2351 if (filename != path)
2352 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR));
2355 BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length)
2357 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length);
2359 if (SearchPathA(NULL, path, NULL, length, buffer, NULL))
2360 return TRUE;
2362 return !!GetFullPathNameA(path, length, buffer, NULL);
2365 BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length)
2367 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length);
2369 if (SearchPathW(NULL, path, NULL, length, buffer, NULL))
2370 return TRUE;
2371 return !!GetFullPathNameW(path, length, buffer, NULL);
2374 BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to,
2375 DWORD attributes_to)
2377 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH];
2378 BOOL ret;
2380 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to);
2382 if (!path || !from || !to)
2383 return FALSE;
2385 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW));
2386 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW));
2387 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to);
2388 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0);
2390 return ret;
2393 BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to,
2394 DWORD attributes_to)
2396 WCHAR fromW[MAX_PATH], toW[MAX_PATH];
2397 DWORD len;
2399 TRACE("%p, %s, %#x, %s, %#x\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to);
2401 if (!path || !from || !to)
2402 return FALSE;
2404 *path = '\0';
2405 lstrcpynW(fromW, from, ARRAY_SIZE(fromW));
2406 lstrcpynW(toW, to, ARRAY_SIZE(toW));
2408 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY))
2409 PathRemoveFileSpecW(fromW);
2410 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY))
2411 PathRemoveFileSpecW(toW);
2413 /* Paths can only be relative if they have a common root */
2414 if (!(len = PathCommonPrefixW(fromW, toW, 0)))
2415 return FALSE;
2417 /* Strip off 'from' components to the root, by adding "..\" */
2418 from = fromW + len;
2419 if (!*from)
2421 path[0] = '.';
2422 path[1] = '\0';
2424 if (*from == '\\')
2425 from++;
2427 while (*from)
2429 from = PathFindNextComponentW(from);
2430 lstrcatW(path, *from ? L"..\\" : L"..");
2433 /* From the root add the components of 'to' */
2434 to += len;
2435 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */
2436 if (*to && to[-1])
2438 if (*to != '\\')
2439 to--;
2440 len = lstrlenW(path);
2441 if (len + lstrlenW(to) >= MAX_PATH)
2443 *path = '\0';
2444 return FALSE;
2446 lstrcpyW(path + len, to);
2449 return TRUE;
2452 BOOL WINAPI PathMatchSpecA(const char *path, const char *mask)
2454 WCHAR *pathW, *maskW;
2455 BOOL ret;
2457 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask));
2459 if (!lstrcmpA(mask, "*.*"))
2460 return TRUE; /* Matches every path */
2462 pathW = heap_strdupAtoW( path );
2463 maskW = heap_strdupAtoW( mask );
2464 ret = PathMatchSpecW( pathW, maskW );
2465 heap_free( pathW );
2466 heap_free( maskW );
2467 return ret;
2470 static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask)
2472 while (*name && *mask && *mask != ';')
2474 if (*mask == '*')
2478 if (path_match_maskW(name, mask + 1))
2479 return TRUE; /* try substrings */
2480 } while (*name++);
2481 return FALSE;
2484 if (towupper(*mask) != towupper(*name) && *mask != '?')
2485 return FALSE;
2487 name++;
2488 mask++;
2491 if (!*name)
2493 while (*mask == '*')
2494 mask++;
2495 if (!*mask || *mask == ';')
2496 return TRUE;
2499 return FALSE;
2502 BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask)
2504 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask));
2506 if (!lstrcmpW(mask, L"*.*"))
2507 return TRUE; /* Matches every path */
2509 while (*mask)
2511 while (*mask == ' ')
2512 mask++; /* Eat leading spaces */
2514 if (path_match_maskW(path, mask))
2515 return TRUE; /* Matches the current path */
2517 while (*mask && *mask != ';')
2518 mask++; /* masks separated by ';' */
2520 if (*mask == ';')
2521 mask++;
2524 return FALSE;
2527 void WINAPI PathQuoteSpacesA(char *path)
2529 TRACE("%s\n", wine_dbgstr_a(path));
2531 if (path && StrChrA(path, ' '))
2533 size_t len = strlen(path) + 1;
2535 if (len + 2 < MAX_PATH)
2537 memmove(path + 1, path, len);
2538 path[0] = '"';
2539 path[len] = '"';
2540 path[len + 1] = '\0';
2545 void WINAPI PathQuoteSpacesW(WCHAR *path)
2547 TRACE("%s\n", wine_dbgstr_w(path));
2549 if (path && StrChrW(path, ' '))
2551 int len = lstrlenW(path) + 1;
2553 if (len + 2 < MAX_PATH)
2555 memmove(path + 1, path, len * sizeof(WCHAR));
2556 path[0] = '"';
2557 path[len] = '"';
2558 path[len + 1] = '\0';
2563 BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2)
2565 const char *start;
2566 int len;
2568 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2));
2570 if (!path1 || !path2 || !(start = PathSkipRootA(path1)))
2571 return FALSE;
2573 len = PathCommonPrefixA(path1, path2, NULL) + 1;
2574 return start - path1 <= len;
2577 BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2)
2579 const WCHAR *start;
2580 int len;
2582 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2));
2584 if (!path1 || !path2 || !(start = PathSkipRootW(path1)))
2585 return FALSE;
2587 len = PathCommonPrefixW(path1, path2, NULL) + 1;
2588 return start - path1 <= len;
2591 BOOL WINAPI PathFileExistsA(const char *path)
2593 UINT prev_mode;
2594 DWORD attrs;
2596 TRACE("%s\n", wine_dbgstr_a(path));
2598 if (!path)
2599 return FALSE;
2601 /* Prevent a dialog box if path is on a disk that has been ejected. */
2602 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2603 attrs = GetFileAttributesA(path);
2604 SetErrorMode(prev_mode);
2605 return attrs != INVALID_FILE_ATTRIBUTES;
2608 BOOL WINAPI PathFileExistsW(const WCHAR *path)
2610 UINT prev_mode;
2611 DWORD attrs;
2613 TRACE("%s\n", wine_dbgstr_w(path));
2615 if (!path)
2616 return FALSE;
2618 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
2619 attrs = GetFileAttributesW(path);
2620 SetErrorMode(prev_mode);
2621 return attrs != INVALID_FILE_ATTRIBUTES;
2624 int WINAPI PathParseIconLocationA(char *path)
2626 int ret = 0;
2627 char *comma;
2629 TRACE("%s\n", debugstr_a(path));
2631 if (!path)
2632 return 0;
2634 if ((comma = strchr(path, ',')))
2636 *comma++ = '\0';
2637 ret = StrToIntA(comma);
2639 PathUnquoteSpacesA(path);
2640 PathRemoveBlanksA(path);
2642 return ret;
2645 int WINAPI PathParseIconLocationW(WCHAR *path)
2647 WCHAR *comma;
2648 int ret = 0;
2650 TRACE("%s\n", debugstr_w(path));
2652 if (!path)
2653 return 0;
2655 if ((comma = StrChrW(path, ',')))
2657 *comma++ = '\0';
2658 ret = StrToIntW(comma);
2660 PathUnquoteSpacesW(path);
2661 PathRemoveBlanksW(path);
2663 return ret;
2666 BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len)
2668 WCHAR bufferW[MAX_PATH], *pathW;
2669 DWORD len;
2670 BOOL ret;
2672 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len);
2674 pathW = heap_strdupAtoW(path);
2675 if (!pathW) return FALSE;
2677 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH);
2678 HeapFree(GetProcessHeap(), 0, pathW);
2679 if (!ret) return FALSE;
2681 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
2682 if (buf_len < len + 1) return FALSE;
2684 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL);
2685 return TRUE;
2688 struct envvars_map
2690 const WCHAR *var;
2691 WCHAR path[MAX_PATH];
2692 DWORD len;
2695 static void init_envvars_map(struct envvars_map *map)
2697 while (map->var)
2699 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path));
2700 /* exclude null from length */
2701 if (map->len) map->len--;
2702 map++;
2706 BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len)
2708 static struct envvars_map null_var = {L"", {0}, 0};
2709 struct envvars_map *match = &null_var, *cur;
2710 struct envvars_map envvars[] =
2712 { L"%ALLUSERSPROFILE%" },
2713 { L"%APPDATA%" },
2714 { L"%ProgramFiles%" },
2715 { L"%SystemRoot%" },
2716 { L"%SystemDrive%" },
2717 { L"%USERPROFILE%" },
2718 { NULL }
2720 DWORD pathlen;
2721 UINT needed;
2723 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len);
2725 pathlen = lstrlenW(path);
2726 init_envvars_map(envvars);
2727 cur = envvars;
2728 while (cur->var)
2730 /* path can't contain expanded value or value wasn't retrieved */
2731 if (cur->len == 0 || cur->len > pathlen ||
2732 CompareStringOrdinal( cur->path, cur->len, path, cur->len, TRUE ) != CSTR_EQUAL)
2734 cur++;
2735 continue;
2738 if (cur->len > match->len)
2739 match = cur;
2740 cur++;
2743 needed = lstrlenW(match->var) + 1 + pathlen - match->len;
2744 if (match->len == 0 || needed > buf_len) return FALSE;
2746 lstrcpyW(buffer, match->var);
2747 lstrcatW(buffer, &path[match->len]);
2748 TRACE("ret %s\n", debugstr_w(buffer));
2750 return TRUE;
2753 static const struct
2755 URL_SCHEME scheme_number;
2756 const WCHAR *scheme_name;
2758 url_schemes[] =
2760 { URL_SCHEME_FTP, L"ftp"},
2761 { URL_SCHEME_HTTP, L"http"},
2762 { URL_SCHEME_GOPHER, L"gopher"},
2763 { URL_SCHEME_MAILTO, L"mailto"},
2764 { URL_SCHEME_NEWS, L"news"},
2765 { URL_SCHEME_NNTP, L"nntp"},
2766 { URL_SCHEME_TELNET, L"telnet"},
2767 { URL_SCHEME_WAIS, L"wais"},
2768 { URL_SCHEME_FILE, L"file"},
2769 { URL_SCHEME_MK, L"mk"},
2770 { URL_SCHEME_HTTPS, L"https"},
2771 { URL_SCHEME_SHELL, L"shell"},
2772 { URL_SCHEME_SNEWS, L"snews"},
2773 { URL_SCHEME_LOCAL, L"local"},
2774 { URL_SCHEME_JAVASCRIPT, L"javascript"},
2775 { URL_SCHEME_VBSCRIPT, L"vbscript"},
2776 { URL_SCHEME_ABOUT, L"about"},
2777 { URL_SCHEME_RES, L"res"},
2780 static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len)
2782 unsigned int i;
2784 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i)
2786 if (scheme_len == lstrlenW(url_schemes[i].scheme_name)
2787 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len))
2788 return url_schemes[i].scheme_number;
2791 return URL_SCHEME_UNKNOWN;
2794 HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result)
2796 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
2797 const char *ptr = url;
2798 int len;
2800 TRACE("%s, %p\n", wine_dbgstr_a(url), result);
2802 if (result->cbSize != sizeof(*result))
2803 return E_INVALIDARG;
2805 while (*ptr && ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') ||
2806 (*ptr >= '0' && *ptr <= '9') || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2807 ptr++;
2809 if (*ptr != ':' || ptr <= url + 1)
2811 result->pszProtocol = NULL;
2812 return URL_E_INVALID_SYNTAX;
2815 result->pszProtocol = url;
2816 result->cchProtocol = ptr - url;
2817 result->pszSuffix = ptr + 1;
2818 result->cchSuffix = strlen(result->pszSuffix);
2820 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme));
2821 result->nScheme = get_scheme_code(scheme, len);
2823 return S_OK;
2826 HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result)
2828 const WCHAR *ptr = url;
2830 TRACE("%s, %p\n", wine_dbgstr_w(url), result);
2832 if (result->cbSize != sizeof(*result))
2833 return E_INVALIDARG;
2835 while (*ptr && (isalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.'))
2836 ptr++;
2838 if (*ptr != ':' || ptr <= url + 1)
2840 result->pszProtocol = NULL;
2841 return URL_E_INVALID_SYNTAX;
2844 result->pszProtocol = url;
2845 result->cchProtocol = ptr - url;
2846 result->pszSuffix = ptr + 1;
2847 result->cchSuffix = lstrlenW(result->pszSuffix);
2848 result->nScheme = get_scheme_code(url, ptr - url);
2850 return S_OK;
2853 HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags)
2855 BOOL stop_unescaping = FALSE;
2856 const char *src;
2857 char *dst, next;
2858 DWORD needed;
2859 HRESULT hr;
2861 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags);
2863 if (!url)
2864 return E_INVALIDARG;
2866 if (flags & URL_UNESCAPE_INPLACE)
2867 dst = url;
2868 else
2870 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2871 dst = unescaped;
2874 for (src = url, needed = 0; *src; src++, needed++)
2876 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2878 stop_unescaping = TRUE;
2879 next = *src;
2881 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2883 INT ih;
2884 char buf[3];
2885 memcpy(buf, src + 1, 2);
2886 buf[2] = '\0';
2887 ih = strtol(buf, NULL, 16);
2888 next = (CHAR) ih;
2889 src += 2; /* Advance to end of escape */
2891 else
2892 next = *src;
2894 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2895 *dst++ = next;
2898 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2900 *dst = '\0';
2901 hr = S_OK;
2903 else
2905 needed++; /* add one for the '\0' */
2906 hr = E_POINTER;
2909 if (!(flags & URL_UNESCAPE_INPLACE))
2910 *unescaped_len = needed;
2912 if (hr == S_OK)
2913 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped));
2915 return hr;
2918 HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags)
2920 BOOL stop_unescaping = FALSE;
2921 const WCHAR *src;
2922 WCHAR *dst, next;
2923 DWORD needed;
2924 HRESULT hr;
2926 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags);
2928 if (!url)
2929 return E_INVALIDARG;
2931 if (flags & URL_UNESCAPE_INPLACE)
2932 dst = url;
2933 else
2935 if (!unescaped || !unescaped_len) return E_INVALIDARG;
2936 dst = unescaped;
2939 for (src = url, needed = 0; *src; src++, needed++)
2941 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?'))
2943 stop_unescaping = TRUE;
2944 next = *src;
2946 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping)
2948 INT ih;
2949 WCHAR buf[5] = L"0x";
2950 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
2951 buf[4] = 0;
2952 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
2953 next = (WCHAR) ih;
2954 src += 2; /* Advance to end of escape */
2956 else
2957 next = *src;
2959 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2960 *dst++ = next;
2963 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len)
2965 *dst = '\0';
2966 hr = S_OK;
2968 else
2970 needed++; /* add one for the '\0' */
2971 hr = E_POINTER;
2974 if (!(flags & URL_UNESCAPE_INPLACE))
2975 *unescaped_len = needed;
2977 if (hr == S_OK)
2978 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped));
2980 return hr;
2983 HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved)
2985 WCHAR bufW[MAX_PATH];
2986 WCHAR *pathW = bufW;
2987 UNICODE_STRING urlW;
2988 HRESULT ret;
2989 DWORD lenW = ARRAY_SIZE(bufW), lenA;
2991 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
2992 return E_INVALIDARG;
2994 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
2995 return E_INVALIDARG;
2996 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) {
2997 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2998 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved);
3000 if(ret == S_OK) {
3001 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR));
3002 if(*pcchPath > lenA) {
3003 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR));
3004 pszPath[lenA] = 0;
3005 *pcchPath = lenA;
3006 } else {
3007 *pcchPath = lenA + 1;
3008 ret = E_POINTER;
3011 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW);
3012 RtlFreeUnicodeString(&urlW);
3013 return ret;
3016 HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved)
3018 DWORD nslashes, unescape, len;
3019 const WCHAR *src;
3020 WCHAR *tpath, *dst;
3021 HRESULT hr = S_OK;
3023 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(url), path, pcchPath, dwReserved);
3025 if (!url || !path || !pcchPath || !*pcchPath)
3026 return E_INVALIDARG;
3028 if (wcsnicmp( url, L"file:", 5))
3029 return E_INVALIDARG;
3031 url += 5;
3033 src = url;
3034 nslashes = 0;
3035 while (*src == '/' || *src == '\\')
3037 nslashes++;
3038 src++;
3041 /* We need a temporary buffer so we can compute what size to ask for.
3042 * We know that the final string won't be longer than the current pszUrl
3043 * plus at most two backslashes. All the other transformations make it
3044 * shorter.
3046 len = 2 + lstrlenW(url) + 1;
3047 if (*pcchPath < len)
3048 tpath = heap_alloc(len * sizeof(WCHAR));
3049 else
3050 tpath = path;
3052 len = 0;
3053 dst = tpath;
3054 unescape = 1;
3055 switch (nslashes)
3057 case 0:
3058 /* 'file:' + escaped DOS path */
3059 break;
3060 case 1:
3061 /* 'file:/' + escaped DOS path */
3062 /* fall through */
3063 case 3:
3064 /* 'file:///' (implied localhost) + escaped DOS path */
3065 if (!is_escaped_drive_spec( src ))
3066 src -= 1;
3067 break;
3068 case 2:
3069 if (lstrlenW(src) >= 10 && !wcsnicmp( src, L"localhost", 9) && (src[9] == '/' || src[9] == '\\'))
3071 /* 'file://localhost/' + escaped DOS path */
3072 src += 10;
3074 else if (is_escaped_drive_spec( src ))
3076 /* 'file://' + unescaped DOS path */
3077 unescape = 0;
3079 else
3081 /* 'file://hostname:port/path' (where path is escaped)
3082 * or 'file:' + escaped UNC path (\\server\share\path)
3083 * The second form is clearly specific to Windows and it might
3084 * even be doing a network lookup to try to figure it out.
3086 while (*src && *src != '/' && *src != '\\')
3087 src++;
3088 len = src - url;
3089 StrCpyNW(dst, url, len + 1);
3090 dst += len;
3091 if (*src && is_escaped_drive_spec( src + 1 ))
3093 /* 'Forget' to add a trailing '/', just like Windows */
3094 src++;
3097 break;
3098 case 4:
3099 /* 'file://' + unescaped UNC path (\\server\share\path) */
3100 unescape = 0;
3101 if (is_escaped_drive_spec( src ))
3102 break;
3103 /* fall through */
3104 default:
3105 /* 'file:/...' + escaped UNC path (\\server\share\path) */
3106 src -= 2;
3109 /* Copy the remainder of the path */
3110 len += lstrlenW(src);
3111 lstrcpyW(dst, src);
3113 /* First do the Windows-specific path conversions */
3114 for (dst = tpath; *dst; dst++)
3115 if (*dst == '/') *dst = '\\';
3116 if (is_escaped_drive_spec( tpath ))
3117 tpath[1] = ':'; /* c| -> c: */
3119 /* And only then unescape the path (i.e. escaped slashes are left as is) */
3120 if (unescape)
3122 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
3123 if (hr == S_OK)
3125 /* When working in-place UrlUnescapeW() does not set len */
3126 len = lstrlenW(tpath);
3130 if (*pcchPath < len + 1)
3132 hr = E_POINTER;
3133 *pcchPath = len + 1;
3135 else
3137 *pcchPath = len;
3138 if (tpath != path)
3139 lstrcpyW(path, tpath);
3141 if (tpath != path)
3142 heap_free(tpath);
3144 TRACE("Returning (%u) %s\n", *pcchPath, wine_dbgstr_w(path));
3145 return hr;
3148 HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved)
3150 WCHAR pathW[MAX_PATH];
3151 DWORD size;
3152 HRESULT hr;
3154 size = MAX_PATH;
3155 hr = PathCreateFromUrlW(url, pathW, &size, reserved);
3156 if (SUCCEEDED(hr))
3158 /* Yes, this is supposed to crash if 'path' is NULL */
3159 *path = StrDupW(pathW);
3162 return hr;
3165 BOOL WINAPI PathIsURLA(const char *path)
3167 PARSEDURLA base;
3168 HRESULT hr;
3170 TRACE("%s\n", wine_dbgstr_a(path));
3172 if (!path || !*path)
3173 return FALSE;
3175 /* get protocol */
3176 base.cbSize = sizeof(base);
3177 hr = ParseURLA(path, &base);
3178 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3181 BOOL WINAPI PathIsURLW(const WCHAR *path)
3183 PARSEDURLW base;
3184 HRESULT hr;
3186 TRACE("%s\n", wine_dbgstr_w(path));
3188 if (!path || !*path)
3189 return FALSE;
3191 /* get protocol */
3192 base.cbSize = sizeof(base);
3193 hr = ParseURLW(path, &base);
3194 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID);
3197 #define WINE_URL_BASH_AS_SLASH 0x01
3198 #define WINE_URL_COLLAPSE_SLASHES 0x02
3199 #define WINE_URL_ESCAPE_SLASH 0x04
3200 #define WINE_URL_ESCAPE_HASH 0x08
3201 #define WINE_URL_ESCAPE_QUESTION 0x10
3202 #define WINE_URL_STOP_ON_HASH 0x20
3203 #define WINE_URL_STOP_ON_QUESTION 0x40
3205 static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags)
3207 if (flags & URL_ESCAPE_SPACES_ONLY)
3208 return ch == ' ';
3210 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%'))
3211 return TRUE;
3213 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80))
3214 return TRUE;
3216 if (ch <= 31 || (ch >= 127 && ch <= 255) )
3217 return TRUE;
3219 if (isalnum(ch))
3220 return FALSE;
3222 switch (ch) {
3223 case ' ':
3224 case '<':
3225 case '>':
3226 case '\"':
3227 case '{':
3228 case '}':
3229 case '|':
3230 case '\\':
3231 case '^':
3232 case ']':
3233 case '[':
3234 case '`':
3235 case '&':
3236 return TRUE;
3237 case '/':
3238 return !!(int_flags & WINE_URL_ESCAPE_SLASH);
3239 case '?':
3240 return !!(int_flags & WINE_URL_ESCAPE_QUESTION);
3241 case '#':
3242 return !!(int_flags & WINE_URL_ESCAPE_HASH);
3243 default:
3244 return FALSE;
3248 HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags)
3250 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
3251 WCHAR *escapedW = bufW;
3252 UNICODE_STRING urlW;
3253 HRESULT hr;
3254 DWORD lenW = ARRAY_SIZE(bufW), lenA;
3256 if (!escaped || !escaped_len || !*escaped_len)
3257 return E_INVALIDARG;
3259 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url))
3260 return E_INVALIDARG;
3262 if (flags & URL_ESCAPE_AS_UTF8)
3264 RtlFreeUnicodeString(&urlW);
3265 return E_NOTIMPL;
3268 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER)
3270 escapedW = heap_alloc(lenW * sizeof(WCHAR));
3271 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags);
3274 if (hr == S_OK)
3276 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
3277 if (*escaped_len > lenA)
3279 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
3280 escaped[lenA] = 0;
3281 *escaped_len = lenA;
3283 else
3285 *escaped_len = lenA + 1;
3286 hr = E_POINTER;
3289 if (escapedW != bufW)
3290 heap_free(escapedW);
3291 RtlFreeUnicodeString(&urlW);
3292 return hr;
3295 HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags)
3297 DWORD needed = 0, slashes = 0, int_flags;
3298 WCHAR next[12], *dst, *dst_ptr;
3299 BOOL stop_escaping = FALSE;
3300 PARSEDURLW parsed_url;
3301 const WCHAR *src;
3302 INT i, len;
3303 HRESULT hr;
3305 TRACE("%p, %s, %p, %p, %#x\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags);
3307 if (!url || !escaped_len || !escaped || *escaped_len == 0)
3308 return E_INVALIDARG;
3310 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO |
3311 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8))
3313 FIXME("Unimplemented flags: %08x\n", flags);
3316 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR));
3317 if (!dst_ptr)
3318 return E_OUTOFMEMORY;
3320 /* fix up flags */
3321 if (flags & URL_ESCAPE_SPACES_ONLY)
3322 /* if SPACES_ONLY specified, reset the other controls */
3323 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY);
3324 else
3325 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
3326 flags |= URL_DONT_ESCAPE_EXTRA_INFO;
3328 int_flags = 0;
3329 if (flags & URL_ESCAPE_SEGMENT_ONLY)
3330 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
3331 else
3333 parsed_url.cbSize = sizeof(parsed_url);
3334 if (ParseURLW(url, &parsed_url) != S_OK)
3335 parsed_url.nScheme = URL_SCHEME_INVALID;
3337 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
3339 if (flags & URL_DONT_ESCAPE_EXTRA_INFO)
3340 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
3342 switch(parsed_url.nScheme) {
3343 case URL_SCHEME_FILE:
3344 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
3345 int_flags &= ~WINE_URL_STOP_ON_HASH;
3346 break;
3348 case URL_SCHEME_HTTP:
3349 case URL_SCHEME_HTTPS:
3350 int_flags |= WINE_URL_BASH_AS_SLASH;
3351 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
3352 int_flags |= WINE_URL_ESCAPE_SLASH;
3353 break;
3355 case URL_SCHEME_MAILTO:
3356 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
3357 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
3358 break;
3360 case URL_SCHEME_INVALID:
3361 break;
3363 case URL_SCHEME_FTP:
3364 default:
3365 if(parsed_url.pszSuffix[0] != '/')
3366 int_flags |= WINE_URL_ESCAPE_SLASH;
3367 break;
3371 for (src = url; *src; )
3373 WCHAR cur = *src;
3374 len = 0;
3376 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1)
3378 while (cur == '/' || cur == '\\')
3380 slashes++;
3381 cur = *++src;
3383 if (slashes == 2 && !wcsnicmp(src, L"localhost", 9)) { /* file://localhost/ -> file:/// */
3384 if(src[9] == '/' || src[9] == '\\') src += 10;
3385 slashes = 3;
3388 switch (slashes)
3390 case 1:
3391 case 3:
3392 next[0] = next[1] = next[2] = '/';
3393 len = 3;
3394 break;
3395 case 0:
3396 len = 0;
3397 break;
3398 default:
3399 next[0] = next[1] = '/';
3400 len = 2;
3401 break;
3404 if (len == 0)
3406 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
3407 stop_escaping = TRUE;
3409 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
3410 stop_escaping = TRUE;
3412 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
3414 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping)
3416 if (flags & URL_ESCAPE_AS_UTF8)
3418 char utf[16];
3420 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff))
3422 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL);
3423 src++;
3425 else
3426 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL);
3428 if (!len)
3430 utf[0] = 0xef;
3431 utf[1] = 0xbf;
3432 utf[2] = 0xbd;
3433 len = 3;
3436 for (i = 0; i < len; ++i)
3438 next[i*3+0] = '%';
3439 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf];
3440 next[i*3+2] = hexDigits[utf[i] & 0xf];
3442 len *= 3;
3444 else
3446 next[0] = '%';
3447 next[1] = hexDigits[(cur >> 4) & 0xf];
3448 next[2] = hexDigits[cur & 0xf];
3449 len = 3;
3452 else
3454 next[0] = cur;
3455 len = 1;
3457 src++;
3460 if (needed + len <= *escaped_len)
3462 memcpy(dst, next, len*sizeof(WCHAR));
3463 dst += len;
3465 needed += len;
3468 if (needed < *escaped_len)
3470 *dst = '\0';
3471 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR));
3472 hr = S_OK;
3474 else
3476 needed++; /* add one for the '\0' */
3477 hr = E_POINTER;
3479 *escaped_len = needed;
3481 heap_free(dst_ptr);
3482 return hr;
3485 HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags)
3487 LPWSTR url, canonical;
3488 HRESULT hr;
3490 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags);
3492 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3493 return E_INVALIDARG;
3495 url = heap_strdupAtoW(src_url);
3496 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR));
3497 if (!url || !canonical)
3499 heap_free(url);
3500 heap_free(canonical);
3501 return E_OUTOFMEMORY;
3504 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags);
3505 if (hr == S_OK)
3506 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL);
3508 heap_free(url);
3509 heap_free(canonical);
3510 return hr;
3513 HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags)
3515 WCHAR *url_copy, *url, *wk2, *mp, *mp2;
3516 DWORD nByteLen, nLen, nWkLen;
3517 const WCHAR *wk1, *root;
3518 DWORD escape_flags;
3519 WCHAR slash = '\0';
3520 HRESULT hr = S_OK;
3521 BOOL is_file_url;
3522 INT state;
3524 TRACE("%s, %p, %p, %#x\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags);
3526 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len)
3527 return E_INVALIDARG;
3529 if (!*src_url)
3531 *canonicalized = 0;
3532 return S_OK;
3535 /* Remove '\t' characters from URL */
3536 nByteLen = (lstrlenW(src_url) + 1) * sizeof(WCHAR); /* length in bytes */
3537 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
3538 if(!url)
3539 return E_OUTOFMEMORY;
3541 wk1 = src_url;
3542 wk2 = url;
3545 while(*wk1 == '\t')
3546 wk1++;
3547 *wk2++ = *wk1;
3548 } while (*wk1++);
3550 /* Allocate memory for simplified URL (before escaping) */
3551 nByteLen = (wk2-url)*sizeof(WCHAR);
3552 url_copy = heap_alloc(nByteLen + sizeof(L"file:///"));
3553 if (!url_copy)
3555 heap_free(url);
3556 return E_OUTOFMEMORY;
3559 is_file_url = !wcsncmp(url, L"file:", 5);
3561 if ((nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"http:", 5)) || is_file_url)
3562 slash = '/';
3564 if ((flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
3565 slash = '\\';
3567 if (nByteLen >= 4*sizeof(WCHAR) && !wcsncmp(url, L"res:", 4))
3569 flags &= ~URL_FILE_USE_PATHURL;
3570 slash = '\0';
3574 * state =
3575 * 0 initial 1,3
3576 * 1 have 2[+] alnum 2,3
3577 * 2 have scheme (found :) 4,6,3
3578 * 3 failed (no location)
3579 * 4 have // 5,3
3580 * 5 have 1[+] alnum 6,3
3581 * 6 have location (found /) save root location
3584 wk1 = url;
3585 wk2 = url_copy;
3586 state = 0;
3588 /* Assume path */
3589 if (url[1] == ':')
3591 lstrcpyW(wk2, L"file:///");
3592 wk2 += lstrlenW(wk2);
3593 if (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
3595 slash = '\\';
3596 --wk2;
3598 else
3599 flags |= URL_ESCAPE_UNSAFE;
3600 state = 5;
3601 is_file_url = TRUE;
3603 else if (url[0] == '/')
3605 state = 5;
3606 is_file_url = TRUE;
3609 while (*wk1)
3611 switch (state)
3613 case 0:
3614 if (!isalnum(*wk1)) {state = 3; break;}
3615 *wk2++ = *wk1++;
3616 if (!isalnum(*wk1)) {state = 3; break;}
3617 *wk2++ = *wk1++;
3618 state = 1;
3619 break;
3620 case 1:
3621 *wk2++ = *wk1;
3622 if (*wk1++ == ':') state = 2;
3623 break;
3624 case 2:
3625 *wk2++ = *wk1++;
3626 if (*wk1 != '/') {state = 6; break;}
3627 *wk2++ = *wk1++;
3628 if ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 9*sizeof(WCHAR) && is_file_url
3629 && !wcsncmp(wk1, L"localhost", 9))
3631 wk1 += 9;
3632 while (*wk1 == '\\' && (flags & URL_FILE_USE_PATHURL))
3633 wk1++;
3636 if (*wk1 == '/' && (flags & URL_FILE_USE_PATHURL))
3637 wk1++;
3638 else if (is_file_url)
3640 const WCHAR *body = wk1;
3642 while (*body == '/')
3643 ++body;
3645 if (is_drive_spec( body ))
3647 if (!(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)))
3649 if (slash)
3650 *wk2++ = slash;
3651 else
3652 *wk2++ = '/';
3655 else
3657 if (flags & URL_WININET_COMPATIBILITY)
3659 if (*wk1 == '/' && *(wk1 + 1) != '/')
3661 *wk2++ = '\\';
3663 else
3665 *wk2++ = '\\';
3666 *wk2++ = '\\';
3669 else
3671 if (*wk1 == '/' && *(wk1+1) != '/')
3673 if (slash)
3674 *wk2++ = slash;
3675 else
3676 *wk2++ = '/';
3680 wk1 = body;
3682 state = 4;
3683 break;
3684 case 3:
3685 nWkLen = lstrlenW(wk1);
3686 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3687 mp = wk2;
3688 wk1 += nWkLen;
3689 wk2 += nWkLen;
3691 if (slash)
3693 while (mp < wk2)
3695 if (*mp == '/' || *mp == '\\')
3696 *mp = slash;
3697 mp++;
3700 break;
3701 case 4:
3702 if (!isalnum(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
3704 state = 3;
3705 break;
3707 while (isalnum(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
3708 *wk2++ = *wk1++;
3709 state = 5;
3710 if (!*wk1)
3712 if (slash)
3713 *wk2++ = slash;
3714 else
3715 *wk2++ = '/';
3717 break;
3718 case 5:
3719 if (*wk1 != '/' && *wk1 != '\\')
3721 state = 3;
3722 break;
3724 while (*wk1 == '/' || *wk1 == '\\')
3726 if (slash)
3727 *wk2++ = slash;
3728 else
3729 *wk2++ = *wk1;
3730 wk1++;
3732 state = 6;
3733 break;
3734 case 6:
3735 if (flags & URL_DONT_SIMPLIFY)
3737 state = 3;
3738 break;
3741 /* Now at root location, cannot back up any more. */
3742 /* "root" will point at the '/' */
3744 root = wk2-1;
3745 while (*wk1)
3747 mp = wcschr(wk1, '/');
3748 mp2 = wcschr(wk1, '\\');
3749 if (mp2 && (!mp || mp2 < mp))
3750 mp = mp2;
3751 if (!mp)
3753 nWkLen = lstrlenW(wk1);
3754 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
3755 wk1 += nWkLen;
3756 wk2 += nWkLen;
3757 continue;
3759 nLen = mp - wk1;
3760 if (nLen)
3762 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
3763 wk2 += nLen;
3764 wk1 += nLen;
3766 if (slash)
3767 *wk2++ = slash;
3768 else
3769 *wk2++ = *wk1;
3770 wk1++;
3772 while (*wk1 == '.')
3774 TRACE("found '/.'\n");
3775 if (wk1[1] == '/' || wk1[1] == '\\')
3777 /* case of /./ -> skip the ./ */
3778 wk1 += 2;
3780 else if (wk1[1] == '.' && (wk1[2] == '/' || wk1[2] == '\\' || wk1[2] == '?'
3781 || wk1[2] == '#' || !wk1[2]))
3783 /* case /../ -> need to backup wk2 */
3784 TRACE("found '/../'\n");
3785 *(wk2-1) = '\0'; /* set end of string */
3786 mp = wcsrchr(root, '/');
3787 mp2 = wcsrchr(root, '\\');
3788 if (mp2 && (!mp || mp2 < mp))
3789 mp = mp2;
3790 if (mp && (mp >= root))
3792 /* found valid backup point */
3793 wk2 = mp + 1;
3794 if(wk1[2] != '/' && wk1[2] != '\\')
3795 wk1 += 2;
3796 else
3797 wk1 += 3;
3799 else
3801 /* did not find point, restore '/' */
3802 *(wk2-1) = slash;
3803 break;
3806 else
3807 break;
3810 *wk2 = '\0';
3811 break;
3812 default:
3813 FIXME("how did we get here - state=%d\n", state);
3814 heap_free(url_copy);
3815 heap_free(url);
3816 return E_INVALIDARG;
3818 *wk2 = '\0';
3819 TRACE("Simplified, orig <%s>, simple <%s>\n", wine_dbgstr_w(src_url), wine_dbgstr_w(url_copy));
3821 nLen = lstrlenW(url_copy);
3822 while ((nLen > 0) && ((url_copy[nLen-1] <= ' ')))
3823 url_copy[--nLen]=0;
3825 if ((flags & URL_UNESCAPE) ||
3826 ((flags & URL_FILE_USE_PATHURL) && nByteLen >= 5*sizeof(WCHAR) && !wcsncmp(url, L"file:", 5)))
3828 UrlUnescapeW(url_copy, NULL, &nLen, URL_UNESCAPE_INPLACE);
3831 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT |
3832 URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_SEGMENT_ONLY);
3834 if (escape_flags)
3836 escape_flags &= ~URL_ESCAPE_UNSAFE;
3837 hr = UrlEscapeW(url_copy, canonicalized, canonicalized_len, escape_flags);
3839 else
3841 /* No escaping needed, just copy the string */
3842 nLen = lstrlenW(url_copy);
3843 if (nLen < *canonicalized_len)
3844 memcpy(canonicalized, url_copy, (nLen + 1)*sizeof(WCHAR));
3845 else
3847 hr = E_POINTER;
3848 nLen++;
3850 *canonicalized_len = nLen;
3853 heap_free(url_copy);
3854 heap_free(url);
3856 if (hr == S_OK)
3857 TRACE("result %s\n", wine_dbgstr_w(canonicalized));
3859 return hr;
3862 HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags)
3864 LPWSTR inW, outW;
3865 HRESULT hr;
3866 DWORD len;
3868 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags);
3870 if (!url || !out || !out_len)
3871 return E_INVALIDARG;
3873 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
3874 outW = inW + INTERNET_MAX_URL_LENGTH;
3876 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
3877 len = INTERNET_MAX_URL_LENGTH;
3879 hr = UrlApplySchemeW(inW, outW, &len, flags);
3880 if (hr != S_OK)
3882 heap_free(inW);
3883 return hr;
3886 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL);
3887 if (len > *out_len)
3889 hr = E_POINTER;
3890 goto cleanup;
3893 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL);
3894 len--;
3896 cleanup:
3897 *out_len = len;
3898 heap_free(inW);
3899 return hr;
3902 static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len)
3904 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH];
3905 DWORD value_len, data_len, dwType, i;
3906 WCHAR Wxx, Wyy;
3907 HKEY newkey;
3908 INT index;
3909 BOOL j;
3911 MultiByteToWideChar(CP_ACP, 0,
3912 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH);
3913 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
3914 index = 0;
3915 while (value_len = data_len = MAX_PATH,
3916 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0)
3918 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data));
3920 j = FALSE;
3921 for (i = 0; i < value_len; ++i)
3923 Wxx = url[i];
3924 Wyy = value[i];
3925 /* remember that TRUE is not-equal */
3926 j = ChrCmpIW(Wxx, Wyy);
3927 if (j) break;
3929 if ((i == value_len) && !j)
3931 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len)
3933 *out_len = lstrlenW(data) + lstrlenW(url) + 1;
3934 RegCloseKey(newkey);
3935 return E_POINTER;
3937 lstrcpyW(out, data);
3938 lstrcatW(out, url);
3939 *out_len = lstrlenW(out);
3940 TRACE("matched and set to %s\n", wine_dbgstr_w(out));
3941 RegCloseKey(newkey);
3942 return S_OK;
3944 index++;
3946 RegCloseKey(newkey);
3947 return E_FAIL;
3950 static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len)
3952 PARSEDURLW parsed_url;
3953 WCHAR *new_url;
3954 DWORD needed;
3955 HRESULT hr;
3957 parsed_url.cbSize = sizeof(parsed_url);
3958 if (ParseURLW(path, &parsed_url) == S_OK)
3960 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1)
3962 needed = lstrlenW(path);
3963 if (needed >= *url_len)
3965 *url_len = needed + 1;
3966 return E_POINTER;
3968 else
3970 *url_len = needed;
3971 return S_FALSE;
3976 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */
3977 lstrcpyW(new_url, L"file:");
3978 if (is_drive_spec( path )) lstrcatW(new_url, L"///");
3979 lstrcatW(new_url, path);
3980 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT);
3981 heap_free(new_url);
3982 return hr;
3985 static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length)
3987 DWORD data_len, dwType;
3988 WCHAR data[MAX_PATH];
3989 HKEY newkey;
3991 /* get and prepend default */
3992 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
3993 0, 1, &newkey);
3994 data_len = sizeof(data);
3995 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len);
3996 RegCloseKey(newkey);
3997 if (lstrlenW(data) + lstrlenW(url) + 1 > *length)
3999 *length = lstrlenW(data) + lstrlenW(url) + 1;
4000 return E_POINTER;
4002 lstrcpyW(out, data);
4003 lstrcatW(out, url);
4004 *length = lstrlenW(out);
4005 TRACE("used default %s\n", wine_dbgstr_w(out));
4006 return S_OK;
4009 HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags)
4011 PARSEDURLW in_scheme;
4012 DWORD res1;
4013 HRESULT hr;
4015 TRACE("%s, %p, %p:out size %d, %#x\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags);
4017 if (!url || !out || !length)
4018 return E_INVALIDARG;
4020 if (flags & URL_APPLY_GUESSFILE)
4022 if (*length > 1 && ':' == url[1])
4024 res1 = *length;
4025 hr = url_create_from_path(url, out, &res1);
4026 if (hr == S_OK || hr == E_POINTER)
4028 *length = res1;
4029 return hr;
4031 else if (hr == S_FALSE)
4033 return hr;
4038 in_scheme.cbSize = sizeof(in_scheme);
4039 /* See if the base has a scheme */
4040 res1 = ParseURLW(url, &in_scheme);
4041 if (res1)
4043 /* no scheme in input, need to see if we need to guess */
4044 if (flags & URL_APPLY_GUESSSCHEME)
4046 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL)
4047 return hr;
4051 /* If we are here, then either invalid scheme,
4052 * or no scheme and can't/failed guess.
4054 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT))
4055 return url_apply_default_scheme(url, out, length);
4057 return S_FALSE;
4060 INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash)
4062 INT ret, len, len1, len2;
4064 if (!ignore_slash)
4065 return strcmp(url1, url2);
4066 len1 = strlen(url1);
4067 if (url1[len1-1] == '/') len1--;
4068 len2 = strlen(url2);
4069 if (url2[len2-1] == '/') len2--;
4070 if (len1 == len2)
4071 return strncmp(url1, url2, len1);
4072 len = min(len1, len2);
4073 ret = strncmp(url1, url2, len);
4074 if (ret) return ret;
4075 if (len1 > len2) return 1;
4076 return -1;
4079 INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash)
4081 size_t len, len1, len2;
4082 INT ret;
4084 if (!ignore_slash)
4085 return lstrcmpW(url1, url2);
4086 len1 = lstrlenW(url1);
4087 if (url1[len1-1] == '/') len1--;
4088 len2 = lstrlenW(url2);
4089 if (url2[len2-1] == '/') len2--;
4090 if (len1 == len2)
4091 return wcsncmp(url1, url2, len1);
4092 len = min(len1, len2);
4093 ret = wcsncmp(url1, url2, len);
4094 if (ret) return ret;
4095 if (len1 > len2) return 1;
4096 return -1;
4099 HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars)
4101 DWORD srcLen;
4103 FIXME("%s, %p, %d stub\n", wine_dbgstr_w(url), translatedUrl, maxChars);
4105 if (!url)
4106 return E_FAIL;
4108 srcLen = lstrlenW(url) + 1;
4110 /* For now just copy the URL directly */
4111 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
4113 return S_OK;
4116 const char * WINAPI UrlGetLocationA(const char *url)
4118 PARSEDURLA base;
4120 base.cbSize = sizeof(base);
4121 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */
4123 /* if scheme is file: then never return pointer */
4124 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol)))
4125 return NULL;
4127 /* Look for '#' and return its addr */
4128 return strchr(base.pszSuffix, '#');
4131 const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url)
4133 PARSEDURLW base;
4135 base.cbSize = sizeof(base);
4136 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */
4138 /* if scheme is file: then never return pointer */
4139 if (!wcsncmp(base.pszProtocol, L"file", min(4, base.cchProtocol)))
4140 return NULL;
4142 /* Look for '#' and return its addr */
4143 return wcschr(base.pszSuffix, '#');
4146 HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags)
4148 LPWSTR inW, outW;
4149 DWORD len, len2;
4150 HRESULT hr;
4152 if (!url || !out || !out_len || !*out_len)
4153 return E_INVALIDARG;
4155 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4156 outW = inW + INTERNET_MAX_URL_LENGTH;
4158 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH);
4160 len = INTERNET_MAX_URL_LENGTH;
4161 hr = UrlGetPartW(inW, outW, &len, part, flags);
4162 if (FAILED(hr))
4164 heap_free(inW);
4165 return hr;
4168 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len, NULL, 0, NULL, NULL);
4169 if (len2 > *out_len)
4171 *out_len = len2 + 1;
4172 heap_free(inW);
4173 return E_POINTER;
4175 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL);
4176 *out_len = len2 - 1;
4177 heap_free(inW);
4178 return hr;
4181 static const WCHAR * scan_url(const WCHAR *start, DWORD *size, enum url_scan_type type)
4183 *size = 0;
4185 switch (type)
4187 case SCHEME:
4188 while ((*start >= 'a' && *start <= 'z') || (*start >= '0' && *start <= '9') ||
4189 *start == '+' || *start == '-' || *start == '.')
4191 start++;
4192 (*size)++;
4194 if (*start != ':')
4195 *size = 0;
4196 break;
4198 case USERPASS:
4199 for (;;)
4201 if (isalnum(*start) ||
4202 /* user/password only characters */
4203 (*start == ';') ||
4204 (*start == '?') ||
4205 (*start == '&') ||
4206 (*start == '=') ||
4207 /* *extra* characters */
4208 (*start == '!') ||
4209 (*start == '*') ||
4210 (*start == '\'') ||
4211 (*start == '(') ||
4212 (*start == ')') ||
4213 (*start == ',') ||
4214 /* *safe* characters */
4215 (*start == '$') ||
4216 (*start == '_') ||
4217 (*start == '+') ||
4218 (*start == '-') ||
4219 (*start == '.') ||
4220 (*start == ' '))
4222 start++;
4223 (*size)++;
4225 else if (*start == '%' && isxdigit(start[1]) && isxdigit(start[2]))
4227 start += 3;
4228 *size += 3;
4230 else break;
4232 break;
4234 case PORT:
4235 while (*start >= '0' && *start <= '9')
4237 start++;
4238 (*size)++;
4240 break;
4242 case HOST:
4243 while (isalnum(*start) || *start == '-' || *start == '.' || *start == ' ' || *start == '*')
4245 start++;
4246 (*size)++;
4248 break;
4250 default:
4251 FIXME("unknown type %d\n", type);
4252 return L"";
4255 return start;
4258 static LONG parse_url(const WCHAR *url, struct parsed_url *pl)
4260 const WCHAR *work;
4262 memset(pl, 0, sizeof(*pl));
4263 pl->scheme = url;
4264 work = scan_url(pl->scheme, &pl->scheme_len, SCHEME);
4265 if (!*work || (*work != ':')) goto ErrorExit;
4266 work++;
4267 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
4269 pl->username = work + 2;
4270 work = scan_url(pl->username, &pl->username_len, USERPASS);
4271 if (*work == ':' )
4273 /* parse password */
4274 work++;
4275 pl->password = work;
4276 work = scan_url(pl->password, &pl->password_len, USERPASS);
4277 if (*work != '@')
4279 /* what we just parsed must be the hostname and port
4280 * so reset pointers and clear then let it parse */
4281 pl->username_len = pl->password_len = 0;
4282 work = pl->username - 1;
4283 pl->username = pl->password = 0;
4286 else if (*work == '@')
4288 /* no password */
4289 pl->password_len = 0;
4290 pl->password = 0;
4292 else if (!*work || *work == '/' || *work == '.')
4294 /* what was parsed was hostname, so reset pointers and let it parse */
4295 pl->username_len = pl->password_len = 0;
4296 work = pl->username - 1;
4297 pl->username = pl->password = 0;
4299 else goto ErrorExit;
4301 /* now start parsing hostname or hostnumber */
4302 work++;
4303 pl->hostname = work;
4304 work = scan_url(pl->hostname, &pl->hostname_len, HOST);
4305 if (*work == ':')
4307 /* parse port */
4308 work++;
4309 pl->port = work;
4310 work = scan_url(pl->port, &pl->port_len, PORT);
4312 if (*work == '/')
4314 /* see if query string */
4315 pl->query = wcschr(work, '?');
4316 if (pl->query) pl->query_len = lstrlenW(pl->query);
4318 SuccessExit:
4319 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
4320 pl->scheme, pl->scheme_len, pl->username, pl->username_len, pl->password, pl->password_len, pl->hostname,
4321 pl->hostname_len, pl->port, pl->port_len, pl->query, pl->query_len);
4323 return S_OK;
4325 ErrorExit:
4326 FIXME("failed to parse %s\n", debugstr_w(url));
4327 return E_INVALIDARG;
4330 HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags)
4332 DWORD scheme, size, schsize;
4333 LPCWSTR addr, schaddr;
4334 struct parsed_url pl;
4335 HRESULT hr;
4337 TRACE("%s, %p, %p(%d), %#x, %#x\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags);
4339 if (!url || !out || !out_len || !*out_len)
4340 return E_INVALIDARG;
4342 *out = '\0';
4344 addr = wcschr(url, ':');
4345 if (!addr)
4346 scheme = URL_SCHEME_UNKNOWN;
4347 else
4348 scheme = get_scheme_code(url, addr - url);
4350 hr = parse_url(url, &pl);
4352 switch (part)
4354 case URL_PART_SCHEME:
4355 if (!pl.scheme_len)
4357 *out_len = 0;
4358 return S_FALSE;
4360 addr = pl.scheme;
4361 size = pl.scheme_len;
4362 break;
4364 case URL_PART_HOSTNAME:
4365 switch (scheme)
4367 case URL_SCHEME_FTP:
4368 case URL_SCHEME_HTTP:
4369 case URL_SCHEME_GOPHER:
4370 case URL_SCHEME_TELNET:
4371 case URL_SCHEME_FILE:
4372 case URL_SCHEME_HTTPS:
4373 break;
4374 default:
4375 *out_len = 0;
4376 return E_FAIL;
4379 if (scheme == URL_SCHEME_FILE && (!pl.hostname_len || (pl.hostname_len == 1 && *(pl.hostname + 1) == ':')))
4381 *out_len = 0;
4382 return S_FALSE;
4385 if (!pl.hostname_len)
4387 *out_len = 0;
4388 return S_FALSE;
4390 addr = pl.hostname;
4391 size = pl.hostname_len;
4392 break;
4394 case URL_PART_USERNAME:
4395 if (!pl.username_len)
4397 *out_len = 0;
4398 return S_FALSE;
4400 addr = pl.username;
4401 size = pl.username_len;
4402 break;
4404 case URL_PART_PASSWORD:
4405 if (!pl.password_len)
4407 *out_len = 0;
4408 return S_FALSE;
4410 addr = pl.password;
4411 size = pl.password_len;
4412 break;
4414 case URL_PART_PORT:
4415 if (!pl.port_len)
4417 *out_len = 0;
4418 return S_FALSE;
4420 addr = pl.port;
4421 size = pl.port_len;
4422 break;
4424 case URL_PART_QUERY:
4425 if (!pl.query_len)
4427 *out_len = 0;
4428 return S_FALSE;
4430 addr = pl.query;
4431 size = pl.query_len;
4432 break;
4434 default:
4435 *out_len = 0;
4436 return E_INVALIDARG;
4439 if (flags == URL_PARTFLAG_KEEPSCHEME)
4441 if (!pl.scheme || !pl.scheme_len)
4443 *out_len = 0;
4444 return E_FAIL;
4446 schaddr = pl.scheme;
4447 schsize = pl.scheme_len;
4448 if (*out_len < schsize + size + 2)
4450 *out_len = schsize + size + 2;
4451 return E_POINTER;
4453 memcpy(out, schaddr, schsize*sizeof(WCHAR));
4454 out[schsize] = ':';
4455 memcpy(out + schsize+1, addr, size*sizeof(WCHAR));
4456 out[schsize+1+size] = 0;
4457 *out_len = schsize + 1 + size;
4459 else
4461 if (*out_len < size + 1)
4463 *out_len = size + 1;
4464 return E_POINTER;
4466 memcpy(out, addr, size*sizeof(WCHAR));
4467 out[size] = 0;
4468 *out_len = size;
4470 TRACE("len=%d %s\n", *out_len, wine_dbgstr_w(out));
4472 return hr;
4475 BOOL WINAPI UrlIsA(const char *url, URLIS Urlis)
4477 const char *last;
4478 PARSEDURLA base;
4480 TRACE("%s, %d\n", debugstr_a(url), Urlis);
4482 if (!url)
4483 return FALSE;
4485 switch (Urlis) {
4487 case URLIS_OPAQUE:
4488 base.cbSize = sizeof(base);
4489 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */
4490 switch (base.nScheme)
4492 case URL_SCHEME_MAILTO:
4493 case URL_SCHEME_SHELL:
4494 case URL_SCHEME_JAVASCRIPT:
4495 case URL_SCHEME_VBSCRIPT:
4496 case URL_SCHEME_ABOUT:
4497 return TRUE;
4499 return FALSE;
4501 case URLIS_FILEURL:
4502 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL);
4504 case URLIS_DIRECTORY:
4505 last = url + strlen(url) - 1;
4506 return (last >= url && (*last == '/' || *last == '\\' ));
4508 case URLIS_URL:
4509 return PathIsURLA(url);
4511 case URLIS_NOHISTORY:
4512 case URLIS_APPLIABLE:
4513 case URLIS_HASQUERY:
4514 default:
4515 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis);
4518 return FALSE;
4521 BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis)
4523 const WCHAR *last;
4524 PARSEDURLW base;
4526 TRACE("%s, %d\n", debugstr_w(url), Urlis);
4528 if (!url)
4529 return FALSE;
4531 switch (Urlis)
4533 case URLIS_OPAQUE:
4534 base.cbSize = sizeof(base);
4535 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */
4536 switch (base.nScheme)
4538 case URL_SCHEME_MAILTO:
4539 case URL_SCHEME_SHELL:
4540 case URL_SCHEME_JAVASCRIPT:
4541 case URL_SCHEME_VBSCRIPT:
4542 case URL_SCHEME_ABOUT:
4543 return TRUE;
4545 return FALSE;
4547 case URLIS_FILEURL:
4548 return !wcsnicmp( url, L"file:", 5 );
4550 case URLIS_DIRECTORY:
4551 last = url + lstrlenW(url) - 1;
4552 return (last >= url && (*last == '/' || *last == '\\'));
4554 case URLIS_URL:
4555 return PathIsURLW(url);
4557 case URLIS_NOHISTORY:
4558 case URLIS_APPLIABLE:
4559 case URLIS_HASQUERY:
4560 default:
4561 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis);
4564 return FALSE;
4567 BOOL WINAPI UrlIsOpaqueA(const char *url)
4569 return UrlIsA(url, URLIS_OPAQUE);
4572 BOOL WINAPI UrlIsOpaqueW(const WCHAR *url)
4574 return UrlIsW(url, URLIS_OPAQUE);
4577 BOOL WINAPI UrlIsNoHistoryA(const char *url)
4579 return UrlIsA(url, URLIS_NOHISTORY);
4582 BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url)
4584 return UrlIsW(url, URLIS_NOHISTORY);
4587 HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved)
4589 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
4590 DWORD lenW = ARRAY_SIZE(bufW), lenA;
4591 UNICODE_STRING pathW;
4592 WCHAR *urlW = bufW;
4593 HRESULT hr;
4595 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path))
4596 return E_INVALIDARG;
4598 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER)
4600 urlW = heap_alloc(lenW * sizeof(WCHAR));
4601 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved);
4604 if (SUCCEEDED(hr))
4606 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
4607 if (*url_len > lenA)
4609 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR));
4610 url[lenA] = 0;
4611 *url_len = lenA;
4613 else
4615 *url_len = lenA + 1;
4616 hr = E_POINTER;
4619 if (urlW != bufW)
4620 heap_free(urlW);
4621 RtlFreeUnicodeString(&pathW);
4622 return hr;
4625 HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved)
4627 HRESULT hr;
4629 TRACE("%s, %p, %p, %#x\n", debugstr_w(path), url, url_len, reserved);
4631 if (reserved || !url || !url_len)
4632 return E_INVALIDARG;
4634 hr = url_create_from_path(path, url, url_len);
4635 if (hr == S_FALSE)
4636 lstrcpyW(url, path);
4638 return hr;
4641 HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags)
4643 WCHAR *baseW, *relativeW, *combinedW;
4644 DWORD len, len2;
4645 HRESULT hr;
4647 TRACE("%s, %s, %d, %#x\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags);
4649 if (!base || !relative || !combined_len)
4650 return E_INVALIDARG;
4652 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4653 relativeW = baseW + INTERNET_MAX_URL_LENGTH;
4654 combinedW = relativeW + INTERNET_MAX_URL_LENGTH;
4656 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH);
4657 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH);
4658 len = *combined_len;
4660 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags);
4661 if (hr != S_OK)
4663 *combined_len = len;
4664 heap_free(baseW);
4665 return hr;
4668 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL);
4669 if (len2 > *combined_len)
4671 *combined_len = len2;
4672 heap_free(baseW);
4673 return E_POINTER;
4675 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL);
4676 *combined_len = len2;
4677 heap_free(baseW);
4678 return S_OK;
4681 HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags)
4683 DWORD i, len, process_case = 0, myflags, sizeloc = 0;
4684 LPWSTR work, preliminary, mbase, mrelative;
4685 PARSEDURLW base, relative;
4686 HRESULT hr;
4688 TRACE("%s, %s, %d, %#x\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags);
4690 if (!baseW || !relativeW || !combined_len)
4691 return E_INVALIDARG;
4693 base.cbSize = sizeof(base);
4694 relative.cbSize = sizeof(relative);
4696 /* Get space for duplicates of the input and the output */
4697 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
4698 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
4699 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
4700 *preliminary = '\0';
4702 /* Canonicalize the base input prior to looking for the scheme */
4703 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
4704 len = INTERNET_MAX_URL_LENGTH;
4705 UrlCanonicalizeW(baseW, mbase, &len, myflags);
4707 /* Canonicalize the relative input prior to looking for the scheme */
4708 len = INTERNET_MAX_URL_LENGTH;
4709 UrlCanonicalizeW(relativeW, mrelative, &len, myflags);
4711 /* See if the base has a scheme */
4712 if (ParseURLW(mbase, &base) != S_OK)
4714 /* If base has no scheme return relative. */
4715 TRACE("no scheme detected in Base\n");
4716 process_case = 1;
4718 else do
4720 BOOL manual_search = FALSE;
4722 work = (LPWSTR)base.pszProtocol;
4723 for (i = 0; i < base.cchProtocol; ++i)
4724 work[i] = RtlDowncaseUnicodeChar(work[i]);
4726 /* mk is a special case */
4727 if (base.nScheme == URL_SCHEME_MK)
4729 WCHAR *ptr = wcsstr(base.pszSuffix, L"::");
4730 if (ptr)
4732 int delta;
4734 ptr += 2;
4735 delta = ptr-base.pszSuffix;
4736 base.cchProtocol += delta;
4737 base.pszSuffix += delta;
4738 base.cchSuffix -= delta;
4741 else
4743 /* get size of location field (if it exists) */
4744 work = (LPWSTR)base.pszSuffix;
4745 sizeloc = 0;
4746 if (*work++ == '/')
4748 if (*work++ == '/')
4750 /* At this point have start of location and
4751 * it ends at next '/' or end of string.
4753 while (*work && (*work != '/')) work++;
4754 sizeloc = (DWORD)(work - base.pszSuffix);
4759 /* If there is a '?', then the remaining part can only contain a
4760 * query string or fragment, so start looking for the last leaf
4761 * from the '?'. Otherwise, if there is a '#' and the characters
4762 * immediately preceding it are ".htm[l]", then begin looking for
4763 * the last leaf starting from the '#'. Otherwise the '#' is not
4764 * meaningful and just start looking from the end. */
4765 if ((work = wcspbrk(base.pszSuffix + sizeloc, L"#?")))
4767 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
4768 manual_search = TRUE;
4769 else if (work - base.pszSuffix > 4)
4771 if (!wcsnicmp(work - 4, L".htm", 4)) manual_search = TRUE;
4774 if (!manual_search && work - base.pszSuffix > 5)
4776 if (!wcsnicmp(work - 5, L".html", 5)) manual_search = TRUE;
4780 if (manual_search)
4782 /* search backwards starting from the current position */
4783 while (*work != '/' && work > base.pszSuffix + sizeloc)
4784 --work;
4785 base.cchSuffix = work - base.pszSuffix + 1;
4787 else
4789 /* search backwards starting from the end of the string */
4790 work = wcsrchr((base.pszSuffix+sizeloc), '/');
4791 if (work)
4793 len = (DWORD)(work - base.pszSuffix + 1);
4794 base.cchSuffix = len;
4796 else
4797 base.cchSuffix = sizeloc;
4801 * At this point:
4802 * .pszSuffix points to location (starting with '//')
4803 * .cchSuffix length of location (above) and rest less the last
4804 * leaf (if any)
4805 * sizeloc length of location (above) up to but not including
4806 * the last '/'
4809 if (ParseURLW(mrelative, &relative) != S_OK)
4811 /* No scheme in relative */
4812 TRACE("no scheme detected in Relative\n");
4813 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
4814 relative.cchSuffix = lstrlenW(mrelative);
4815 if (*relativeW == ':')
4817 /* Case that is either left alone or uses base. */
4818 if (flags & URL_PLUGGABLE_PROTOCOL)
4820 process_case = 5;
4821 break;
4823 process_case = 1;
4824 break;
4826 if (is_drive_spec( mrelative ))
4828 /* case that becomes "file:///" */
4829 lstrcpyW(preliminary, L"file:///");
4830 process_case = 1;
4831 break;
4833 if (*mrelative == '/' && *(mrelative+1) == '/')
4835 /* Relative has location and the rest. */
4836 process_case = 3;
4837 break;
4839 if (*mrelative == '/')
4841 /* Relative is root to location. */
4842 process_case = 4;
4843 break;
4845 if (*mrelative == '#')
4847 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#')))
4848 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix);
4850 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
4851 preliminary[work-base.pszProtocol] = '\0';
4852 process_case = 1;
4853 break;
4855 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
4856 break;
4858 else
4860 work = (LPWSTR)relative.pszProtocol;
4861 for (i = 0; i < relative.cchProtocol; ++i)
4862 work[i] = RtlDowncaseUnicodeChar(work[i]);
4865 /* Handle cases where relative has scheme. */
4866 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol))
4868 /* since the schemes are the same */
4869 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4871 /* Relative replaces location and what follows. */
4872 process_case = 3;
4873 break;
4875 if (*relative.pszSuffix == '/')
4877 /* Relative is root to location */
4878 process_case = 4;
4879 break;
4881 /* replace either just location if base's location starts with a
4882 * slash or otherwise everything */
4883 process_case = (*base.pszSuffix == '/') ? 5 : 1;
4884 break;
4887 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/')
4889 /* Relative replaces scheme, location, and following and handles PLUGGABLE */
4890 process_case = 2;
4891 break;
4893 process_case = 1;
4894 break;
4895 } while (FALSE); /* a little trick to allow easy exit from nested if's */
4897 hr = S_OK;
4898 switch (process_case)
4900 case 1:
4901 /* Return relative appended to whatever is in combined (which may the string "file:///" */
4902 lstrcatW(preliminary, mrelative);
4903 break;
4905 case 2:
4906 /* Relative replaces scheme and location */
4907 lstrcpyW(preliminary, mrelative);
4908 break;
4910 case 3:
4911 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */
4912 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
4913 work = preliminary + base.cchProtocol + 1;
4914 lstrcpyW(work, relative.pszSuffix);
4915 break;
4917 case 4:
4918 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */
4919 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
4920 work = preliminary + base.cchProtocol + 1 + sizeloc;
4921 if (flags & URL_PLUGGABLE_PROTOCOL)
4922 *(work++) = '/';
4923 lstrcpyW(work, relative.pszSuffix);
4924 break;
4926 case 5:
4927 /* Return the base without its document (if any) and append relative after its scheme. */
4928 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR));
4929 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1;
4930 if (*work++ != '/')
4931 *(work++) = '/';
4932 lstrcpyW(work, relative.pszSuffix);
4933 break;
4935 default:
4936 FIXME("Unexpected case %d.\n", process_case);
4937 hr = E_INVALIDARG;
4940 if (hr == S_OK)
4942 /* Reuse mrelative as temp storage as it's already allocated and not needed anymore */
4943 if (*combined_len == 0)
4944 *combined_len = 1;
4945 hr = UrlCanonicalizeW(preliminary, mrelative, combined_len, flags & ~URL_FILE_USE_PATHURL);
4946 if (SUCCEEDED(hr) && combined)
4947 lstrcpyW(combined, mrelative);
4949 TRACE("return-%d len=%d, %s\n", process_case, *combined_len, debugstr_w(combined));
4952 heap_free(preliminary);
4953 return hr;
4956 HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len)
4958 INT src_count = src_len - 1, dest_count = dest_len - 1;
4960 if (!src || !dest)
4961 return E_INVALIDARG;
4963 while (dest_count >= 0)
4965 dest[dest_count] = (dest_count & 0xff);
4966 dest_count--;
4969 while (src_count >= 0)
4971 dest_count = dest_len - 1;
4972 while (dest_count >= 0)
4974 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]];
4975 dest_count--;
4977 src_count--;
4980 return S_OK;
4983 HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len)
4985 __TRY
4987 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len);
4989 __EXCEPT_PAGE_FAULT
4991 return E_INVALIDARG;
4993 __ENDTRY
4994 return S_OK;
4997 HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len)
4999 char urlA[MAX_PATH];
5001 TRACE("%s, %p, %d\n", debugstr_w(url), dest, dest_len);
5003 __TRY
5005 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL);
5006 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len);
5008 __EXCEPT_PAGE_FAULT
5010 return E_INVALIDARG;
5012 __ENDTRY
5013 return S_OK;
5016 BOOL WINAPI IsInternetESCEnabled(void)
5018 FIXME(": stub\n");
5019 return FALSE;