Reactos
at master 5309 lines 146 kB view raw
1/* 2 * Copyright 2018 Nikolay Sivov 3 * Copyright 2018 Zhiyi Zhang 4 * Copyright 2021-2023 Zebediah Figura 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21#include <stdarg.h> 22#include <stdbool.h> 23#include <string.h> 24#include <wchar.h> 25 26#ifndef STATIC_PATHCCH 27 28#include "windef.h" 29#include "winbase.h" 30#include "pathcch.h" 31#include "strsafe.h" 32#include "shlwapi.h" 33#include "wininet.h" 34#include "intshcut.h" 35#include "winternl.h" 36 37#include "kernelbase.h" 38#include "wine/exception.h" 39#include "wine/debug.h" 40#include "wine/heap.h" 41 42WINE_DEFAULT_DEBUG_CHANNEL(path); 43 44#else // STATIC_PATHCCH 45#ifdef __REACTOS__ 46/* This is the static implementation of the PathCch library */ 47 48#include <windef.h> 49#include <winbase.h> 50 51/* The PathCch functions use size_t, but Wine's implementation uses SIZE_T, 52 * so temporarily change the define'd SIZE_T type to the compatible one... */ 53#undef SIZE_T 54#define SIZE_T size_t 55 56#include <pathcch.h> 57#include <strsafe.h> 58 59#define TRACE(...) 60 61#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || (DLL_EXPORT_VERSION < _WIN32_WINNT_VISTA) 62/* wcsnlen is an NT6+ function. To cover all cases, use a private implementation */ 63static inline size_t compat_wcsnlen(const wchar_t* str, size_t size) 64{ 65 StringCchLengthW(str, size, &size); 66 return size; 67} 68#define wcsnlen compat_wcsnlen 69#endif /* _WIN32_WINNT || DLL_EXPORT_VERSION */ 70 71// Implementation from string.c copied here in order not 72// to depend on the whole file just for the PathCch library. 73WCHAR * WINAPI StrRChrW(const WCHAR *str, const WCHAR *end, WORD ch) 74{ 75 WCHAR *ret = NULL; 76 77 if (!str) return NULL; 78 if (!end) end = str + lstrlenW(str); 79 while (str < end) 80 { 81 if (*str == ch) ret = (WCHAR *)str; 82 str++; 83 } 84 return ret; 85} 86 87#endif /* __REACTOS__ */ 88#endif // STATIC_PATHCCH 89 90#ifndef STATIC_PATHCCH 91 92static const char hexDigits[] = "0123456789ABCDEF"; 93 94static const unsigned char hashdata_lookup[256] = 95{ 96 0x01, 0x0e, 0x6e, 0x19, 0x61, 0xae, 0x84, 0x77, 0x8a, 0xaa, 0x7d, 0x76, 0x1b, 0xe9, 0x8c, 0x33, 97 0x57, 0xc5, 0xb1, 0x6b, 0xea, 0xa9, 0x38, 0x44, 0x1e, 0x07, 0xad, 0x49, 0xbc, 0x28, 0x24, 0x41, 98 0x31, 0xd5, 0x68, 0xbe, 0x39, 0xd3, 0x94, 0xdf, 0x30, 0x73, 0x0f, 0x02, 0x43, 0xba, 0xd2, 0x1c, 99 0x0c, 0xb5, 0x67, 0x46, 0x16, 0x3a, 0x4b, 0x4e, 0xb7, 0xa7, 0xee, 0x9d, 0x7c, 0x93, 0xac, 0x90, 100 0xb0, 0xa1, 0x8d, 0x56, 0x3c, 0x42, 0x80, 0x53, 0x9c, 0xf1, 0x4f, 0x2e, 0xa8, 0xc6, 0x29, 0xfe, 101 0xb2, 0x55, 0xfd, 0xed, 0xfa, 0x9a, 0x85, 0x58, 0x23, 0xce, 0x5f, 0x74, 0xfc, 0xc0, 0x36, 0xdd, 102 0x66, 0xda, 0xff, 0xf0, 0x52, 0x6a, 0x9e, 0xc9, 0x3d, 0x03, 0x59, 0x09, 0x2a, 0x9b, 0x9f, 0x5d, 103 0xa6, 0x50, 0x32, 0x22, 0xaf, 0xc3, 0x64, 0x63, 0x1a, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xbd, 104 0x79, 0x40, 0x4d, 0x48, 0xd0, 0xf5, 0x82, 0x7a, 0x8f, 0x37, 0x69, 0x86, 0x1d, 0xa4, 0xb9, 0xc2, 105 0xc1, 0xef, 0x65, 0xf2, 0x05, 0xab, 0x7e, 0x0b, 0x4a, 0x3b, 0x89, 0xe4, 0x6c, 0xbf, 0xe8, 0x8b, 106 0x06, 0x18, 0x51, 0x14, 0x7f, 0x11, 0x5b, 0x5c, 0xfb, 0x97, 0xe1, 0xcf, 0x15, 0x62, 0x71, 0x70, 107 0x54, 0xe2, 0x12, 0xd6, 0xc7, 0xbb, 0x0d, 0x20, 0x5e, 0xdc, 0xe0, 0xd4, 0xf7, 0xcc, 0xc4, 0x2b, 108 0xf9, 0xec, 0x2d, 0xf4, 0x6f, 0xb6, 0x99, 0x88, 0x81, 0x5a, 0xd9, 0xca, 0x13, 0xa5, 0xe7, 0x47, 109 0xe6, 0x8e, 0x60, 0xe3, 0x3e, 0xb3, 0xf6, 0x72, 0xa2, 0x35, 0xa0, 0xd7, 0xcd, 0xb4, 0x2f, 0x6d, 110 0x2c, 0x26, 0x1f, 0x95, 0x87, 0x00, 0xd8, 0x34, 0x3f, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xb8, 111 0xa3, 0xc8, 0xde, 0xeb, 0xf8, 0xf3, 0xdb, 0x0a, 0x98, 0x83, 0x7b, 0xe5, 0xcb, 0x4c, 0x78, 0xd1, 112}; 113 114struct parsed_url 115{ 116 const WCHAR *scheme; /* [out] start of scheme */ 117 DWORD scheme_len; /* [out] size of scheme (until colon) */ 118 const WCHAR *username; /* [out] start of Username */ 119 DWORD username_len; /* [out] size of Username (until ":" or "@") */ 120 const WCHAR *password; /* [out] start of Password */ 121 DWORD password_len; /* [out] size of Password (until "@") */ 122 const WCHAR *hostname; /* [out] start of Hostname */ 123 DWORD hostname_len; /* [out] size of Hostname (until ":" or "/") */ 124 const WCHAR *port; /* [out] start of Port */ 125 DWORD port_len; /* [out] size of Port (until "/" or eos) */ 126 const WCHAR *query; /* [out] start of Query */ 127 DWORD query_len; /* [out] size of Query (until eos) */ 128 DWORD scheme_number; 129}; 130 131static WCHAR *heap_strdupAtoW(const char *str) 132{ 133 WCHAR *ret = NULL; 134 135 if (str) 136 { 137 DWORD len; 138 139 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); 140 ret = heap_alloc(len * sizeof(WCHAR)); 141 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len); 142 } 143 144 return ret; 145} 146 147static bool array_reserve(void **elements, size_t *capacity, size_t count, size_t size) 148{ 149 unsigned int new_capacity, max_capacity; 150 void *new_elements; 151 152 if (count <= *capacity) 153 return true; 154 155 max_capacity = ~(SIZE_T)0 / size; 156 if (count > max_capacity) 157 return false; 158 159 new_capacity = max(4, *capacity); 160 while (new_capacity < count && new_capacity <= max_capacity / 2) 161 new_capacity *= 2; 162 if (new_capacity < count) 163 new_capacity = max_capacity; 164 165 if (!(new_elements = heap_realloc( *elements, new_capacity * size ))) 166 return false; 167 168 *elements = new_elements; 169 *capacity = new_capacity; 170 171 return true; 172} 173 174static bool is_slash( char c ) 175{ 176 return c == '/' || c == '\\'; 177} 178 179static BOOL is_drive_specA( const char *str ) 180{ 181 return isalpha( str[0] ) && str[1] == ':'; 182} 183 184#endif // !STATIC_PATHCCH 185 186static BOOL is_drive_spec( const WCHAR *str ) 187{ 188 return isalpha( str[0] ) && str[1] == ':'; 189} 190 191#ifndef STATIC_PATHCCH 192static BOOL is_escaped_drive_spec( const WCHAR *str ) 193{ 194 return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|'); 195} 196#endif // !STATIC_PATHCCH 197 198static BOOL is_prefixed_unc(const WCHAR *string) 199{ 200 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 ); 201} 202 203static BOOL is_prefixed_disk(const WCHAR *string) 204{ 205 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 ); 206} 207 208static BOOL is_prefixed_volume(const WCHAR *string) 209{ 210 const WCHAR *guid; 211 INT i = 0; 212 213 if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE; 214 215 guid = string + 10; 216 217 while (i <= 37) 218 { 219 switch (i) 220 { 221 case 0: 222 if (guid[i] != '{') return FALSE; 223 break; 224 case 9: 225 case 14: 226 case 19: 227 case 24: 228 if (guid[i] != '-') return FALSE; 229 break; 230 case 37: 231 if (guid[i] != '}') return FALSE; 232 break; 233 default: 234 if (!isxdigit(guid[i])) return FALSE; 235 break; 236 } 237 i++; 238 } 239 240 return TRUE; 241} 242 243/* Get the next character beyond end of the segment. 244 Return TRUE if the last segment ends with a backslash */ 245static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment) 246{ 247 while (*next && *next != '\\') next++; 248 if (*next == '\\') 249 { 250 *next_segment = next + 1; 251 return TRUE; 252 } 253 else 254 { 255 *next_segment = next; 256 return FALSE; 257 } 258} 259 260/* Find the last character of the root in a path, if there is one, without any segments */ 261static const WCHAR *get_root_end(const WCHAR *path) 262{ 263 /* Find path root */ 264 if (is_prefixed_volume(path)) 265 return path[48] == '\\' ? path + 48 : path + 47; 266 else if (is_prefixed_unc(path)) 267 return path + 7; 268 else if (is_prefixed_disk(path)) 269 return path[6] == '\\' ? path + 6 : path + 5; 270 /* \\ */ 271 else if (path[0] == '\\' && path[1] == '\\') 272 return path + 1; 273 /* \ */ 274 else if (path[0] == '\\') 275 return path; 276 /* X:\ */ 277 else if (is_drive_spec( path )) 278 return path[2] == '\\' ? path + 2 : path + 1; 279 else 280 return NULL; 281} 282 283HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out) 284{ 285 WCHAR *buffer, *dst; 286 const WCHAR *src; 287 const WCHAR *root_end; 288 SIZE_T buffer_size, length; 289 290 TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out); 291 292 if (!path_in || !path_out 293 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)) 294 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS) 295 && !(flags & PATHCCH_ALLOW_LONG_PATHS)) 296 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS))) 297 { 298 if (path_out) *path_out = NULL; 299 return E_INVALIDARG; 300 } 301 302 length = lstrlenW(path_in); 303 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))) 304 || (length + 1 > PATHCCH_MAX_CCH)) 305 { 306 *path_out = NULL; 307 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 308 } 309 310 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */ 311 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS; 312 313 /* path length + possible \\?\ addition + possible \ addition + NUL */ 314 buffer_size = (length + 6) * sizeof(WCHAR); 315 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size); 316 if (!buffer) 317 { 318 *path_out = NULL; 319 return E_OUTOFMEMORY; 320 } 321 322 src = path_in; 323 dst = buffer; 324 325 root_end = get_root_end(path_in); 326 if (root_end) root_end = buffer + (root_end - path_in); 327 328 /* Copy path root */ 329 if (root_end) 330 { 331 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR)); 332 src += root_end - buffer + 1; 333 if(PathCchStripPrefix(dst, length + 6) == S_OK) 334 { 335 /* Fill in \ in X:\ if the \ is missing */ 336 if (is_drive_spec( dst ) && dst[2]!= '\\') 337 { 338 dst[2] = '\\'; 339 dst[3] = 0; 340 } 341 dst = buffer + lstrlenW(buffer); 342 root_end = dst; 343 } 344 else 345 dst += root_end - buffer + 1; 346 } 347 348 while (*src) 349 { 350 if (src[0] == '.') 351 { 352 if (src[1] == '.') 353 { 354 /* Keep one . after * */ 355 if (dst > buffer && dst[-1] == '*') 356 { 357 *dst++ = *src++; 358 continue; 359 } 360 361 /* Keep the .. if not surrounded by \ */ 362 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\')) 363 { 364 *dst++ = *src++; 365 *dst++ = *src++; 366 continue; 367 } 368 369 /* Remove the \ before .. if the \ is not part of root */ 370 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) 371 { 372 *--dst = '\0'; 373 /* Remove characters until a \ is encountered */ 374 while (dst > buffer) 375 { 376 if (dst[-1] == '\\') 377 { 378 *--dst = 0; 379 break; 380 } 381 else 382 *--dst = 0; 383 } 384 } 385 /* Remove the extra \ after .. if the \ before .. wasn't deleted */ 386 else if (src[2] == '\\') 387 src++; 388 389 src += 2; 390 } 391 else 392 { 393 /* Keep the . if not surrounded by \ */ 394 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\')) 395 { 396 *dst++ = *src++; 397 continue; 398 } 399 400 /* Remove the \ before . if the \ is not part of root */ 401 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--; 402 /* Remove the extra \ after . if the \ before . wasn't deleted */ 403 else if (src[1] == '\\') 404 src++; 405 406 src++; 407 } 408 409 /* If X:\ is not complete, then complete it */ 410 if (is_drive_spec( buffer ) && buffer[2] != '\\') 411 { 412 root_end = buffer + 2; 413 dst = buffer + 3; 414 buffer[2] = '\\'; 415 /* If next character is \, use the \ to fill in */ 416 if (src[0] == '\\') src++; 417 } 418 } 419 /* Copy over */ 420 else 421 *dst++ = *src++; 422 } 423 /* End the path */ 424 *dst = 0; 425 426 /* Strip multiple trailing . */ 427 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS)) 428 { 429 while (dst > buffer && dst[-1] == '.') 430 { 431 /* Keep a . after * */ 432 if (dst - 1 > buffer && dst[-2] == '*') 433 break; 434 /* If . follow a : at the second character, remove the . and add a \ */ 435 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1) 436 *--dst = '\\'; 437 else 438 *--dst = 0; 439 } 440 } 441 442 /* If result path is empty, fill in \ */ 443 if (!*buffer) 444 { 445 buffer[0] = '\\'; 446 buffer[1] = 0; 447 } 448 449 /* Extend the path if needed */ 450 length = lstrlenW(buffer); 451 if (((length + 1 > MAX_PATH && is_drive_spec( buffer )) 452 || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)) 453 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS)) 454 { 455 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR)); 456 buffer[0] = '\\'; 457 buffer[1] = '\\'; 458 buffer[2] = '?'; 459 buffer[3] = '\\'; 460 } 461 462 /* Add a trailing backslash to the path if needed */ 463 if (flags & PATHCCH_ENSURE_TRAILING_SLASH) 464 PathCchAddBackslash(buffer, buffer_size); 465 466 *path_out = buffer; 467 return S_OK; 468} 469 470HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out) 471{ 472 SIZE_T combined_length, length2; 473 WCHAR *combined_path; 474 BOOL add_backslash = FALSE; 475 HRESULT hr; 476 477 TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out); 478 479 if ((!path1 && !path2) || !out) 480 { 481 if (out) *out = NULL; 482 return E_INVALIDARG; 483 } 484 485 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out); 486 487 /* If path2 is fully qualified, use path2 only */ 488 if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\')) 489 { 490 path1 = path2; 491 path2 = NULL; 492 add_backslash = (is_drive_spec(path1) && !path1[2]) 493 || (is_prefixed_disk(path1) && !path1[6]); 494 } 495 496 length2 = path2 ? lstrlenW(path2) : 0; 497 /* path1 length + path2 length + possible backslash + NULL */ 498 combined_length = lstrlenW(path1) + length2 + 2; 499 500 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR)); 501 if (!combined_path) 502 { 503 *out = NULL; 504 return E_OUTOFMEMORY; 505 } 506 507 lstrcpyW(combined_path, path1); 508 PathCchStripPrefix(combined_path, combined_length); 509 if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); 510 511 if (path2 && path2[0]) 512 { 513 if (path2[0] == '\\' && path2[1] != '\\') 514 { 515 PathCchStripToRoot(combined_path, combined_length); 516 path2++; 517 } 518 519 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); 520 lstrcatW(combined_path, path2); 521 } 522 523 hr = PathAllocCanonicalize(combined_path, flags, out); 524 HeapFree(GetProcessHeap(), 0, combined_path); 525 return hr; 526} 527 528HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size) 529{ 530 return PathCchAddBackslashEx(path, size, NULL, NULL); 531} 532 533HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining) 534{ 535 BOOL needs_termination; 536 SIZE_T length; 537 538 TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining); 539 540 length = lstrlenW(path); 541 needs_termination = size && length && path[length - 1] != '\\'; 542 543 if (length >= (needs_termination ? size - 1 : size)) 544 { 545 if (endptr) *endptr = NULL; 546 if (remaining) *remaining = 0; 547 return STRSAFE_E_INSUFFICIENT_BUFFER; 548 } 549 550 if (!needs_termination) 551 { 552 if (endptr) *endptr = path + length; 553 if (remaining) *remaining = size - length; 554 return S_FALSE; 555 } 556 557 path[length++] = '\\'; 558 path[length] = 0; 559 560 if (endptr) *endptr = path + length; 561 if (remaining) *remaining = size - length; 562 563 return S_OK; 564} 565 566HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) 567{ 568 const WCHAR *existing_extension, *next; 569 SIZE_T path_length, extension_length, dot_length; 570 BOOL has_dot; 571 HRESULT hr; 572 573 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); 574 575 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG; 576 577 next = extension; 578 while (*next) 579 { 580 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG; 581 next++; 582 } 583 584 has_dot = extension[0] == '.'; 585 586 hr = PathCchFindExtension(path, size, &existing_extension); 587 if (FAILED(hr)) return hr; 588 if (*existing_extension) return S_FALSE; 589 590 path_length = wcsnlen(path, size); 591 dot_length = has_dot ? 0 : 1; 592 extension_length = lstrlenW(extension); 593 594 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER; 595 596 /* If extension is empty or only dot, return S_OK with path unchanged */ 597 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK; 598 599 if (!has_dot) 600 { 601 path[path_length] = '.'; 602 path_length++; 603 } 604 605 lstrcpyW(path + path_length, extension); 606 return S_OK; 607} 608 609HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2) 610{ 611 TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2)); 612 613 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE); 614} 615 616HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags) 617{ 618 HRESULT hr; 619 WCHAR *result; 620 621 TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags); 622 623 if (!path1 || !size) return E_INVALIDARG; 624 625 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs. 626 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output 627 * buffer for PathCchCombineEx */ 628 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); 629 if (!result) return E_OUTOFMEMORY; 630 631 /* Avoid the single backslash behavior with PathCchCombineEx when appending */ 632 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++; 633 634 hr = PathCchCombineEx(result, size, path1, path2, flags); 635 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR)); 636 637 HeapFree(GetProcessHeap(), 0, result); 638 return hr; 639} 640 641HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in) 642{ 643 TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in)); 644 645 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ 646 if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\')) 647 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 648 649 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE); 650} 651 652HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags) 653{ 654 WCHAR *buffer; 655 SIZE_T length; 656 HRESULT hr; 657 658 TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags); 659 660 if (!size) return E_INVALIDARG; 661 662 hr = PathAllocCanonicalize(in, flags, &buffer); 663 if (FAILED(hr)) return hr; 664 665 length = lstrlenW(buffer); 666 if (size < length + 1) 667 { 668 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ 669 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\'))) 670 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 671 else 672 hr = STRSAFE_E_INSUFFICIENT_BUFFER; 673 } 674 675 if (SUCCEEDED(hr)) 676 { 677 memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); 678 679 /* Fill a backslash at the end of X: */ 680 if (is_drive_spec( out ) && !out[2] && size > 3) 681 { 682 out[2] = '\\'; 683 out[3] = 0; 684 } 685 } 686 687 LocalFree(buffer); 688 return hr; 689} 690 691HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2) 692{ 693 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2)); 694 695 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE); 696} 697 698HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags) 699{ 700 HRESULT hr; 701 WCHAR *buffer; 702 SIZE_T length; 703 704 TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags); 705 706 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 707 708 hr = PathAllocCombine(path1, path2, flags, &buffer); 709 if (FAILED(hr)) 710 { 711 out[0] = 0; 712 return hr; 713 } 714 715 length = lstrlenW(buffer); 716 if (length + 1 > size) 717 { 718 out[0] = 0; 719 LocalFree(buffer); 720 return STRSAFE_E_INSUFFICIENT_BUFFER; 721 } 722 else 723 { 724 memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); 725 LocalFree(buffer); 726 return S_OK; 727 } 728} 729 730HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension) 731{ 732 const WCHAR *lastpoint = NULL; 733 SIZE_T counter = 0; 734 735 TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension); 736 737 if (!path || !size || size > PATHCCH_MAX_CCH) 738 { 739 *extension = NULL; 740 return E_INVALIDARG; 741 } 742 743 while (*path) 744 { 745 if (*path == '\\' || *path == ' ') 746 lastpoint = NULL; 747 else if (*path == '.') 748 lastpoint = path; 749 750 path++; 751 counter++; 752 if (counter == size || counter == PATHCCH_MAX_CCH) 753 { 754 *extension = NULL; 755 return E_INVALIDARG; 756 } 757 } 758 759 *extension = lastpoint ? lastpoint : path; 760 return S_OK; 761} 762 763BOOL WINAPI PathCchIsRoot(const WCHAR *path) 764{ 765 const WCHAR *root_end; 766 const WCHAR *next; 767 BOOL is_unc; 768 769 TRACE("%s\n", wine_dbgstr_w(path)); 770 771 if (!path || !*path) return FALSE; 772 773 root_end = get_root_end(path); 774 if (!root_end) return FALSE; 775 776 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')) 777 { 778 next = root_end + 1; 779 /* No extra segments */ 780 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE; 781 782 /* Has first segment with an ending backslash but no remaining characters */ 783 if (get_next_segment(next, &next) && !*next) return FALSE; 784 /* Has first segment with no ending backslash */ 785 else if (!*next) 786 return TRUE; 787 /* Has first segment with an ending backslash and has remaining characters*/ 788 else 789 { 790 /* Second segment must have no backslash and no remaining characters */ 791 return !get_next_segment(next, &next) && !*next; 792 } 793 } 794 else if (*root_end == '\\' && !root_end[1]) 795 return TRUE; 796 else 797 return FALSE; 798} 799 800HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size) 801{ 802 WCHAR *path_end; 803 SIZE_T free_size; 804 805 TRACE("%s %Iu\n", debugstr_w(path), path_size); 806 807 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size); 808} 809 810HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size) 811{ 812 const WCHAR *root_end; 813 SIZE_T path_length; 814 815 TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size); 816 817 if (!path_size || !path_end || !free_size) 818 { 819 if (path_end) *path_end = NULL; 820 if (free_size) *free_size = 0; 821 return E_INVALIDARG; 822 } 823 824 path_length = wcsnlen(path, path_size); 825 if (path_length == path_size && !path[path_length]) return E_INVALIDARG; 826 827 root_end = get_root_end(path); 828 if (path_length > 0 && path[path_length - 1] == '\\') 829 { 830 *path_end = path + path_length - 1; 831 *free_size = path_size - path_length + 1; 832 /* If the last character is beyond end of root */ 833 if (!root_end || path + path_length - 1 > root_end) 834 { 835 path[path_length - 1] = 0; 836 return S_OK; 837 } 838 else 839 return S_FALSE; 840 } 841 else 842 { 843 *path_end = path + path_length; 844 *free_size = path_size - path_length; 845 return S_FALSE; 846 } 847} 848 849HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size) 850{ 851 const WCHAR *extension; 852 WCHAR *next; 853 HRESULT hr; 854 855 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 856 857 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 858 859 hr = PathCchFindExtension(path, size, &extension); 860 if (FAILED(hr)) return hr; 861 862 next = path + (extension - path); 863 while (next - path < size && *next) *next++ = 0; 864 865 return next == extension ? S_FALSE : S_OK; 866} 867 868HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size) 869{ 870 WCHAR *last, *root_end; 871 872 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 873 874 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 875 876 if (FAILED(PathCchSkipRoot(path, (const WCHAR **)&root_end))) 877 root_end = path; 878 879 /* The backslash at the end of UNC and \\* are not considered part of root in this case */ 880 if (root_end > path && root_end[-1] == '\\' && ((is_prefixed_unc(path) && path[8]) 881 || (path[0] == '\\' && path[1] == '\\' && path[2] && path[2] != '?'))) 882 root_end--; 883 884 if (!(last = StrRChrW(root_end, NULL, '\\'))) last = root_end; 885 if (last > root_end && last[-1] == '\\' && last[1] != '?') --last; 886 if (last - path >= size) return E_INVALIDARG; 887 if (!*last) return S_FALSE; 888 *last = 0; 889 return S_OK; 890} 891 892HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) 893{ 894 HRESULT hr; 895 896 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); 897 898 hr = PathCchRemoveExtension(path, size); 899 if (FAILED(hr)) return hr; 900 901 hr = PathCchAddExtension(path, size, extension); 902 return FAILED(hr) ? hr : S_OK; 903} 904 905HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end) 906{ 907 TRACE("%s %p\n", debugstr_w(path), root_end); 908 909 if (!path || !path[0] || !root_end 910 || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path) 911 && !is_prefixed_disk(path))) 912 return E_INVALIDARG; 913 914 *root_end = get_root_end(path); 915 if (*root_end) 916 { 917 (*root_end)++; 918 if (is_prefixed_unc(path)) 919 { 920 get_next_segment(*root_end, root_end); 921 get_next_segment(*root_end, root_end); 922 } 923 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') 924 { 925 /* Skip share server */ 926 get_next_segment(*root_end, root_end); 927 /* If mount point is empty, don't skip over mount point */ 928 if (**root_end != '\\') get_next_segment(*root_end, root_end); 929 } 930 } 931 932 return *root_end ? S_OK : E_INVALIDARG; 933} 934 935HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size) 936{ 937 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 938 939 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 940 941 if (is_prefixed_unc(path)) 942 { 943 /* \\?\UNC\a -> \\a */ 944 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG; 945 lstrcpyW(path + 2, path + 8); 946 return S_OK; 947 } 948 else if (is_prefixed_disk(path)) 949 { 950 /* \\?\C:\ -> C:\ */ 951 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG; 952 lstrcpyW(path, path + 4); 953 return S_OK; 954 } 955 else 956 return S_FALSE; 957} 958 959HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size) 960{ 961 const WCHAR *root_end; 962 WCHAR *segment_end; 963 964 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 965 966 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 967 968 if (PathCchSkipRoot(path, &root_end) == S_OK) 969 { 970 if (root_end && root_end > path && root_end[-1] == '\\' 971 && ((is_prefixed_unc(path) && path[8]) || (path[0] == '\\' && path[1] == '\\' && path[2] && path[2] != '?'))) 972 root_end--; 973 if (root_end - path >= size) return E_INVALIDARG; 974 975 segment_end = path + (root_end - path); 976 if (!*segment_end) return S_FALSE; 977 978 *segment_end = 0; 979 return S_OK; 980 } 981 else 982 { 983 *path = 0; 984 return E_INVALIDARG; 985 } 986} 987 988BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server) 989{ 990 const WCHAR *result = NULL; 991 992 TRACE("%s %p\n", wine_dbgstr_w(path), server); 993 994 if (is_prefixed_unc(path)) 995 result = path + 8; 996 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') 997 result = path + 2; 998 999 if (server) *server = result; 1000 return !!result; 1001} 1002 1003#ifndef STATIC_PATHCCH 1004/* Other functions not part of the PathCch library */ 1005 1006BOOL WINAPI PathIsUNCA(const char *path) 1007{ 1008 TRACE("%s\n", wine_dbgstr_a(path)); 1009 1010 return path && (path[0] == '\\') && (path[1] == '\\'); 1011} 1012 1013BOOL WINAPI PathIsUNCW(const WCHAR *path) 1014{ 1015 TRACE("%s\n", wine_dbgstr_w(path)); 1016 1017 return path && (path[0] == '\\') && (path[1] == '\\'); 1018} 1019 1020BOOL WINAPI PathIsRelativeA(const char *path) 1021{ 1022 TRACE("%s\n", wine_dbgstr_a(path)); 1023 1024 if (!path || !*path || IsDBCSLeadByte(*path)) 1025 return TRUE; 1026 1027 return !(*path == '\\' || (*path && path[1] == ':')); 1028} 1029 1030BOOL WINAPI PathIsRelativeW(const WCHAR *path) 1031{ 1032 TRACE("%s\n", wine_dbgstr_w(path)); 1033 1034 if (!path || !*path) 1035 return TRUE; 1036 1037 return !(*path == '\\' || (*path && path[1] == ':')); 1038} 1039 1040BOOL WINAPI PathIsUNCServerShareA(const char *path) 1041{ 1042 BOOL seen_slash = FALSE; 1043 1044 TRACE("%s\n", wine_dbgstr_a(path)); 1045 1046 if (path && *path++ == '\\' && *path++ == '\\') 1047 { 1048 while (*path) 1049 { 1050 if (*path == '\\') 1051 { 1052 if (seen_slash) 1053 return FALSE; 1054 seen_slash = TRUE; 1055 } 1056 1057 path = CharNextA(path); 1058 } 1059 } 1060 1061 return seen_slash; 1062} 1063 1064BOOL WINAPI PathIsUNCServerShareW(const WCHAR *path) 1065{ 1066 BOOL seen_slash = FALSE; 1067 1068 TRACE("%s\n", wine_dbgstr_w(path)); 1069 1070 if (path && *path++ == '\\' && *path++ == '\\') 1071 { 1072 while (*path) 1073 { 1074 if (*path == '\\') 1075 { 1076 if (seen_slash) 1077 return FALSE; 1078 seen_slash = TRUE; 1079 } 1080 1081 path++; 1082 } 1083 } 1084 1085 return seen_slash; 1086} 1087 1088BOOL WINAPI PathIsRootA(const char *path) 1089{ 1090 WCHAR pathW[MAX_PATH]; 1091 1092 TRACE("%s\n", wine_dbgstr_a(path)); 1093 1094 if (!MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, MAX_PATH)) 1095 return FALSE; 1096 if (is_prefixed_unc(pathW) || is_prefixed_disk(pathW) || is_prefixed_volume(pathW)) return FALSE; 1097 1098 return PathIsRootW(pathW); 1099} 1100 1101BOOL WINAPI PathIsRootW(const WCHAR *path) 1102{ 1103 TRACE("%s\n", wine_dbgstr_w(path)); 1104 1105 return PathCchIsRoot(path); 1106} 1107 1108BOOL WINAPI PathRemoveFileSpecA(char *path) 1109{ 1110 char *root_end = NULL, *ptr; 1111 1112 TRACE("%s\n", debugstr_a(path)); 1113 1114 if (!path || !*path) 1115 return FALSE; 1116 1117 if (is_drive_specA(path)) 1118 { 1119 root_end = path + 2; 1120 if (*root_end == '\\') ++root_end; 1121 } 1122 else 1123 { 1124 root_end = path; 1125 if (*root_end == '\\') ++root_end; 1126 if (root_end[1] != '?') 1127 { 1128 if (*root_end == '\\') ++root_end; 1129 if (root_end - path > 1 && is_drive_specA(root_end)) root_end += 2; 1130 if (*root_end == '\\' && root_end[1] && root_end[1] != '\\') ++root_end; 1131 } 1132 } 1133 ptr = StrRChrA(root_end, NULL, '\\'); 1134 if (ptr && ptr != root_end) 1135 { 1136 if (ptr[-1] == '\\') --ptr; 1137 *ptr = 0; 1138 return TRUE; 1139 } 1140 if (!*root_end) return FALSE; 1141 *root_end = 0; 1142 return TRUE; 1143} 1144 1145BOOL WINAPI PathRemoveFileSpecW(WCHAR *path) 1146{ 1147 WCHAR *root_end = NULL, *ptr; 1148 1149 TRACE("%s\n", debugstr_w(path)); 1150 1151 if (!path || !*path) 1152 return FALSE; 1153 1154 if (is_prefixed_volume(path)) root_end = path + 48; 1155 else if (is_prefixed_disk(path)) root_end = path + 6; 1156 else if (is_drive_spec(path)) root_end = path + 2; 1157 if (!root_end) 1158 { 1159 root_end = path; 1160 if (*root_end == '\\') ++root_end; 1161 if (root_end[1] != '?') 1162 { 1163 if (*root_end == '\\') ++root_end; 1164 if (root_end - path > 1 && is_drive_spec(root_end)) root_end += 2; 1165 if (*root_end == '\\' && root_end[1] && root_end[1] != '\\') ++root_end; 1166 } 1167 } 1168 else if (*root_end == '\\') ++root_end; 1169 ptr = StrRChrW(root_end, NULL, '\\'); 1170 if (ptr && ptr != root_end) 1171 { 1172 if (ptr[-1] == '\\') --ptr; 1173 *ptr = 0; 1174 return TRUE; 1175 } 1176 if (!*root_end) return FALSE; 1177 *root_end = 0; 1178 return TRUE; 1179} 1180 1181BOOL WINAPI PathStripToRootA(char *path) 1182{ 1183 WCHAR pathW[MAX_PATH]; 1184 1185 TRACE("%s\n", wine_dbgstr_a(path)); 1186 1187 if (!MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, MAX_PATH)) return FALSE; 1188 1189 *path = 0; 1190 if (is_prefixed_unc(pathW) || is_prefixed_disk(pathW) || is_prefixed_volume(pathW)) return FALSE; 1191 if (!PathStripToRootW(pathW)) return FALSE; 1192 return !!WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0); 1193} 1194 1195BOOL WINAPI PathStripToRootW(WCHAR *path) 1196{ 1197 TRACE("%s\n", wine_dbgstr_w(path)); 1198 1199 return SUCCEEDED(PathCchStripToRoot(path, PATHCCH_MAX_CCH)); 1200} 1201 1202LPSTR WINAPI PathAddBackslashA(char *path) 1203{ 1204 unsigned int len; 1205 char *prev = path; 1206 1207 TRACE("%s\n", wine_dbgstr_a(path)); 1208 1209 if (!path || (len = strlen(path)) >= MAX_PATH) 1210 return NULL; 1211 1212 if (len) 1213 { 1214 do 1215 { 1216 path = CharNextA(prev); 1217 if (*path) 1218 prev = path; 1219 } while (*path); 1220 1221 if (*prev != '\\') 1222 { 1223 *path++ = '\\'; 1224 *path = '\0'; 1225 } 1226 } 1227 1228 return path; 1229} 1230 1231LPWSTR WINAPI PathAddBackslashW(WCHAR *path) 1232{ 1233 unsigned int len; 1234 1235 TRACE("%s\n", wine_dbgstr_w(path)); 1236 1237 if (!path || (len = lstrlenW(path)) >= MAX_PATH) 1238 return NULL; 1239 1240 if (len) 1241 { 1242 path += len; 1243 if (path[-1] != '\\') 1244 { 1245 *path++ = '\\'; 1246 *path = '\0'; 1247 } 1248 } 1249 1250 return path; 1251} 1252 1253LPSTR WINAPI PathFindExtensionA(const char *path) 1254{ 1255 const char *lastpoint = NULL; 1256 1257 TRACE("%s\n", wine_dbgstr_a(path)); 1258 1259 if (path) 1260 { 1261 while (*path) 1262 { 1263 if (*path == '\\' || *path == ' ') 1264 lastpoint = NULL; 1265 else if (*path == '.') 1266 lastpoint = path; 1267 path = CharNextA(path); 1268 } 1269 } 1270 1271 return (LPSTR)(lastpoint ? lastpoint : path); 1272} 1273 1274LPWSTR WINAPI PathFindExtensionW(const WCHAR *path) 1275{ 1276 const WCHAR *lastpoint = NULL; 1277 1278 TRACE("%s\n", wine_dbgstr_w(path)); 1279 1280 if (path) 1281 { 1282 while (*path) 1283 { 1284 if (*path == '\\' || *path == ' ') 1285 lastpoint = NULL; 1286 else if (*path == '.') 1287 lastpoint = path; 1288 path++; 1289 } 1290 } 1291 1292 return (LPWSTR)(lastpoint ? lastpoint : path); 1293} 1294 1295BOOL WINAPI PathAddExtensionA(char *path, const char *ext) 1296{ 1297 unsigned int len; 1298 1299 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext)); 1300 1301 if (!path || !ext || *(PathFindExtensionA(path))) 1302 return FALSE; 1303 1304 len = strlen(path); 1305 if (len + strlen(ext) >= MAX_PATH) 1306 return FALSE; 1307 1308 strcpy(path + len, ext); 1309 return TRUE; 1310} 1311 1312BOOL WINAPI PathAddExtensionW(WCHAR *path, const WCHAR *ext) 1313{ 1314 unsigned int len; 1315 1316 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext)); 1317 1318 if (!path || !ext || *(PathFindExtensionW(path))) 1319 return FALSE; 1320 1321 len = lstrlenW(path); 1322 if (len + lstrlenW(ext) >= MAX_PATH) 1323 return FALSE; 1324 1325 lstrcpyW(path + len, ext); 1326 return TRUE; 1327} 1328 1329BOOL WINAPI PathCanonicalizeW(WCHAR *buffer, const WCHAR *path) 1330{ 1331 const WCHAR *src = path; 1332 WCHAR *dst = buffer; 1333 1334 TRACE("%p, %s\n", buffer, wine_dbgstr_w(path)); 1335 1336 if (dst) 1337 *dst = '\0'; 1338 1339 if (!dst || !path) 1340 { 1341 SetLastError(ERROR_INVALID_PARAMETER); 1342 return FALSE; 1343 } 1344 1345 if (!*path) 1346 { 1347 *buffer++ = '\\'; 1348 *buffer = '\0'; 1349 return TRUE; 1350 } 1351 1352 /* Copy path root */ 1353 if (*src == '\\') 1354 { 1355 *dst++ = *src++; 1356 } 1357 else if (*src && src[1] == ':') 1358 { 1359 /* X:\ */ 1360 *dst++ = *src++; 1361 *dst++ = *src++; 1362 if (*src == '\\') 1363 *dst++ = *src++; 1364 } 1365 1366 /* Canonicalize the rest of the path */ 1367 while (*src) 1368 { 1369 if (*src == '.') 1370 { 1371 if (src[1] == '\\' && (src == path || src[-1] == '\\' || src[-1] == ':')) 1372 { 1373 src += 2; /* Skip .\ */ 1374 } 1375 else if (src[1] == '.' && dst != buffer && dst[-1] == '\\') 1376 { 1377 /* \.. backs up a directory, over the root if it has no \ following X:. 1378 * .. is ignored if it would remove a UNC server name or initial \\ 1379 */ 1380 if (dst != buffer) 1381 { 1382 *dst = '\0'; /* Allow PathIsUNCServerShareA test on lpszBuf */ 1383 if (dst > buffer + 1 && dst[-1] == '\\' && (dst[-2] != '\\' || dst > buffer + 2)) 1384 { 1385 if (dst[-2] == ':' && (dst > buffer + 3 || dst[-3] == ':')) 1386 { 1387 dst -= 2; 1388 while (dst > buffer && *dst != '\\') 1389 dst--; 1390 if (*dst == '\\') 1391 dst++; /* Reset to last '\' */ 1392 else 1393 dst = buffer; /* Start path again from new root */ 1394 } 1395 else if (dst[-2] != ':' && !PathIsUNCServerShareW(buffer)) 1396 dst -= 2; 1397 } 1398 while (dst > buffer && *dst != '\\') 1399 dst--; 1400 if (dst == buffer) 1401 { 1402 *dst++ = '\\'; 1403 src++; 1404 } 1405 } 1406 src += 2; /* Skip .. in src path */ 1407 } 1408 else 1409 *dst++ = *src++; 1410 } 1411 else 1412 *dst++ = *src++; 1413 } 1414 1415 /* Append \ to naked drive specs */ 1416 if (dst - buffer == 2 && dst[-1] == ':') 1417 *dst++ = '\\'; 1418 *dst++ = '\0'; 1419 return TRUE; 1420} 1421 1422BOOL WINAPI PathCanonicalizeA(char *buffer, const char *path) 1423{ 1424 WCHAR pathW[MAX_PATH], bufferW[MAX_PATH]; 1425 BOOL ret; 1426 int len; 1427 1428 TRACE("%p, %s\n", buffer, wine_dbgstr_a(path)); 1429 1430 if (buffer) 1431 *buffer = '\0'; 1432 1433 if (!buffer || !path) 1434 { 1435 SetLastError(ERROR_INVALID_PARAMETER); 1436 return FALSE; 1437 } 1438 1439 len = MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, ARRAY_SIZE(pathW)); 1440 if (!len) 1441 return FALSE; 1442 1443 ret = PathCanonicalizeW(bufferW, pathW); 1444 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, 0, 0); 1445 1446 return ret; 1447} 1448 1449WCHAR * WINAPI PathCombineW(WCHAR *dst, const WCHAR *dir, const WCHAR *file) 1450{ 1451 BOOL use_both = FALSE, strip = FALSE; 1452 WCHAR tmp[MAX_PATH]; 1453 1454 TRACE("%p, %s, %s\n", dst, wine_dbgstr_w(dir), wine_dbgstr_w(file)); 1455 1456 /* Invalid parameters */ 1457 if (!dst) 1458 return NULL; 1459 1460 if (!dir && !file) 1461 { 1462 dst[0] = 0; 1463 return NULL; 1464 } 1465 1466 if ((!file || !*file) && dir) 1467 { 1468 /* Use dir only */ 1469 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp)); 1470 } 1471 else if (!dir || !*dir || !PathIsRelativeW(file)) 1472 { 1473 if (!dir || !*dir || *file != '\\' || PathIsUNCW(file)) 1474 { 1475 /* Use file only */ 1476 lstrcpynW(tmp, file, ARRAY_SIZE(tmp)); 1477 } 1478 else 1479 { 1480 use_both = TRUE; 1481 strip = TRUE; 1482 } 1483 } 1484 else 1485 use_both = TRUE; 1486 1487 if (use_both) 1488 { 1489 lstrcpynW(tmp, dir, ARRAY_SIZE(tmp)); 1490 if (strip) 1491 { 1492 PathStripToRootW(tmp); 1493 file++; /* Skip '\' */ 1494 } 1495 1496 if (!PathAddBackslashW(tmp) || lstrlenW(tmp) + lstrlenW(file) >= MAX_PATH) 1497 { 1498 dst[0] = 0; 1499 return NULL; 1500 } 1501 1502 lstrcatW(tmp, file); 1503 } 1504 1505 PathCanonicalizeW(dst, tmp); 1506 return dst; 1507} 1508 1509LPSTR WINAPI PathCombineA(char *dst, const char *dir, const char *file) 1510{ 1511 WCHAR dstW[MAX_PATH], dirW[MAX_PATH], fileW[MAX_PATH]; 1512 1513 TRACE("%p, %s, %s\n", dst, wine_dbgstr_a(dir), wine_dbgstr_a(file)); 1514 1515 /* Invalid parameters */ 1516 if (!dst) 1517 return NULL; 1518 1519 if (!dir && !file) 1520 goto fail; 1521 1522 if (dir && !MultiByteToWideChar(CP_ACP, 0, dir, -1, dirW, ARRAY_SIZE(dirW))) 1523 goto fail; 1524 1525 if (file && !MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW))) 1526 goto fail; 1527 1528 if (PathCombineW(dstW, dir ? dirW : NULL, file ? fileW : NULL)) 1529 if (WideCharToMultiByte(CP_ACP, 0, dstW, -1, dst, MAX_PATH, 0, 0)) 1530 return dst; 1531fail: 1532 dst[0] = 0; 1533 return NULL; 1534} 1535 1536BOOL WINAPI PathAppendA(char *path, const char *append) 1537{ 1538 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(append)); 1539 1540 if (path && append) 1541 { 1542 if (!PathIsUNCA(append)) 1543 while (*append == '\\') 1544 append++; 1545 1546 if (PathCombineA(path, path, append)) 1547 return TRUE; 1548 } 1549 1550 return FALSE; 1551} 1552 1553BOOL WINAPI PathAppendW(WCHAR *path, const WCHAR *append) 1554{ 1555 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(append)); 1556 1557 if (path && append) 1558 { 1559 if (!PathIsUNCW(append)) 1560 while (*append == '\\') 1561 append++; 1562 1563 if (PathCombineW(path, path, append)) 1564 return TRUE; 1565 } 1566 1567 return FALSE; 1568} 1569 1570int WINAPI PathCommonPrefixA(const char *file1, const char *file2, char *path) 1571{ 1572 const char *iter1 = file1; 1573 const char *iter2 = file2; 1574 unsigned int len = 0; 1575 1576 TRACE("%s, %s, %p.\n", wine_dbgstr_a(file1), wine_dbgstr_a(file2), path); 1577 1578 if (path) 1579 *path = '\0'; 1580 1581 if (!file1 || !file2) 1582 return 0; 1583 1584 /* Handle roots first */ 1585 if (PathIsUNCA(file1)) 1586 { 1587 if (!PathIsUNCA(file2)) 1588 return 0; 1589 iter1 += 2; 1590 iter2 += 2; 1591 } 1592 else if (PathIsUNCA(file2)) 1593 return 0; 1594 1595 for (;;) 1596 { 1597 /* Update len */ 1598 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\')) 1599 len = iter1 - file1; /* Common to this point */ 1600 1601 if (!*iter1 || (tolower(*iter1) != tolower(*iter2))) 1602 break; /* Strings differ at this point */ 1603 1604 iter1++; 1605 iter2++; 1606 } 1607 1608 if (len == 2) 1609 len++; /* Feature/Bug compatible with Win32 */ 1610 1611 if (len && path) 1612 { 1613 memcpy(path, file1, len); 1614 path[len] = '\0'; 1615 } 1616 1617 return len; 1618} 1619 1620int WINAPI PathCommonPrefixW(const WCHAR *file1, const WCHAR *file2, WCHAR *path) 1621{ 1622 const WCHAR *iter1 = file1; 1623 const WCHAR *iter2 = file2; 1624 unsigned int len = 0; 1625 1626 TRACE("%s, %s, %p\n", wine_dbgstr_w(file1), wine_dbgstr_w(file2), path); 1627 1628 if (path) 1629 *path = '\0'; 1630 1631 if (!file1 || !file2) 1632 return 0; 1633 1634 /* Handle roots first */ 1635 if (PathIsUNCW(file1)) 1636 { 1637 if (!PathIsUNCW(file2)) 1638 return 0; 1639 iter1 += 2; 1640 iter2 += 2; 1641 } 1642 else if (PathIsUNCW(file2)) 1643 return 0; 1644 1645 for (;;) 1646 { 1647 /* Update len */ 1648 if ((!*iter1 || *iter1 == '\\') && (!*iter2 || *iter2 == '\\')) 1649 len = iter1 - file1; /* Common to this point */ 1650 1651 if (!*iter1 || (towupper(*iter1) != towupper(*iter2))) 1652 break; /* Strings differ at this point */ 1653 1654 iter1++; 1655 iter2++; 1656 } 1657 1658 if (len == 2) 1659 len++; /* Feature/Bug compatible with Win32 */ 1660 1661 if (len && path) 1662 { 1663 memcpy(path, file1, len * sizeof(WCHAR)); 1664 path[len] = '\0'; 1665 } 1666 1667 return len; 1668} 1669 1670BOOL WINAPI PathIsPrefixA(const char *prefix, const char *path) 1671{ 1672 TRACE("%s, %s\n", wine_dbgstr_a(prefix), wine_dbgstr_a(path)); 1673 1674 return prefix && path && PathCommonPrefixA(path, prefix, NULL) == (int)strlen(prefix); 1675} 1676 1677BOOL WINAPI PathIsPrefixW(const WCHAR *prefix, const WCHAR *path) 1678{ 1679 TRACE("%s, %s\n", wine_dbgstr_w(prefix), wine_dbgstr_w(path)); 1680 1681 return prefix && path && PathCommonPrefixW(path, prefix, NULL) == (int)lstrlenW(prefix); 1682} 1683 1684char * WINAPI PathFindFileNameA(const char *path) 1685{ 1686 const char *last_slash = path; 1687 1688 TRACE("%s\n", wine_dbgstr_a(path)); 1689 1690 while (path && *path) 1691 { 1692 if ((*path == '\\' || *path == '/' || *path == ':') && 1693 path[1] && path[1] != '\\' && path[1] != '/') 1694 last_slash = path + 1; 1695 path = CharNextA(path); 1696 } 1697 1698 return (char *)last_slash; 1699} 1700 1701WCHAR * WINAPI PathFindFileNameW(const WCHAR *path) 1702{ 1703 const WCHAR *last_slash = path; 1704 1705 TRACE("%s\n", wine_dbgstr_w(path)); 1706 1707 while (path && *path) 1708 { 1709 if ((*path == '\\' || *path == '/' || *path == ':') && 1710 path[1] && path[1] != '\\' && path[1] != '/') 1711 last_slash = path + 1; 1712 path++; 1713 } 1714 1715 return (WCHAR *)last_slash; 1716} 1717 1718char * WINAPI PathGetArgsA(const char *path) 1719{ 1720 BOOL seen_quote = FALSE; 1721 1722 TRACE("%s\n", wine_dbgstr_a(path)); 1723 1724 if (!path) 1725 return NULL; 1726 1727 while (*path) 1728 { 1729 if (*path == ' ' && !seen_quote) 1730 return (char *)path + 1; 1731 1732 if (*path == '"') 1733 seen_quote = !seen_quote; 1734 path = CharNextA(path); 1735 } 1736 1737 return (char *)path; 1738} 1739 1740WCHAR * WINAPI PathGetArgsW(const WCHAR *path) 1741{ 1742 BOOL seen_quote = FALSE; 1743 1744 TRACE("%s\n", wine_dbgstr_w(path)); 1745 1746 if (!path) 1747 return NULL; 1748 1749 while (*path) 1750 { 1751 if (*path == ' ' && !seen_quote) 1752 return (WCHAR *)path + 1; 1753 1754 if (*path == '"') 1755 seen_quote = !seen_quote; 1756 path++; 1757 } 1758 1759 return (WCHAR *)path; 1760} 1761 1762UINT WINAPI PathGetCharTypeW(WCHAR ch) 1763{ 1764 UINT flags = 0; 1765 1766 TRACE("%#x\n", ch); 1767 1768 if (!ch || ch < ' ' || ch == '<' || ch == '>' || ch == '"' || ch == '|' || ch == '/') 1769 flags = GCT_INVALID; /* Invalid */ 1770 else if (ch == '*' || ch == '?') 1771 flags = GCT_WILD; /* Wildchars */ 1772 else if (ch == '\\' || ch == ':') 1773 return GCT_SEPARATOR; /* Path separators */ 1774 else 1775 { 1776 if (ch < 126) 1777 { 1778 if (((ch & 0x1) && ch != ';') || !ch || iswalnum(ch) || ch == '$' || ch == '&' || ch == '(' || 1779 ch == '.' || ch == '@' || ch == '^' || ch == '\'' || ch == '`') 1780 { 1781 flags |= GCT_SHORTCHAR; /* All these are valid for DOS */ 1782 } 1783 } 1784 else 1785 flags |= GCT_SHORTCHAR; /* Bug compatible with win32 */ 1786 1787 flags |= GCT_LFNCHAR; /* Valid for long file names */ 1788 } 1789 1790 return flags; 1791} 1792 1793UINT WINAPI PathGetCharTypeA(UCHAR ch) 1794{ 1795 return PathGetCharTypeW(ch); 1796} 1797 1798int WINAPI PathGetDriveNumberA(const char *path) 1799{ 1800 TRACE("%s\n", wine_dbgstr_a(path)); 1801 1802 if (path && *path && path[1] == ':') 1803 { 1804 if (*path >= 'a' && *path <= 'z') return *path - 'a'; 1805 if (*path >= 'A' && *path <= 'Z') return *path - 'A'; 1806 } 1807 return -1; 1808} 1809 1810int WINAPI PathGetDriveNumberW(const WCHAR *path) 1811{ 1812 TRACE("%s\n", wine_dbgstr_w(path)); 1813 1814 if (!path) 1815 return -1; 1816 1817 if (!wcsncmp(path, L"\\\\?\\", 4)) path += 4; 1818 1819 if (!path[0] || path[1] != ':') return -1; 1820 if (path[0] >= 'A' && path[0] <= 'Z') return path[0] - 'A'; 1821 if (path[0] >= 'a' && path[0] <= 'z') return path[0] - 'a'; 1822 return -1; 1823} 1824 1825BOOL WINAPI PathIsFileSpecA(const char *path) 1826{ 1827 TRACE("%s\n", wine_dbgstr_a(path)); 1828 1829 if (!path) 1830 return FALSE; 1831 1832 while (*path) 1833 { 1834 if (*path == '\\' || *path == ':') 1835 return FALSE; 1836 path = CharNextA(path); 1837 } 1838 1839 return TRUE; 1840} 1841 1842BOOL WINAPI PathIsFileSpecW(const WCHAR *path) 1843{ 1844 TRACE("%s\n", wine_dbgstr_w(path)); 1845 1846 if (!path) 1847 return FALSE; 1848 1849 while (*path) 1850 { 1851 if (*path == '\\' || *path == ':') 1852 return FALSE; 1853 path++; 1854 } 1855 1856 return TRUE; 1857} 1858 1859BOOL WINAPI PathIsUNCServerA(const char *path) 1860{ 1861 TRACE("%s\n", wine_dbgstr_a(path)); 1862 1863 if (!(path && path[0] == '\\' && path[1] == '\\')) 1864 return FALSE; 1865 1866 while (*path) 1867 { 1868 if (*path == '\\') 1869 return FALSE; 1870 path = CharNextA(path); 1871 } 1872 1873 return TRUE; 1874} 1875 1876BOOL WINAPI PathIsUNCServerW(const WCHAR *path) 1877{ 1878 TRACE("%s\n", wine_dbgstr_w(path)); 1879 1880 if (!(path && path[0] == '\\' && path[1] == '\\')) 1881 return FALSE; 1882 1883 return !wcschr(path + 2, '\\'); 1884} 1885 1886void WINAPI PathRemoveBlanksA(char *path) 1887{ 1888 char *start, *first; 1889 1890 TRACE("%s\n", wine_dbgstr_a(path)); 1891 1892 if (!path || !*path) 1893 return; 1894 1895 start = first = path; 1896 1897 while (*path == ' ') 1898 path = CharNextA(path); 1899 1900 while (*path) 1901 *start++ = *path++; 1902 1903 if (start != first) 1904 while (start[-1] == ' ') 1905 start--; 1906 1907 *start = '\0'; 1908} 1909 1910void WINAPI PathRemoveBlanksW(WCHAR *path) 1911{ 1912 WCHAR *start, *first; 1913 1914 TRACE("%s\n", wine_dbgstr_w(path)); 1915 1916 if (!path || !*path) 1917 return; 1918 1919 start = first = path; 1920 1921 while (*path == ' ') 1922 path++; 1923 1924 while (*path) 1925 *start++ = *path++; 1926 1927 if (start != first) 1928 while (start[-1] == ' ') 1929 start--; 1930 1931 *start = '\0'; 1932} 1933 1934void WINAPI PathRemoveExtensionA(char *path) 1935{ 1936 TRACE("%s\n", wine_dbgstr_a(path)); 1937 1938 if (!path) 1939 return; 1940 1941 path = PathFindExtensionA(path); 1942 if (path && *path) 1943 *path = '\0'; 1944} 1945 1946void WINAPI PathRemoveExtensionW(WCHAR *path) 1947{ 1948 TRACE("%s\n", wine_dbgstr_w(path)); 1949 1950 if (!path) 1951 return; 1952 1953 path = PathFindExtensionW(path); 1954 if (path && *path) 1955 *path = '\0'; 1956} 1957 1958BOOL WINAPI PathRenameExtensionA(char *path, const char *ext) 1959{ 1960 char *extension; 1961 1962 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(ext)); 1963 1964 extension = PathFindExtensionA(path); 1965 1966 if (!extension || (extension - path + strlen(ext) >= MAX_PATH)) 1967 return FALSE; 1968 1969 strcpy(extension, ext); 1970 return TRUE; 1971} 1972 1973BOOL WINAPI PathRenameExtensionW(WCHAR *path, const WCHAR *ext) 1974{ 1975 WCHAR *extension; 1976 1977 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(ext)); 1978 1979 extension = PathFindExtensionW(path); 1980 1981 if (!extension || (extension - path + lstrlenW(ext) >= MAX_PATH)) 1982 return FALSE; 1983 1984 lstrcpyW(extension, ext); 1985 return TRUE; 1986} 1987 1988void WINAPI PathUnquoteSpacesA(char *path) 1989{ 1990 unsigned int len; 1991 1992 TRACE("%s\n", wine_dbgstr_a(path)); 1993 1994 if (!path || *path != '"') 1995 return; 1996 1997 len = strlen(path) - 1; 1998 if (path[len] == '"') 1999 { 2000 path[len] = '\0'; 2001 for (; *path; path++) 2002 *path = path[1]; 2003 } 2004} 2005 2006void WINAPI PathUnquoteSpacesW(WCHAR *path) 2007{ 2008 unsigned int len; 2009 2010 TRACE("%s\n", wine_dbgstr_w(path)); 2011 2012 if (!path || *path != '"') 2013 return; 2014 2015 len = lstrlenW(path) - 1; 2016 if (path[len] == '"') 2017 { 2018 path[len] = '\0'; 2019 for (; *path; path++) 2020 *path = path[1]; 2021 } 2022} 2023 2024char * WINAPI PathRemoveBackslashA(char *path) 2025{ 2026 char *ptr; 2027 2028 TRACE("%s\n", wine_dbgstr_a(path)); 2029 2030 if (!path) 2031 return NULL; 2032 2033 ptr = CharPrevA(path, path + strlen(path)); 2034 if (!PathIsRootA(path) && *ptr == '\\') 2035 *ptr = '\0'; 2036 2037 return ptr; 2038} 2039 2040WCHAR * WINAPI PathRemoveBackslashW(WCHAR *path) 2041{ 2042 WCHAR *ptr; 2043 2044 TRACE("%s\n", wine_dbgstr_w(path)); 2045 2046 if (!path) 2047 return NULL; 2048 2049 ptr = path + lstrlenW(path); 2050 if (ptr > path) ptr--; 2051 if (!PathIsRootW(path) && *ptr == '\\') 2052 *ptr = '\0'; 2053 2054 return ptr; 2055} 2056 2057BOOL WINAPI PathIsLFNFileSpecA(const char *path) 2058{ 2059 unsigned int name_len = 0, ext_len = 0; 2060 2061 TRACE("%s\n", wine_dbgstr_a(path)); 2062 2063 if (!path) 2064 return FALSE; 2065 2066 while (*path) 2067 { 2068 if (*path == ' ') 2069 return TRUE; /* DOS names cannot have spaces */ 2070 if (*path == '.') 2071 { 2072 if (ext_len) 2073 return TRUE; /* DOS names have only one dot */ 2074 ext_len = 1; 2075 } 2076 else if (ext_len) 2077 { 2078 ext_len++; 2079 if (ext_len > 4) 2080 return TRUE; /* DOS extensions are <= 3 chars*/ 2081 } 2082 else 2083 { 2084 name_len++; 2085 if (name_len > 8) 2086 return TRUE; /* DOS names are <= 8 chars */ 2087 } 2088 path = CharNextA(path); 2089 } 2090 2091 return FALSE; /* Valid DOS path */ 2092} 2093 2094BOOL WINAPI PathIsLFNFileSpecW(const WCHAR *path) 2095{ 2096 unsigned int name_len = 0, ext_len = 0; 2097 2098 TRACE("%s\n", wine_dbgstr_w(path)); 2099 2100 if (!path) 2101 return FALSE; 2102 2103 while (*path) 2104 { 2105 if (*path == ' ') 2106 return TRUE; /* DOS names cannot have spaces */ 2107 if (*path == '.') 2108 { 2109 if (ext_len) 2110 return TRUE; /* DOS names have only one dot */ 2111 ext_len = 1; 2112 } 2113 else if (ext_len) 2114 { 2115 ext_len++; 2116 if (ext_len > 4) 2117 return TRUE; /* DOS extensions are <= 3 chars*/ 2118 } 2119 else 2120 { 2121 name_len++; 2122 if (name_len > 8) 2123 return TRUE; /* DOS names are <= 8 chars */ 2124 } 2125 path++; 2126 } 2127 2128 return FALSE; /* Valid DOS path */ 2129} 2130 2131#define PATH_CHAR_CLASS_LETTER 0x00000001 2132#define PATH_CHAR_CLASS_ASTERIX 0x00000002 2133#define PATH_CHAR_CLASS_DOT 0x00000004 2134#define PATH_CHAR_CLASS_BACKSLASH 0x00000008 2135#define PATH_CHAR_CLASS_COLON 0x00000010 2136#define PATH_CHAR_CLASS_SEMICOLON 0x00000020 2137#define PATH_CHAR_CLASS_COMMA 0x00000040 2138#define PATH_CHAR_CLASS_SPACE 0x00000080 2139#define PATH_CHAR_CLASS_OTHER_VALID 0x00000100 2140#define PATH_CHAR_CLASS_DOUBLEQUOTE 0x00000200 2141 2142#define PATH_CHAR_CLASS_INVALID 0x00000000 2143#define PATH_CHAR_CLASS_ANY 0xffffffff 2144 2145static const DWORD path_charclass[] = 2146{ 2147 /* 0x00 */ PATH_CHAR_CLASS_INVALID, /* 0x01 */ PATH_CHAR_CLASS_INVALID, 2148 /* 0x02 */ PATH_CHAR_CLASS_INVALID, /* 0x03 */ PATH_CHAR_CLASS_INVALID, 2149 /* 0x04 */ PATH_CHAR_CLASS_INVALID, /* 0x05 */ PATH_CHAR_CLASS_INVALID, 2150 /* 0x06 */ PATH_CHAR_CLASS_INVALID, /* 0x07 */ PATH_CHAR_CLASS_INVALID, 2151 /* 0x08 */ PATH_CHAR_CLASS_INVALID, /* 0x09 */ PATH_CHAR_CLASS_INVALID, 2152 /* 0x0a */ PATH_CHAR_CLASS_INVALID, /* 0x0b */ PATH_CHAR_CLASS_INVALID, 2153 /* 0x0c */ PATH_CHAR_CLASS_INVALID, /* 0x0d */ PATH_CHAR_CLASS_INVALID, 2154 /* 0x0e */ PATH_CHAR_CLASS_INVALID, /* 0x0f */ PATH_CHAR_CLASS_INVALID, 2155 /* 0x10 */ PATH_CHAR_CLASS_INVALID, /* 0x11 */ PATH_CHAR_CLASS_INVALID, 2156 /* 0x12 */ PATH_CHAR_CLASS_INVALID, /* 0x13 */ PATH_CHAR_CLASS_INVALID, 2157 /* 0x14 */ PATH_CHAR_CLASS_INVALID, /* 0x15 */ PATH_CHAR_CLASS_INVALID, 2158 /* 0x16 */ PATH_CHAR_CLASS_INVALID, /* 0x17 */ PATH_CHAR_CLASS_INVALID, 2159 /* 0x18 */ PATH_CHAR_CLASS_INVALID, /* 0x19 */ PATH_CHAR_CLASS_INVALID, 2160 /* 0x1a */ PATH_CHAR_CLASS_INVALID, /* 0x1b */ PATH_CHAR_CLASS_INVALID, 2161 /* 0x1c */ PATH_CHAR_CLASS_INVALID, /* 0x1d */ PATH_CHAR_CLASS_INVALID, 2162 /* 0x1e */ PATH_CHAR_CLASS_INVALID, /* 0x1f */ PATH_CHAR_CLASS_INVALID, 2163 /* ' ' */ PATH_CHAR_CLASS_SPACE, /* '!' */ PATH_CHAR_CLASS_OTHER_VALID, 2164 /* '"' */ PATH_CHAR_CLASS_DOUBLEQUOTE, /* '#' */ PATH_CHAR_CLASS_OTHER_VALID, 2165 /* '$' */ PATH_CHAR_CLASS_OTHER_VALID, /* '%' */ PATH_CHAR_CLASS_OTHER_VALID, 2166 /* '&' */ PATH_CHAR_CLASS_OTHER_VALID, /* '\'' */ PATH_CHAR_CLASS_OTHER_VALID, 2167 /* '(' */ PATH_CHAR_CLASS_OTHER_VALID, /* ')' */ PATH_CHAR_CLASS_OTHER_VALID, 2168 /* '*' */ PATH_CHAR_CLASS_ASTERIX, /* '+' */ PATH_CHAR_CLASS_OTHER_VALID, 2169 /* ',' */ PATH_CHAR_CLASS_COMMA, /* '-' */ PATH_CHAR_CLASS_OTHER_VALID, 2170 /* '.' */ PATH_CHAR_CLASS_DOT, /* '/' */ PATH_CHAR_CLASS_INVALID, 2171 /* '0' */ PATH_CHAR_CLASS_OTHER_VALID, /* '1' */ PATH_CHAR_CLASS_OTHER_VALID, 2172 /* '2' */ PATH_CHAR_CLASS_OTHER_VALID, /* '3' */ PATH_CHAR_CLASS_OTHER_VALID, 2173 /* '4' */ PATH_CHAR_CLASS_OTHER_VALID, /* '5' */ PATH_CHAR_CLASS_OTHER_VALID, 2174 /* '6' */ PATH_CHAR_CLASS_OTHER_VALID, /* '7' */ PATH_CHAR_CLASS_OTHER_VALID, 2175 /* '8' */ PATH_CHAR_CLASS_OTHER_VALID, /* '9' */ PATH_CHAR_CLASS_OTHER_VALID, 2176 /* ':' */ PATH_CHAR_CLASS_COLON, /* ';' */ PATH_CHAR_CLASS_SEMICOLON, 2177 /* '<' */ PATH_CHAR_CLASS_INVALID, /* '=' */ PATH_CHAR_CLASS_OTHER_VALID, 2178 /* '>' */ PATH_CHAR_CLASS_INVALID, /* '?' */ PATH_CHAR_CLASS_LETTER, 2179 /* '@' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'A' */ PATH_CHAR_CLASS_ANY, 2180 /* 'B' */ PATH_CHAR_CLASS_ANY, /* 'C' */ PATH_CHAR_CLASS_ANY, 2181 /* 'D' */ PATH_CHAR_CLASS_ANY, /* 'E' */ PATH_CHAR_CLASS_ANY, 2182 /* 'F' */ PATH_CHAR_CLASS_ANY, /* 'G' */ PATH_CHAR_CLASS_ANY, 2183 /* 'H' */ PATH_CHAR_CLASS_ANY, /* 'I' */ PATH_CHAR_CLASS_ANY, 2184 /* 'J' */ PATH_CHAR_CLASS_ANY, /* 'K' */ PATH_CHAR_CLASS_ANY, 2185 /* 'L' */ PATH_CHAR_CLASS_ANY, /* 'M' */ PATH_CHAR_CLASS_ANY, 2186 /* 'N' */ PATH_CHAR_CLASS_ANY, /* 'O' */ PATH_CHAR_CLASS_ANY, 2187 /* 'P' */ PATH_CHAR_CLASS_ANY, /* 'Q' */ PATH_CHAR_CLASS_ANY, 2188 /* 'R' */ PATH_CHAR_CLASS_ANY, /* 'S' */ PATH_CHAR_CLASS_ANY, 2189 /* 'T' */ PATH_CHAR_CLASS_ANY, /* 'U' */ PATH_CHAR_CLASS_ANY, 2190 /* 'V' */ PATH_CHAR_CLASS_ANY, /* 'W' */ PATH_CHAR_CLASS_ANY, 2191 /* 'X' */ PATH_CHAR_CLASS_ANY, /* 'Y' */ PATH_CHAR_CLASS_ANY, 2192 /* 'Z' */ PATH_CHAR_CLASS_ANY, /* '[' */ PATH_CHAR_CLASS_OTHER_VALID, 2193 /* '\\' */ PATH_CHAR_CLASS_BACKSLASH, /* ']' */ PATH_CHAR_CLASS_OTHER_VALID, 2194 /* '^' */ PATH_CHAR_CLASS_OTHER_VALID, /* '_' */ PATH_CHAR_CLASS_OTHER_VALID, 2195 /* '`' */ PATH_CHAR_CLASS_OTHER_VALID, /* 'a' */ PATH_CHAR_CLASS_ANY, 2196 /* 'b' */ PATH_CHAR_CLASS_ANY, /* 'c' */ PATH_CHAR_CLASS_ANY, 2197 /* 'd' */ PATH_CHAR_CLASS_ANY, /* 'e' */ PATH_CHAR_CLASS_ANY, 2198 /* 'f' */ PATH_CHAR_CLASS_ANY, /* 'g' */ PATH_CHAR_CLASS_ANY, 2199 /* 'h' */ PATH_CHAR_CLASS_ANY, /* 'i' */ PATH_CHAR_CLASS_ANY, 2200 /* 'j' */ PATH_CHAR_CLASS_ANY, /* 'k' */ PATH_CHAR_CLASS_ANY, 2201 /* 'l' */ PATH_CHAR_CLASS_ANY, /* 'm' */ PATH_CHAR_CLASS_ANY, 2202 /* 'n' */ PATH_CHAR_CLASS_ANY, /* 'o' */ PATH_CHAR_CLASS_ANY, 2203 /* 'p' */ PATH_CHAR_CLASS_ANY, /* 'q' */ PATH_CHAR_CLASS_ANY, 2204 /* 'r' */ PATH_CHAR_CLASS_ANY, /* 's' */ PATH_CHAR_CLASS_ANY, 2205 /* 't' */ PATH_CHAR_CLASS_ANY, /* 'u' */ PATH_CHAR_CLASS_ANY, 2206 /* 'v' */ PATH_CHAR_CLASS_ANY, /* 'w' */ PATH_CHAR_CLASS_ANY, 2207 /* 'x' */ PATH_CHAR_CLASS_ANY, /* 'y' */ PATH_CHAR_CLASS_ANY, 2208 /* 'z' */ PATH_CHAR_CLASS_ANY, /* '{' */ PATH_CHAR_CLASS_OTHER_VALID, 2209 /* '|' */ PATH_CHAR_CLASS_INVALID, /* '}' */ PATH_CHAR_CLASS_OTHER_VALID, 2210 /* '~' */ PATH_CHAR_CLASS_OTHER_VALID 2211}; 2212 2213BOOL WINAPI PathIsValidCharA(char c, DWORD class) 2214{ 2215 if ((unsigned)c > 0x7e) 2216 return class & PATH_CHAR_CLASS_OTHER_VALID; 2217 2218 return class & path_charclass[(unsigned)c]; 2219} 2220 2221BOOL WINAPI PathIsValidCharW(WCHAR c, DWORD class) 2222{ 2223 if (c > 0x7e) 2224 return class & PATH_CHAR_CLASS_OTHER_VALID; 2225 2226 return class & path_charclass[c]; 2227} 2228 2229char * WINAPI PathFindNextComponentA(const char *path) 2230{ 2231 char *slash; 2232 2233 TRACE("%s\n", wine_dbgstr_a(path)); 2234 2235 if (!path || !*path) 2236 return NULL; 2237 2238 if ((slash = StrChrA(path, '\\'))) 2239 { 2240 if (slash[1] == '\\') 2241 slash++; 2242 return slash + 1; 2243 } 2244 2245 return (char *)path + strlen(path); 2246} 2247 2248WCHAR * WINAPI PathFindNextComponentW(const WCHAR *path) 2249{ 2250 WCHAR *slash; 2251 2252 TRACE("%s\n", wine_dbgstr_w(path)); 2253 2254 if (!path || !*path) 2255 return NULL; 2256 2257 if ((slash = StrChrW(path, '\\'))) 2258 { 2259 if (slash[1] == '\\') 2260 slash++; 2261 return slash + 1; 2262 } 2263 2264 return (WCHAR *)path + lstrlenW(path); 2265} 2266 2267char * WINAPI PathSkipRootA(const char *path) 2268{ 2269 TRACE("%s\n", wine_dbgstr_a(path)); 2270 2271 if (!path || !*path) 2272 return NULL; 2273 2274 if (*path == '\\' && path[1] == '\\') 2275 { 2276 /* Network share: skip share server and mount point */ 2277 path += 2; 2278 if ((path = StrChrA(path, '\\')) && (path = StrChrA(path + 1, '\\'))) 2279 path++; 2280 return (char *)path; 2281 } 2282 2283 if (IsDBCSLeadByte(*path)) 2284 return NULL; 2285 2286 /* Check x:\ */ 2287 if (path[0] && path[1] == ':' && path[2] == '\\') 2288 return (char *)path + 3; 2289 2290 return NULL; 2291} 2292 2293WCHAR * WINAPI PathSkipRootW(const WCHAR *path) 2294{ 2295 TRACE("%s\n", wine_dbgstr_w(path)); 2296 2297 if (!path || !*path) 2298 return NULL; 2299 2300 if (*path == '\\' && path[1] == '\\') 2301 { 2302 /* Network share: skip share server and mount point */ 2303 path += 2; 2304 if ((path = StrChrW(path, '\\')) && (path = StrChrW(path + 1, '\\'))) 2305 path++; 2306 return (WCHAR *)path; 2307 } 2308 2309 /* Check x:\ */ 2310 if (path[0] && path[1] == ':' && path[2] == '\\') 2311 return (WCHAR *)path + 3; 2312 2313 return NULL; 2314} 2315 2316void WINAPI PathStripPathA(char *path) 2317{ 2318 TRACE("%s\n", wine_dbgstr_a(path)); 2319 2320 if (path) 2321 { 2322 char *filename = PathFindFileNameA(path); 2323 if (filename != path) 2324 RtlMoveMemory(path, filename, strlen(filename) + 1); 2325 } 2326} 2327 2328void WINAPI PathStripPathW(WCHAR *path) 2329{ 2330 WCHAR *filename; 2331 2332 TRACE("%s\n", wine_dbgstr_w(path)); 2333 filename = PathFindFileNameW(path); 2334 if (filename != path) 2335 RtlMoveMemory(path, filename, (lstrlenW(filename) + 1) * sizeof(WCHAR)); 2336} 2337 2338BOOL WINAPI PathSearchAndQualifyA(const char *path, char *buffer, UINT length) 2339{ 2340 TRACE("%s, %p, %u\n", wine_dbgstr_a(path), buffer, length); 2341 2342 if (SearchPathA(NULL, path, NULL, length, buffer, NULL)) 2343 return TRUE; 2344 2345 return !!GetFullPathNameA(path, length, buffer, NULL); 2346} 2347 2348BOOL WINAPI PathSearchAndQualifyW(const WCHAR *path, WCHAR *buffer, UINT length) 2349{ 2350 TRACE("%s, %p, %u\n", wine_dbgstr_w(path), buffer, length); 2351 2352 if (SearchPathW(NULL, path, NULL, length, buffer, NULL)) 2353 return TRUE; 2354 return !!GetFullPathNameW(path, length, buffer, NULL); 2355} 2356 2357BOOL WINAPI PathRelativePathToA(char *path, const char *from, DWORD attributes_from, const char *to, 2358 DWORD attributes_to) 2359{ 2360 WCHAR pathW[MAX_PATH], fromW[MAX_PATH], toW[MAX_PATH]; 2361 BOOL ret; 2362 2363 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_a(from), attributes_from, wine_dbgstr_a(to), attributes_to); 2364 2365 if (!path || !from || !to) 2366 return FALSE; 2367 2368 MultiByteToWideChar(CP_ACP, 0, from, -1, fromW, ARRAY_SIZE(fromW)); 2369 MultiByteToWideChar(CP_ACP, 0, to, -1, toW, ARRAY_SIZE(toW)); 2370 ret = PathRelativePathToW(pathW, fromW, attributes_from, toW, attributes_to); 2371 WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, MAX_PATH, 0, 0); 2372 2373 return ret; 2374} 2375 2376BOOL WINAPI PathRelativePathToW(WCHAR *path, const WCHAR *from, DWORD attributes_from, const WCHAR *to, 2377 DWORD attributes_to) 2378{ 2379 WCHAR fromW[MAX_PATH], toW[MAX_PATH]; 2380 DWORD len; 2381 2382 TRACE("%p, %s, %#lx, %s, %#lx\n", path, wine_dbgstr_w(from), attributes_from, wine_dbgstr_w(to), attributes_to); 2383 2384 if (!path || !from || !to) 2385 return FALSE; 2386 2387 *path = '\0'; 2388 lstrcpynW(fromW, from, ARRAY_SIZE(fromW)); 2389 lstrcpynW(toW, to, ARRAY_SIZE(toW)); 2390 2391 if (!(attributes_from & FILE_ATTRIBUTE_DIRECTORY)) 2392 PathRemoveFileSpecW(fromW); 2393 if (!(attributes_to & FILE_ATTRIBUTE_DIRECTORY)) 2394 PathRemoveFileSpecW(toW); 2395 2396 /* Paths can only be relative if they have a common root */ 2397 if (!(len = PathCommonPrefixW(fromW, toW, 0))) 2398 return FALSE; 2399 2400 /* Strip off 'from' components to the root, by adding "..\" */ 2401 from = fromW + len; 2402 if (!*from) 2403 { 2404 path[0] = '.'; 2405 path[1] = '\0'; 2406 } 2407 if (*from == '\\') 2408 from++; 2409 2410 while (*from) 2411 { 2412 from = PathFindNextComponentW(from); 2413 lstrcatW(path, *from ? L"..\\" : L".."); 2414 } 2415 2416 /* From the root add the components of 'to' */ 2417 to += len; 2418 /* We check to[-1] to avoid skipping end of string. See the notes for this function. */ 2419 if (*to && to[-1]) 2420 { 2421 if (*to != '\\') 2422 to--; 2423 len = lstrlenW(path); 2424 if (len + lstrlenW(to) >= MAX_PATH) 2425 { 2426 *path = '\0'; 2427 return FALSE; 2428 } 2429 lstrcpyW(path + len, to); 2430 } 2431 2432 return TRUE; 2433} 2434 2435HRESULT WINAPI PathMatchSpecExA(const char *path, const char *mask, DWORD flags) 2436{ 2437 WCHAR *pathW, *maskW; 2438 HRESULT ret; 2439 2440 TRACE("%s, %s\n", wine_dbgstr_a(path), wine_dbgstr_a(mask)); 2441 2442 if (flags) 2443 FIXME("Ignoring flags %#lx.\n", flags); 2444 2445 if (!lstrcmpA(mask, "*.*")) 2446 return S_OK; /* Matches every path */ 2447 2448 pathW = heap_strdupAtoW( path ); 2449 maskW = heap_strdupAtoW( mask ); 2450 ret = PathMatchSpecExW( pathW, maskW, flags ); 2451 heap_free( pathW ); 2452 heap_free( maskW ); 2453 return ret; 2454} 2455 2456BOOL WINAPI PathMatchSpecA(const char *path, const char *mask) 2457{ 2458 return PathMatchSpecExA(path, mask, 0) == S_OK; 2459} 2460 2461static BOOL path_match_maskW(const WCHAR *name, const WCHAR *mask) 2462{ 2463 while (*name && *mask && *mask != ';') 2464 { 2465 if (*mask == '*') 2466 { 2467 do 2468 { 2469 if (path_match_maskW(name, mask + 1)) 2470 return TRUE; /* try substrings */ 2471 } while (*name++); 2472 return FALSE; 2473 } 2474 2475 if (towupper(*mask) != towupper(*name) && *mask != '?') 2476 return FALSE; 2477 2478 name++; 2479 mask++; 2480 } 2481 2482 if (!*name) 2483 { 2484 while (*mask == '*') 2485 mask++; 2486 if (!*mask || *mask == ';') 2487 return TRUE; 2488 } 2489 2490 return FALSE; 2491} 2492 2493HRESULT WINAPI PathMatchSpecExW(const WCHAR *path, const WCHAR *mask, DWORD flags) 2494{ 2495 TRACE("%s, %s\n", wine_dbgstr_w(path), wine_dbgstr_w(mask)); 2496 2497 if (flags) 2498 FIXME("Ignoring flags %#lx.\n", flags); 2499 2500 if (!lstrcmpW(mask, L"*.*")) 2501 return S_OK; /* Matches every path */ 2502 2503 while (*mask) 2504 { 2505 while (*mask == ' ') 2506 mask++; /* Eat leading spaces */ 2507 2508 if (path_match_maskW(path, mask)) 2509 return S_OK; /* Matches the current path */ 2510 2511 while (*mask && *mask != ';') 2512 mask++; /* masks separated by ';' */ 2513 2514 if (*mask == ';') 2515 mask++; 2516 } 2517 2518 return S_FALSE; 2519} 2520 2521BOOL WINAPI PathMatchSpecW(const WCHAR *path, const WCHAR *mask) 2522{ 2523 return PathMatchSpecExW(path, mask, 0) == S_OK; 2524} 2525 2526void WINAPI PathQuoteSpacesA(char *path) 2527{ 2528 TRACE("%s\n", wine_dbgstr_a(path)); 2529 2530 if (path && StrChrA(path, ' ')) 2531 { 2532 size_t len = strlen(path) + 1; 2533 2534 if (len + 2 < MAX_PATH) 2535 { 2536 memmove(path + 1, path, len); 2537 path[0] = '"'; 2538 path[len] = '"'; 2539 path[len + 1] = '\0'; 2540 } 2541 } 2542} 2543 2544void WINAPI PathQuoteSpacesW(WCHAR *path) 2545{ 2546 TRACE("%s\n", wine_dbgstr_w(path)); 2547 2548 if (path && StrChrW(path, ' ')) 2549 { 2550 int len = lstrlenW(path) + 1; 2551 2552 if (len + 2 < MAX_PATH) 2553 { 2554 memmove(path + 1, path, len * sizeof(WCHAR)); 2555 path[0] = '"'; 2556 path[len] = '"'; 2557 path[len + 1] = '\0'; 2558 } 2559 } 2560} 2561 2562BOOL WINAPI PathIsSameRootA(const char *path1, const char *path2) 2563{ 2564 const char *start; 2565 int len; 2566 2567 TRACE("%s, %s\n", wine_dbgstr_a(path1), wine_dbgstr_a(path2)); 2568 2569 if (!path1 || !path2 || !(start = PathSkipRootA(path1))) 2570 return FALSE; 2571 2572 len = PathCommonPrefixA(path1, path2, NULL) + 1; 2573 return start - path1 <= len; 2574} 2575 2576BOOL WINAPI PathIsSameRootW(const WCHAR *path1, const WCHAR *path2) 2577{ 2578 const WCHAR *start; 2579 int len; 2580 2581 TRACE("%s, %s\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2)); 2582 2583 if (!path1 || !path2 || !(start = PathSkipRootW(path1))) 2584 return FALSE; 2585 2586 len = PathCommonPrefixW(path1, path2, NULL) + 1; 2587 return start - path1 <= len; 2588} 2589 2590BOOL WINAPI PathFileExistsA(const char *path) 2591{ 2592 UINT prev_mode; 2593 DWORD attrs; 2594 2595 TRACE("%s\n", wine_dbgstr_a(path)); 2596 2597 if (!path) 2598 return FALSE; 2599 2600 /* Prevent a dialog box if path is on a disk that has been ejected. */ 2601 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS); 2602 attrs = GetFileAttributesA(path); 2603 SetErrorMode(prev_mode); 2604 return attrs != INVALID_FILE_ATTRIBUTES; 2605} 2606 2607BOOL WINAPI PathFileExistsW(const WCHAR *path) 2608{ 2609 UINT prev_mode; 2610 DWORD attrs; 2611 2612 TRACE("%s\n", wine_dbgstr_w(path)); 2613 2614 if (!path) 2615 return FALSE; 2616 2617 prev_mode = SetErrorMode(SEM_FAILCRITICALERRORS); 2618 attrs = GetFileAttributesW(path); 2619 SetErrorMode(prev_mode); 2620 return attrs != INVALID_FILE_ATTRIBUTES; 2621} 2622 2623int WINAPI PathParseIconLocationA(char *path) 2624{ 2625 int ret = 0; 2626 char *comma; 2627 2628 TRACE("%s\n", debugstr_a(path)); 2629 2630 if (!path) 2631 return 0; 2632 2633 if ((comma = strchr(path, ','))) 2634 { 2635 *comma++ = '\0'; 2636 ret = StrToIntA(comma); 2637 } 2638 PathUnquoteSpacesA(path); 2639 PathRemoveBlanksA(path); 2640 2641 return ret; 2642} 2643 2644int WINAPI PathParseIconLocationW(WCHAR *path) 2645{ 2646 WCHAR *comma; 2647 int ret = 0; 2648 2649 TRACE("%s\n", debugstr_w(path)); 2650 2651 if (!path) 2652 return 0; 2653 2654 if ((comma = StrChrW(path, ','))) 2655 { 2656 *comma++ = '\0'; 2657 ret = StrToIntW(comma); 2658 } 2659 PathUnquoteSpacesW(path); 2660 PathRemoveBlanksW(path); 2661 2662 return ret; 2663} 2664 2665BOOL WINAPI PathUnExpandEnvStringsA(const char *path, char *buffer, UINT buf_len) 2666{ 2667 WCHAR bufferW[MAX_PATH], *pathW; 2668 DWORD len; 2669 BOOL ret; 2670 2671 TRACE("%s, %p, %d\n", debugstr_a(path), buffer, buf_len); 2672 2673 pathW = heap_strdupAtoW(path); 2674 if (!pathW) return FALSE; 2675 2676 ret = PathUnExpandEnvStringsW(pathW, bufferW, MAX_PATH); 2677 HeapFree(GetProcessHeap(), 0, pathW); 2678 if (!ret) return FALSE; 2679 2680 len = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL); 2681 if (buf_len < len + 1) return FALSE; 2682 2683 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buf_len, NULL, NULL); 2684 return TRUE; 2685} 2686 2687struct envvars_map 2688{ 2689 const WCHAR *var; 2690 WCHAR path[MAX_PATH]; 2691 DWORD len; 2692}; 2693 2694static void init_envvars_map(struct envvars_map *map) 2695{ 2696 while (map->var) 2697 { 2698 map->len = ExpandEnvironmentStringsW(map->var, map->path, ARRAY_SIZE(map->path)); 2699 /* exclude null from length */ 2700 if (map->len) map->len--; 2701 map++; 2702 } 2703} 2704 2705BOOL WINAPI PathUnExpandEnvStringsW(const WCHAR *path, WCHAR *buffer, UINT buf_len) 2706{ 2707 static struct envvars_map null_var = {L"", {0}, 0}; 2708 struct envvars_map *match = &null_var, *cur; 2709 struct envvars_map envvars[] = 2710 { 2711 { L"%ALLUSERSPROFILE%" }, 2712 { L"%APPDATA%" }, 2713 { L"%ProgramFiles%" }, 2714 { L"%SystemRoot%" }, 2715 { L"%SystemDrive%" }, 2716 { L"%USERPROFILE%" }, 2717 { NULL } 2718 }; 2719 DWORD pathlen; 2720 UINT needed; 2721 2722 TRACE("%s, %p, %d\n", debugstr_w(path), buffer, buf_len); 2723 2724 pathlen = lstrlenW(path); 2725 init_envvars_map(envvars); 2726 cur = envvars; 2727 while (cur->var) 2728 { 2729 /* path can't contain expanded value or value wasn't retrieved */ 2730 if (cur->len == 0 || cur->len > pathlen || 2731 CompareStringOrdinal( cur->path, cur->len, path, cur->len, TRUE ) != CSTR_EQUAL) 2732 { 2733 cur++; 2734 continue; 2735 } 2736 2737 if (cur->len > match->len) 2738 match = cur; 2739 cur++; 2740 } 2741 2742 needed = lstrlenW(match->var) + 1 + pathlen - match->len; 2743 if (match->len == 0 || needed > buf_len) return FALSE; 2744 2745 lstrcpyW(buffer, match->var); 2746 lstrcatW(buffer, &path[match->len]); 2747 TRACE("ret %s\n", debugstr_w(buffer)); 2748 2749 return TRUE; 2750} 2751 2752static const struct 2753{ 2754 URL_SCHEME scheme_number; 2755 const WCHAR *scheme_name; 2756} 2757url_schemes[] = 2758{ 2759 { URL_SCHEME_FTP, L"ftp"}, 2760 { URL_SCHEME_HTTP, L"http"}, 2761 { URL_SCHEME_GOPHER, L"gopher"}, 2762 { URL_SCHEME_MAILTO, L"mailto"}, 2763 { URL_SCHEME_NEWS, L"news"}, 2764 { URL_SCHEME_NNTP, L"nntp"}, 2765 { URL_SCHEME_TELNET, L"telnet"}, 2766 { URL_SCHEME_WAIS, L"wais"}, 2767 { URL_SCHEME_FILE, L"file"}, 2768 { URL_SCHEME_MK, L"mk"}, 2769 { URL_SCHEME_HTTPS, L"https"}, 2770 { URL_SCHEME_SHELL, L"shell"}, 2771 { URL_SCHEME_SNEWS, L"snews"}, 2772 { URL_SCHEME_LOCAL, L"local"}, 2773 { URL_SCHEME_JAVASCRIPT, L"javascript"}, 2774 { URL_SCHEME_VBSCRIPT, L"vbscript"}, 2775 { URL_SCHEME_ABOUT, L"about"}, 2776 { URL_SCHEME_RES, L"res"}, 2777}; 2778 2779static const WCHAR *parse_scheme( const WCHAR *p ) 2780{ 2781 while (*p <= 0x7f && (iswalnum( *p ) || *p == '+' || *p == '-' || *p == '.')) 2782 ++p; 2783 return p; 2784} 2785 2786static DWORD get_scheme_code(const WCHAR *scheme, DWORD scheme_len) 2787{ 2788 unsigned int i; 2789 2790 for (i = 0; i < ARRAY_SIZE(url_schemes); ++i) 2791 { 2792 if (scheme_len == lstrlenW(url_schemes[i].scheme_name) 2793 && !wcsnicmp(scheme, url_schemes[i].scheme_name, scheme_len)) 2794 return url_schemes[i].scheme_number; 2795 } 2796 2797 return URL_SCHEME_UNKNOWN; 2798} 2799 2800HRESULT WINAPI ParseURLA(const char *url, PARSEDURLA *result) 2801{ 2802 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH]; 2803 const char *ptr = url; 2804 int len; 2805 2806 TRACE("%s, %p\n", wine_dbgstr_a(url), result); 2807 2808 if (result->cbSize != sizeof(*result)) 2809 return E_INVALIDARG; 2810 2811 while (*ptr && (isalnum( *ptr ) || *ptr == '-' || *ptr == '+' || *ptr == '.')) 2812 ptr++; 2813 2814 if (*ptr != ':' || ptr <= url + 1) 2815 { 2816 result->pszProtocol = NULL; 2817 return URL_E_INVALID_SYNTAX; 2818 } 2819 2820 result->pszProtocol = url; 2821 result->cchProtocol = ptr - url; 2822 result->pszSuffix = ptr + 1; 2823 result->cchSuffix = strlen(result->pszSuffix); 2824 2825 len = MultiByteToWideChar(CP_ACP, 0, url, ptr - url, scheme, ARRAY_SIZE(scheme)); 2826 result->nScheme = get_scheme_code(scheme, len); 2827 2828 return S_OK; 2829} 2830 2831HRESULT WINAPI ParseURLW(const WCHAR *url, PARSEDURLW *result) 2832{ 2833 const WCHAR *ptr = url; 2834 2835 TRACE("%s, %p\n", wine_dbgstr_w(url), result); 2836 2837 if (result->cbSize != sizeof(*result)) 2838 return E_INVALIDARG; 2839 2840 while (*ptr && (iswalnum(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.')) 2841 ptr++; 2842 2843 if (*ptr != ':' || ptr <= url + 1) 2844 { 2845 result->pszProtocol = NULL; 2846 return URL_E_INVALID_SYNTAX; 2847 } 2848 2849 result->pszProtocol = url; 2850 result->cchProtocol = ptr - url; 2851 result->pszSuffix = ptr + 1; 2852 result->cchSuffix = lstrlenW(result->pszSuffix); 2853 result->nScheme = get_scheme_code(url, ptr - url); 2854 2855 return S_OK; 2856} 2857 2858HRESULT WINAPI UrlUnescapeA(char *url, char *unescaped, DWORD *unescaped_len, DWORD flags) 2859{ 2860 BOOL stop_unescaping = FALSE; 2861 const char *src; 2862 char *dst, next; 2863 DWORD needed; 2864 HRESULT hr; 2865 2866 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(url), unescaped, unescaped_len, flags); 2867 2868 if (!url) 2869 return E_INVALIDARG; 2870 2871 if (flags & URL_UNESCAPE_INPLACE) 2872 dst = url; 2873 else 2874 { 2875 if (!unescaped || !unescaped_len) return E_INVALIDARG; 2876 dst = unescaped; 2877 } 2878 2879 for (src = url, needed = 0; *src; src++, needed++) 2880 { 2881 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?')) 2882 { 2883 stop_unescaping = TRUE; 2884 next = *src; 2885 } 2886 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping) 2887 { 2888 INT ih; 2889 char buf[3]; 2890 memcpy(buf, src + 1, 2); 2891 buf[2] = '\0'; 2892 ih = strtol(buf, NULL, 16); 2893 next = (CHAR) ih; 2894 src += 2; /* Advance to end of escape */ 2895 } 2896 else 2897 next = *src; 2898 2899 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len) 2900 *dst++ = next; 2901 } 2902 2903 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len) 2904 { 2905 *dst = '\0'; 2906 hr = S_OK; 2907 } 2908 else 2909 { 2910 needed++; /* add one for the '\0' */ 2911 hr = E_POINTER; 2912 } 2913 2914 if (!(flags & URL_UNESCAPE_INPLACE)) 2915 *unescaped_len = needed; 2916 2917 if (hr == S_OK) 2918 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_a(url) : wine_dbgstr_a(unescaped)); 2919 2920 return hr; 2921} 2922 2923static int get_utf8_len(unsigned char code) 2924{ 2925 if (code < 0x80) 2926 return 1; 2927 else if ((code & 0xe0) == 0xc0) 2928 return 2; 2929 else if ((code & 0xf0) == 0xe0) 2930 return 3; 2931 else if ((code & 0xf8) == 0xf0) 2932 return 4; 2933 return 0; 2934} 2935 2936HRESULT WINAPI UrlUnescapeW(WCHAR *url, WCHAR *unescaped, DWORD *unescaped_len, DWORD flags) 2937{ 2938 WCHAR *dst, next, utf16_buf[4]; 2939 BOOL stop_unescaping = FALSE; 2940 int utf8_len, utf16_len, i; 2941 const WCHAR *src; 2942 char utf8_buf[4]; 2943 DWORD needed; 2944 HRESULT hr; 2945 2946 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), unescaped, unescaped_len, flags); 2947 2948 if (!url) 2949 return E_INVALIDARG; 2950 2951 if (flags & URL_UNESCAPE_INPLACE) 2952 dst = url; 2953 else 2954 { 2955 if (!unescaped || !unescaped_len) return E_INVALIDARG; 2956 dst = unescaped; 2957 } 2958 2959 for (src = url, needed = 0; *src; src++, needed++) 2960 { 2961 utf16_len = 0; 2962 if (flags & URL_DONT_UNESCAPE_EXTRA_INFO && (*src == '#' || *src == '?')) 2963 { 2964 stop_unescaping = TRUE; 2965 next = *src; 2966 } 2967 else if (*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2)) && !stop_unescaping) 2968 { 2969 INT ih; 2970 WCHAR buf[5] = L"0x"; 2971 2972 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR)); 2973 buf[4] = 0; 2974 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih); 2975 src += 2; /* Advance to end of escape */ 2976 2977 if (flags & URL_UNESCAPE_AS_UTF8) 2978 { 2979 utf8_buf[0] = ih; 2980 utf8_len = get_utf8_len(ih); 2981 for (i = 1; i < utf8_len && *(src + 1) == '%' && *(src + 2) && *(src + 3); i++) 2982 { 2983 memcpy(buf + 2, src + 2, 2 * sizeof(WCHAR)); 2984 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih); 2985 /* Check if it is a valid continuation byte. */ 2986 if ((ih & 0xc0) == 0x80) 2987 { 2988 utf8_buf[i] = ih; 2989 src += 3; 2990 } 2991 else 2992 break; 2993 } 2994 2995 utf16_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, 2996 utf8_buf, i, utf16_buf, ARRAYSIZE(utf16_buf)); 2997 if (utf16_len) 2998 needed += utf16_len - 1; 2999 else 3000 next = 0xfffd; 3001 } 3002 else 3003 next = (WCHAR) ih; 3004 } 3005 else 3006 next = *src; 3007 3008 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len) 3009 { 3010 if (utf16_len) 3011 { 3012 memcpy(dst, utf16_buf, utf16_len * sizeof(*utf16_buf)); 3013 dst += utf16_len; 3014 } 3015 else 3016 *dst++ = next; 3017 } 3018 } 3019 3020 if (flags & URL_UNESCAPE_INPLACE || needed < *unescaped_len) 3021 { 3022 *dst = '\0'; 3023 hr = S_OK; 3024 } 3025 else 3026 { 3027 needed++; /* add one for the '\0' */ 3028 hr = E_POINTER; 3029 } 3030 3031 if (!(flags & URL_UNESCAPE_INPLACE)) 3032 *unescaped_len = needed; 3033 3034 if (hr == S_OK) 3035 TRACE("result %s\n", flags & URL_UNESCAPE_INPLACE ? wine_dbgstr_w(url) : wine_dbgstr_w(unescaped)); 3036 3037 return hr; 3038} 3039 3040HRESULT WINAPI PathCreateFromUrlA(const char *pszUrl, char *pszPath, DWORD *pcchPath, DWORD dwReserved) 3041{ 3042 WCHAR bufW[MAX_PATH]; 3043 WCHAR *pathW = bufW; 3044 UNICODE_STRING urlW; 3045 HRESULT ret; 3046 DWORD lenW = ARRAY_SIZE(bufW), lenA; 3047 3048 if (!pszUrl || !pszPath || !pcchPath || !*pcchPath) 3049 return E_INVALIDARG; 3050 3051 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl)) 3052 return E_INVALIDARG; 3053 if((ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved)) == E_POINTER) { 3054 pathW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR)); 3055 ret = PathCreateFromUrlW(urlW.Buffer, pathW, &lenW, dwReserved); 3056 } 3057 if(ret == S_OK) { 3058 RtlUnicodeToMultiByteSize(&lenA, pathW, lenW * sizeof(WCHAR)); 3059 if(*pcchPath > lenA) { 3060 RtlUnicodeToMultiByteN(pszPath, *pcchPath - 1, &lenA, pathW, lenW * sizeof(WCHAR)); 3061 pszPath[lenA] = 0; 3062 *pcchPath = lenA; 3063 } else { 3064 *pcchPath = lenA + 1; 3065 ret = E_POINTER; 3066 } 3067 } 3068 if(pathW != bufW) HeapFree(GetProcessHeap(), 0, pathW); 3069 RtlFreeUnicodeString(&urlW); 3070 return ret; 3071} 3072 3073HRESULT WINAPI PathCreateFromUrlW(const WCHAR *url, WCHAR *path, DWORD *pcchPath, DWORD dwReserved) 3074{ 3075 DWORD nslashes, unescape, len; 3076 const WCHAR *src; 3077 WCHAR *tpath, *dst; 3078 HRESULT hr = S_OK; 3079 3080 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(url), path, pcchPath, dwReserved); 3081 3082 if (!url || !path || !pcchPath || !*pcchPath) 3083 return E_INVALIDARG; 3084 3085 if (wcsnicmp( url, L"file:", 5)) 3086 return E_INVALIDARG; 3087 3088 url += 5; 3089 3090 src = url; 3091 nslashes = 0; 3092 while (*src == '/' || *src == '\\') 3093 { 3094 nslashes++; 3095 src++; 3096 } 3097 3098 /* We need a temporary buffer so we can compute what size to ask for. 3099 * We know that the final string won't be longer than the current pszUrl 3100 * plus at most two backslashes. All the other transformations make it 3101 * shorter. 3102 */ 3103 len = 2 + lstrlenW(url) + 1; 3104 if (*pcchPath < len) 3105 tpath = heap_alloc(len * sizeof(WCHAR)); 3106 else 3107 tpath = path; 3108 3109 len = 0; 3110 dst = tpath; 3111 unescape = 1; 3112 switch (nslashes) 3113 { 3114 case 0: 3115 /* 'file:' + escaped DOS path */ 3116 break; 3117 case 1: 3118 /* 'file:/' + escaped DOS path */ 3119 /* fall through */ 3120 case 3: 3121 /* 'file:///' (implied localhost) + escaped DOS path */ 3122 if (!is_escaped_drive_spec( src )) 3123 src -= 1; 3124 break; 3125 case 2: 3126 if (lstrlenW(src) >= 10 && !wcsnicmp( src, L"localhost", 9) && (src[9] == '/' || src[9] == '\\')) 3127 { 3128 /* 'file://localhost/' + escaped DOS path */ 3129 src += 10; 3130 } 3131 else if (is_escaped_drive_spec( src )) 3132 { 3133 /* 'file://' + unescaped DOS path */ 3134 unescape = 0; 3135 } 3136 else 3137 { 3138 /* 'file://hostname:port/path' (where path is escaped) 3139 * or 'file:' + escaped UNC path (\\server\share\path) 3140 * The second form is clearly specific to Windows and it might 3141 * even be doing a network lookup to try to figure it out. 3142 */ 3143 while (*src && *src != '/' && *src != '\\') 3144 src++; 3145 len = src - url; 3146 StrCpyNW(dst, url, len + 1); 3147 dst += len; 3148 if (*src && is_escaped_drive_spec( src + 1 )) 3149 { 3150 /* 'Forget' to add a trailing '/', just like Windows */ 3151 src++; 3152 } 3153 } 3154 break; 3155 case 4: 3156 /* 'file://' + unescaped UNC path (\\server\share\path) */ 3157 unescape = 0; 3158 if (is_escaped_drive_spec( src )) 3159 break; 3160 /* fall through */ 3161 default: 3162 /* 'file:/...' + escaped UNC path (\\server\share\path) */ 3163 src -= 2; 3164 } 3165 3166 /* Copy the remainder of the path */ 3167 len += lstrlenW(src); 3168 lstrcpyW(dst, src); 3169 3170 /* First do the Windows-specific path conversions */ 3171 for (dst = tpath; *dst; dst++) 3172 if (*dst == '/') *dst = '\\'; 3173 if (is_escaped_drive_spec( tpath )) 3174 tpath[1] = ':'; /* c| -> c: */ 3175 3176 /* And only then unescape the path (i.e. escaped slashes are left as is) */ 3177 if (unescape) 3178 { 3179 hr = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE); 3180 if (hr == S_OK) 3181 { 3182 /* When working in-place UrlUnescapeW() does not set len */ 3183 len = lstrlenW(tpath); 3184 } 3185 } 3186 3187 if (*pcchPath < len + 1) 3188 { 3189 hr = E_POINTER; 3190 *pcchPath = len + 1; 3191 } 3192 else 3193 { 3194 *pcchPath = len; 3195 if (tpath != path) 3196 lstrcpyW(path, tpath); 3197 } 3198 if (tpath != path) 3199 heap_free(tpath); 3200 3201 TRACE("Returning (%lu) %s\n", *pcchPath, wine_dbgstr_w(path)); 3202 return hr; 3203} 3204 3205HRESULT WINAPI PathCreateFromUrlAlloc(const WCHAR *url, WCHAR **path, DWORD reserved) 3206{ 3207 WCHAR pathW[MAX_PATH]; 3208 DWORD size; 3209 HRESULT hr; 3210 3211 size = MAX_PATH; 3212 hr = PathCreateFromUrlW(url, pathW, &size, reserved); 3213 if (SUCCEEDED(hr)) 3214 { 3215 /* Yes, this is supposed to crash if 'path' is NULL */ 3216 *path = StrDupW(pathW); 3217 } 3218 3219 return hr; 3220} 3221 3222BOOL WINAPI PathIsURLA(const char *path) 3223{ 3224 PARSEDURLA base; 3225 HRESULT hr; 3226 3227 TRACE("%s\n", wine_dbgstr_a(path)); 3228 3229 if (!path || !*path) 3230 return FALSE; 3231 3232 /* get protocol */ 3233 base.cbSize = sizeof(base); 3234 hr = ParseURLA(path, &base); 3235 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID); 3236} 3237 3238BOOL WINAPI PathIsURLW(const WCHAR *path) 3239{ 3240 PARSEDURLW base; 3241 HRESULT hr; 3242 3243 TRACE("%s\n", wine_dbgstr_w(path)); 3244 3245 if (!path || !*path) 3246 return FALSE; 3247 3248 /* get protocol */ 3249 base.cbSize = sizeof(base); 3250 hr = ParseURLW(path, &base); 3251 return hr == S_OK && (base.nScheme != URL_SCHEME_INVALID); 3252} 3253 3254#define WINE_URL_BASH_AS_SLASH 0x01 3255#define WINE_URL_COLLAPSE_SLASHES 0x02 3256#define WINE_URL_ESCAPE_SLASH 0x04 3257#define WINE_URL_ESCAPE_HASH 0x08 3258#define WINE_URL_ESCAPE_QUESTION 0x10 3259#define WINE_URL_STOP_ON_HASH 0x20 3260#define WINE_URL_STOP_ON_QUESTION 0x40 3261 3262static BOOL url_needs_escape(WCHAR ch, DWORD flags, DWORD int_flags) 3263{ 3264 if (flags & URL_ESCAPE_SPACES_ONLY) 3265 return ch == ' '; 3266 3267 if ((flags & URL_ESCAPE_PERCENT) && (ch == '%')) 3268 return TRUE; 3269 3270 if ((flags & URL_ESCAPE_AS_UTF8) && (ch >= 0x80)) 3271 return TRUE; 3272 3273 if (ch <= 31 || (ch >= 127 && ch <= 255) ) 3274 return TRUE; 3275 3276 if (iswalnum(ch)) 3277 return FALSE; 3278 3279 switch (ch) { 3280 case ' ': 3281 case '<': 3282 case '>': 3283 case '\"': 3284 case '{': 3285 case '}': 3286 case '|': 3287 case '\\': 3288 case '^': 3289 case ']': 3290 case '[': 3291 case '`': 3292 case '&': 3293 return TRUE; 3294 case '/': 3295 return !!(int_flags & WINE_URL_ESCAPE_SLASH); 3296 case '?': 3297 return !!(int_flags & WINE_URL_ESCAPE_QUESTION); 3298 case '#': 3299 return !!(int_flags & WINE_URL_ESCAPE_HASH); 3300 default: 3301 return FALSE; 3302 } 3303} 3304 3305HRESULT WINAPI UrlEscapeA(const char *url, char *escaped, DWORD *escaped_len, DWORD flags) 3306{ 3307 WCHAR bufW[INTERNET_MAX_URL_LENGTH]; 3308 WCHAR *escapedW = bufW; 3309 UNICODE_STRING urlW; 3310 HRESULT hr; 3311 DWORD lenW = ARRAY_SIZE(bufW), lenA; 3312 3313 if (!escaped || !escaped_len || !*escaped_len) 3314 return E_INVALIDARG; 3315 3316 if (!RtlCreateUnicodeStringFromAsciiz(&urlW, url)) 3317 return E_INVALIDARG; 3318 3319 if (flags & URL_ESCAPE_AS_UTF8) 3320 { 3321 RtlFreeUnicodeString(&urlW); 3322 return E_NOTIMPL; 3323 } 3324 3325 if ((hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags)) == E_POINTER) 3326 { 3327 escapedW = heap_alloc(lenW * sizeof(WCHAR)); 3328 hr = UrlEscapeW(urlW.Buffer, escapedW, &lenW, flags); 3329 } 3330 3331 if (hr == S_OK) 3332 { 3333 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR)); 3334 if (*escaped_len > lenA) 3335 { 3336 RtlUnicodeToMultiByteN(escaped, *escaped_len - 1, &lenA, escapedW, lenW * sizeof(WCHAR)); 3337 escaped[lenA] = 0; 3338 *escaped_len = lenA; 3339 } 3340 else 3341 { 3342 *escaped_len = lenA + 1; 3343 hr = E_POINTER; 3344 } 3345 } 3346 if (escapedW != bufW) 3347 heap_free(escapedW); 3348 RtlFreeUnicodeString(&urlW); 3349 return hr; 3350} 3351 3352HRESULT WINAPI UrlEscapeW(const WCHAR *url, WCHAR *escaped, DWORD *escaped_len, DWORD flags) 3353{ 3354 DWORD needed = 0, slashes = 0, int_flags; 3355 WCHAR next[12], *dst, *dst_ptr; 3356 BOOL stop_escaping = FALSE; 3357 PARSEDURLW parsed_url; 3358 const WCHAR *src; 3359 INT i, len; 3360 HRESULT hr; 3361 3362 TRACE("%p, %s, %p, %p, %#lx\n", url, wine_dbgstr_w(url), escaped, escaped_len, flags); 3363 3364 if (!url || !escaped_len || !escaped || *escaped_len == 0) 3365 return E_INVALIDARG; 3366 3367 if (flags & ~(URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_SEGMENT_ONLY | URL_DONT_ESCAPE_EXTRA_INFO | 3368 URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8)) 3369 { 3370 FIXME("Unimplemented flags: %08lx\n", flags); 3371 } 3372 3373 dst_ptr = dst = heap_alloc(*escaped_len * sizeof(WCHAR)); 3374 if (!dst_ptr) 3375 return E_OUTOFMEMORY; 3376 3377 /* fix up flags */ 3378 if (flags & URL_ESCAPE_SPACES_ONLY) 3379 /* if SPACES_ONLY specified, reset the other controls */ 3380 flags &= ~(URL_DONT_ESCAPE_EXTRA_INFO | URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY); 3381 else 3382 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */ 3383 flags |= URL_DONT_ESCAPE_EXTRA_INFO; 3384 3385 int_flags = 0; 3386 if (flags & URL_ESCAPE_SEGMENT_ONLY) 3387 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH; 3388 else 3389 { 3390 parsed_url.cbSize = sizeof(parsed_url); 3391 if (ParseURLW(url, &parsed_url) != S_OK) 3392 parsed_url.nScheme = URL_SCHEME_INVALID; 3393 3394 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol)); 3395 3396 if (flags & URL_DONT_ESCAPE_EXTRA_INFO) 3397 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION; 3398 3399 switch(parsed_url.nScheme) { 3400 case URL_SCHEME_FILE: 3401 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH; 3402 int_flags &= ~WINE_URL_STOP_ON_HASH; 3403 break; 3404 3405 case URL_SCHEME_HTTP: 3406 case URL_SCHEME_HTTPS: 3407 int_flags |= WINE_URL_BASH_AS_SLASH; 3408 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\') 3409 int_flags |= WINE_URL_ESCAPE_SLASH; 3410 break; 3411 3412 case URL_SCHEME_MAILTO: 3413 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH; 3414 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH); 3415 break; 3416 3417 case URL_SCHEME_INVALID: 3418 break; 3419 3420 case URL_SCHEME_FTP: 3421 default: 3422 if(parsed_url.pszSuffix[0] != '/') 3423 int_flags |= WINE_URL_ESCAPE_SLASH; 3424 break; 3425 } 3426 } 3427 3428 for (src = url; *src; ) 3429 { 3430 WCHAR cur = *src; 3431 len = 0; 3432 3433 if ((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == url + parsed_url.cchProtocol + 1) 3434 { 3435 while (cur == '/' || cur == '\\') 3436 { 3437 slashes++; 3438 cur = *++src; 3439 } 3440 if (slashes == 2 && !wcsnicmp(src, L"localhost", 9)) { /* file://localhost/ -> file:/// */ 3441 if(src[9] == '/' || src[9] == '\\') src += 10; 3442 slashes = 3; 3443 } 3444 3445 switch (slashes) 3446 { 3447 case 1: 3448 case 3: 3449 next[0] = next[1] = next[2] = '/'; 3450 len = 3; 3451 break; 3452 case 0: 3453 len = 0; 3454 break; 3455 default: 3456 next[0] = next[1] = '/'; 3457 len = 2; 3458 break; 3459 } 3460 } 3461 if (len == 0) 3462 { 3463 if (cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH)) 3464 stop_escaping = TRUE; 3465 3466 if (cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION)) 3467 stop_escaping = TRUE; 3468 3469 if (cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/'; 3470 3471 if (url_needs_escape(cur, flags, int_flags) && !stop_escaping) 3472 { 3473 if (flags & URL_ESCAPE_AS_UTF8) 3474 { 3475 char utf[16]; 3476 3477 if ((cur >= 0xd800 && cur <= 0xdfff) && (src[1] >= 0xdc00 && src[1] <= 0xdfff)) 3478 { 3479 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, 2, utf, sizeof(utf), NULL, NULL); 3480 src++; 3481 } 3482 else 3483 len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &cur, 1, utf, sizeof(utf), NULL, NULL); 3484 3485 if (!len) 3486 { 3487 utf[0] = 0xef; 3488 utf[1] = 0xbf; 3489 utf[2] = 0xbd; 3490 len = 3; 3491 } 3492 3493 for (i = 0; i < len; ++i) 3494 { 3495 next[i*3+0] = '%'; 3496 next[i*3+1] = hexDigits[(utf[i] >> 4) & 0xf]; 3497 next[i*3+2] = hexDigits[utf[i] & 0xf]; 3498 } 3499 len *= 3; 3500 } 3501 else 3502 { 3503 next[0] = '%'; 3504 next[1] = hexDigits[(cur >> 4) & 0xf]; 3505 next[2] = hexDigits[cur & 0xf]; 3506 len = 3; 3507 } 3508 } 3509 else 3510 { 3511 next[0] = cur; 3512 len = 1; 3513 } 3514 src++; 3515 } 3516 3517 if (needed + len <= *escaped_len) 3518 { 3519 memcpy(dst, next, len*sizeof(WCHAR)); 3520 dst += len; 3521 } 3522 needed += len; 3523 } 3524 3525 if (needed < *escaped_len) 3526 { 3527 *dst = '\0'; 3528 memcpy(escaped, dst_ptr, (needed+1)*sizeof(WCHAR)); 3529 hr = S_OK; 3530 } 3531 else 3532 { 3533 needed++; /* add one for the '\0' */ 3534 hr = E_POINTER; 3535 } 3536 *escaped_len = needed; 3537 3538 heap_free(dst_ptr); 3539 return hr; 3540} 3541 3542HRESULT WINAPI UrlCanonicalizeA(const char *src_url, char *canonicalized, DWORD *canonicalized_len, DWORD flags) 3543{ 3544 LPWSTR url, canonical; 3545 HRESULT hr; 3546 3547 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_a(src_url), canonicalized, canonicalized_len, flags); 3548 3549 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len) 3550 return E_INVALIDARG; 3551 3552 url = heap_strdupAtoW(src_url); 3553 canonical = heap_alloc(*canonicalized_len * sizeof(WCHAR)); 3554 if (!url || !canonical) 3555 { 3556 heap_free(url); 3557 heap_free(canonical); 3558 return E_OUTOFMEMORY; 3559 } 3560 3561 hr = UrlCanonicalizeW(url, canonical, canonicalized_len, flags); 3562 if (hr == S_OK) 3563 WideCharToMultiByte(CP_ACP, 0, canonical, -1, canonicalized, *canonicalized_len + 1, NULL, NULL); 3564 3565 heap_free(url); 3566 heap_free(canonical); 3567 return hr; 3568} 3569 3570static bool scheme_is_opaque( URL_SCHEME scheme ) 3571{ 3572 switch (scheme) 3573 { 3574 case URL_SCHEME_ABOUT: 3575 case URL_SCHEME_JAVASCRIPT: 3576 case URL_SCHEME_MAILTO: 3577 case URL_SCHEME_SHELL: 3578 case URL_SCHEME_VBSCRIPT: 3579 return true; 3580 3581 default: 3582 return false; 3583 } 3584} 3585 3586static bool scheme_preserves_backslashes( URL_SCHEME scheme ) 3587{ 3588 switch (scheme) 3589 { 3590 case URL_SCHEME_FTP: 3591 case URL_SCHEME_INVALID: 3592 case URL_SCHEME_LOCAL: 3593 case URL_SCHEME_MK: 3594 case URL_SCHEME_RES: 3595 case URL_SCHEME_UNKNOWN: 3596 case URL_SCHEME_WAIS: 3597 return true; 3598 3599 default: 3600 return false; 3601 } 3602} 3603 3604static bool scheme_uses_hostname( URL_SCHEME scheme ) 3605{ 3606 switch (scheme) 3607 { 3608 case URL_SCHEME_ABOUT: 3609 case URL_SCHEME_JAVASCRIPT: 3610 case URL_SCHEME_MAILTO: 3611 case URL_SCHEME_MK: 3612 case URL_SCHEME_SHELL: 3613 case URL_SCHEME_VBSCRIPT: 3614 return false; 3615 3616 default: 3617 return true; 3618 } 3619} 3620 3621static bool scheme_char_is_separator( URL_SCHEME scheme, WCHAR c ) 3622{ 3623 if (c == '/') 3624 return true; 3625 if (c == '\\' && scheme != URL_SCHEME_INVALID && scheme != URL_SCHEME_UNKNOWN) 3626 return true; 3627 return false; 3628} 3629 3630static bool scheme_char_is_hostname_separator( URL_SCHEME scheme, DWORD flags, WCHAR c ) 3631{ 3632 switch (c) 3633 { 3634 case 0: 3635 case '/': 3636 return true; 3637 case '\\': 3638 return !scheme_preserves_backslashes( scheme ); 3639 case '?': 3640 return scheme != URL_SCHEME_FILE || (flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL)); 3641 case '#': 3642 return scheme != URL_SCHEME_FILE; 3643 default: 3644 return false; 3645 } 3646} 3647 3648static bool scheme_char_is_dot_separator( URL_SCHEME scheme, DWORD flags, WCHAR c ) 3649{ 3650 switch (c) 3651 { 3652 case 0: 3653 case '/': 3654 case '?': 3655 return true; 3656 case '#': 3657 return (scheme != URL_SCHEME_FILE || !(flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL))); 3658 case '\\': 3659 return (scheme != URL_SCHEME_INVALID && scheme != URL_SCHEME_UNKNOWN && scheme != URL_SCHEME_MK); 3660 default: 3661 return false; 3662 } 3663} 3664 3665/* There are essentially two types of behaviour concerning dot simplification, 3666 * not counting opaque schemes: 3667 * 3668 * 1) Simplify dots if and only if the first element is not a single or double 3669 * dot. If a double dot would rewind past the root, ignore it. For example: 3670 * 3671 * http://hostname/a/../../b/. -> http://hostname/b/ 3672 * http://hostname/./../../b/. -> http://hostname/./../../b/. 3673 * 3674 * 2) Effectively treat all paths as relative. Always simplify, except if a 3675 * double dot would rewind past the root, in which case emit it verbatim. 3676 * For example: 3677 * 3678 * wine://hostname/a/../../b/. -> wine://hostname/../b/ 3679 * wine://hostname/./../../b/. -> wine://hostname/../b/ 3680 * 3681 * For unclear reasons, this behaviour also correlates with whether a final 3682 * slash is always emitted after a single or double dot (e.g. if 3683 * URL_DONT_SIMPLIFY is specified). The former type does not emit a slash; the 3684 * latter does. 3685 */ 3686static bool scheme_is_always_relative( URL_SCHEME scheme, DWORD flags ) 3687{ 3688 switch (scheme) 3689 { 3690 case URL_SCHEME_INVALID: 3691 case URL_SCHEME_UNKNOWN: 3692 return true; 3693 3694 case URL_SCHEME_FILE: 3695 return flags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL); 3696 3697 default: 3698 return false; 3699 } 3700} 3701 3702struct string_buffer 3703{ 3704 WCHAR *string; 3705 size_t len, capacity; 3706}; 3707 3708static void append_string( struct string_buffer *buffer, const WCHAR *str, size_t len ) 3709{ 3710 array_reserve( (void **)&buffer->string, &buffer->capacity, buffer->len + len, sizeof(WCHAR) ); 3711 memcpy( buffer->string + buffer->len, str, len * sizeof(WCHAR) ); 3712 buffer->len += len; 3713} 3714 3715static void append_char( struct string_buffer *buffer, WCHAR c ) 3716{ 3717 append_string( buffer, &c, 1 ); 3718} 3719 3720static char get_slash_dir( URL_SCHEME scheme, DWORD flags, char src, const struct string_buffer *dst ) 3721{ 3722 if (src && scheme_preserves_backslashes( scheme )) 3723 return src; 3724 3725 if (scheme == URL_SCHEME_FILE && (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) 3726 && !wmemchr( dst->string, '#', dst->len )) 3727 return '\\'; 3728 3729 return '/'; 3730} 3731 3732static void rewrite_url( struct string_buffer *dst, const WCHAR *url, DWORD *flags_ptr ) 3733{ 3734 DWORD flags = *flags_ptr; 3735 bool pathurl = (flags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)); 3736 bool is_relative = false, has_hostname = false, has_initial_slash = false; 3737 const WCHAR *query = NULL, *hash = NULL; 3738 URL_SCHEME scheme = URL_SCHEME_INVALID; 3739 size_t query_len = 0, hash_len = 0; 3740 const WCHAR *scheme_end, *src_end; 3741 const WCHAR *hostname = NULL; 3742 size_t hostname_len = 0; 3743 const WCHAR *src = url; 3744 size_t root_offset; 3745 3746 /* Determine the scheme. */ 3747 3748 scheme_end = parse_scheme( url ); 3749 3750 if (*scheme_end == ':' && scheme_end >= url + 2) 3751 { 3752 size_t scheme_len = scheme_end + 1 - url; 3753 3754 scheme = get_scheme_code( url, scheme_len - 1 ); 3755 3756 for (size_t i = 0; i < scheme_len; ++i) 3757 append_char( dst, tolower( *src++ )); 3758 } 3759 else if (url[0] == '\\' && url[1] == '\\') 3760 { 3761 append_string( dst, L"file:", 5 ); 3762 if (!pathurl && !(flags & URL_UNESCAPE)) 3763 flags |= URL_ESCAPE_UNSAFE | URL_ESCAPE_PERCENT; 3764 scheme = URL_SCHEME_FILE; 3765 3766 has_hostname = true; 3767 } 3768 3769 if (is_escaped_drive_spec( url )) 3770 { 3771 append_string( dst, L"file://", 7 ); 3772 if (!pathurl && !(flags & URL_UNESCAPE)) 3773 flags |= URL_ESCAPE_UNSAFE | URL_ESCAPE_PERCENT; 3774 scheme = URL_SCHEME_FILE; 3775 3776 hostname_len = 0; 3777 has_hostname = true; 3778 } 3779 else if (scheme == URL_SCHEME_MK) 3780 { 3781 if (src[0] == '@') 3782 { 3783 while (*src && *src != '/') 3784 append_char( dst, *src++ ); 3785 if (*src == '/') 3786 append_char( dst, *src++ ); 3787 else 3788 append_char( dst, '/' ); 3789 3790 if ((src[0] == '.' && scheme_char_is_dot_separator( scheme, flags, src[1] )) || 3791 (src[0] == '.' && src[1] == '.' && scheme_char_is_dot_separator( scheme, flags, src[2] ))) 3792 is_relative = true; 3793 } 3794 } 3795 else if (scheme_uses_hostname( scheme ) && scheme_char_is_separator( scheme, src[0] ) 3796 && scheme_char_is_separator( scheme, src[1] )) 3797 { 3798 append_char( dst, scheme_preserves_backslashes( scheme ) ? src[0] : '/' ); 3799 append_char( dst, scheme_preserves_backslashes( scheme ) ? src[1] : '/' ); 3800 src += 2; 3801 if (scheme == URL_SCHEME_FILE && is_slash( src[0] ) && is_slash( src[1] )) 3802 { 3803 while (is_slash( *src )) 3804 ++src; 3805 } 3806 3807 hostname = src; 3808 3809 while (!scheme_char_is_hostname_separator( scheme, flags, *src )) 3810 ++src; 3811 hostname_len = src - hostname; 3812 has_hostname = true; 3813 has_initial_slash = true; 3814 } 3815 else if (scheme_char_is_separator( scheme, src[0] )) 3816 { 3817 has_initial_slash = true; 3818 3819 if (scheme == URL_SCHEME_UNKNOWN || scheme == URL_SCHEME_INVALID) 3820 { 3821 /* Special case: an unknown scheme starting with a single slash 3822 * considers the "root" to be the single slash. 3823 * Most other schemes treat it as an empty path segment instead. */ 3824 append_char( dst, *src++ ); 3825 3826 if (*src == '\\') 3827 ++src; 3828 } 3829 else if (scheme == URL_SCHEME_FILE) 3830 { 3831 src++; 3832 3833 append_string( dst, L"//", 2 ); 3834 3835 hostname_len = 0; 3836 has_hostname = true; 3837 } 3838 } 3839 else 3840 { 3841 if (scheme == URL_SCHEME_FILE) 3842 { 3843 if (is_escaped_drive_spec( src )) 3844 { 3845 append_string( dst, L"//", 2 ); 3846 hostname_len = 0; 3847 has_hostname = true; 3848 } 3849 else 3850 { 3851 if (flags & URL_FILE_USE_PATHURL) 3852 append_string( dst, L"//", 2 ); 3853 } 3854 } 3855 } 3856 3857 if (scheme == URL_SCHEME_FILE && (flags & URL_FILE_USE_PATHURL)) 3858 flags |= URL_UNESCAPE; 3859 3860 *flags_ptr = flags; 3861 3862 if (has_hostname) 3863 { 3864 if (scheme == URL_SCHEME_FILE) 3865 { 3866 bool is_drive = false; 3867 3868 if (is_slash( *src )) 3869 ++src; 3870 3871 if (hostname_len >= 2 && is_escaped_drive_spec( hostname )) 3872 { 3873 hostname_len = 0; 3874 src = hostname; 3875 is_drive = true; 3876 } 3877 else if (is_escaped_drive_spec( src )) 3878 { 3879 is_drive = true; 3880 } 3881 3882 if (pathurl) 3883 { 3884 if (hostname_len == 9 && !wcsnicmp( hostname, L"localhost", 9 )) 3885 { 3886 hostname_len = 0; 3887 if (is_slash( *src )) 3888 ++src; 3889 if (is_escaped_drive_spec( src )) 3890 is_drive = true; 3891 } 3892 3893 if (!is_drive) 3894 { 3895 if (hostname_len) 3896 { 3897 append_string( dst, L"\\\\", 2 ); 3898 append_string( dst, hostname, hostname_len ); 3899 } 3900 3901 if ((*src && *src != '?') || (flags & URL_WININET_COMPATIBILITY)) 3902 append_char( dst, get_slash_dir( scheme, flags, 0, dst )); 3903 } 3904 } 3905 else 3906 { 3907 if (hostname_len) 3908 append_string( dst, hostname, hostname_len ); 3909 append_char( dst, '/' ); 3910 } 3911 3912 if (is_drive) 3913 { 3914 /* Root starts after the first slash when file flags are in use, 3915 * but directly after the drive specification if not. */ 3916 if (pathurl) 3917 { 3918 while (!scheme_char_is_hostname_separator( scheme, flags, *src )) 3919 append_char( dst, *src++ ); 3920 if (is_slash( *src )) 3921 { 3922 append_char( dst, '\\' ); 3923 src++; 3924 } 3925 } 3926 else 3927 { 3928 append_char( dst, *src++ ); 3929 append_char( dst, *src++ ); 3930 if (is_slash( *src )) 3931 { 3932 append_char( dst, '/' ); 3933 src++; 3934 } 3935 } 3936 } 3937 } 3938 else 3939 { 3940 for (size_t i = 0; i < hostname_len; ++i) 3941 { 3942 if (scheme == URL_SCHEME_UNKNOWN || scheme == URL_SCHEME_INVALID) 3943 append_char( dst, hostname[i] ); 3944 else 3945 append_char( dst, tolower( hostname[i] )); 3946 } 3947 3948 if (*src == '/' || *src == '\\') 3949 { 3950 append_char( dst, scheme_preserves_backslashes( scheme ) ? *src : '/' ); 3951 src++; 3952 } 3953 else 3954 { 3955 append_char( dst, '/' ); 3956 } 3957 } 3958 3959 if ((src[0] == '.' && scheme_char_is_dot_separator( scheme, flags, src[1] )) || 3960 (src[0] == '.' && src[1] == '.' && scheme_char_is_dot_separator( scheme, flags, src[2] ))) 3961 { 3962 if (!scheme_is_always_relative( scheme, flags )) 3963 is_relative = true; 3964 } 3965 } 3966 3967 /* root_offset now points to the point past which we will not rewind. 3968 * If there is a hostname, it points to the character after the closing 3969 * slash. */ 3970 3971 root_offset = dst->len; 3972 3973 /* Break up the rest of the URL into the body, query, and hash parts. */ 3974 3975 src_end = src + wcslen( src ); 3976 3977 if (scheme_is_opaque( scheme )) 3978 { 3979 /* +1 for null terminator */ 3980 append_string( dst, src, src_end + 1 - src ); 3981 return; 3982 } 3983 3984 if (scheme == URL_SCHEME_FILE) 3985 { 3986 if (!pathurl) 3987 { 3988 if (src[0] == '#') 3989 hash = src; 3990 else if (is_slash( src[0] ) && src[1] == '#') 3991 hash = src + 1; 3992 3993 if (src[0] == '?') 3994 query = src; 3995 else if (is_slash( src[0] ) && src[1] == '?') 3996 query = src + 1; 3997 } 3998 else 3999 { 4000 query = wcschr( src, '?' ); 4001 } 4002 4003 if (!hash) 4004 { 4005 for (const WCHAR *p = src; p < src_end; ++p) 4006 { 4007 if (!wcsnicmp( p, L".htm#" , 5)) 4008 hash = p + 4; 4009 else if (!wcsnicmp( p, L".html#", 6 )) 4010 hash = p + 5; 4011 } 4012 } 4013 } 4014 else 4015 { 4016 query = wcschr( src, '?' ); 4017 hash = wcschr( src, '#' ); 4018 } 4019 4020 if (query) 4021 query_len = ((hash && hash > query) ? hash : src_end) - query; 4022 if (hash) 4023 hash_len = ((query && query > hash) ? query : src_end) - hash; 4024 4025 if (query) 4026 src_end = query; 4027 if (hash && hash < src_end) 4028 src_end = hash; 4029 4030 if (scheme == URL_SCHEME_UNKNOWN && !has_initial_slash) 4031 { 4032 if (!(flags & URL_DONT_SIMPLIFY) && src[0] == '.' && src_end == src + 1) 4033 src++; 4034 flags |= URL_DONT_SIMPLIFY; 4035 } 4036 4037 while (src < src_end) 4038 { 4039 bool is_dots = false; 4040 size_t len; 4041 4042 for (len = 0; src + len < src_end && !scheme_char_is_separator( scheme, src[len] ); ++len) 4043 ; 4044 4045 if (src[0] == '.' && scheme_char_is_dot_separator( scheme, flags, src[1] )) 4046 { 4047 if (!is_relative) 4048 { 4049 if (flags & URL_DONT_SIMPLIFY) 4050 { 4051 is_dots = true; 4052 } 4053 else 4054 { 4055 ++src; 4056 if (*src == '/' || *src == '\\') 4057 ++src; 4058 continue; 4059 } 4060 } 4061 } 4062 else if (src[0] == '.' && src[1] == '.' && scheme_char_is_dot_separator( scheme, flags, src[2] )) 4063 { 4064 if (!is_relative) 4065 { 4066 if (flags & URL_DONT_SIMPLIFY) 4067 { 4068 is_dots = true; 4069 } 4070 else if (dst->len == root_offset && scheme_is_always_relative( scheme, flags )) 4071 { 4072 /* We could also use is_dots here, except that we need to 4073 * update root afterwards. */ 4074 4075 append_char( dst, *src++ ); 4076 append_char( dst, *src++ ); 4077 if (*src == '/' || *src == '\\') 4078 append_char( dst, get_slash_dir( scheme, flags, *src++, dst )); 4079 else 4080 append_char( dst, get_slash_dir( scheme, flags, 0, dst )); 4081 root_offset = dst->len; 4082 continue; 4083 } 4084 else 4085 { 4086 if (dst->len > root_offset) 4087 --dst->len; /* rewind past the last slash */ 4088 4089 while (dst->len > root_offset && !scheme_char_is_separator( scheme, dst->string[dst->len - 1] )) 4090 --dst->len; 4091 4092 src += 2; 4093 if (*src == '/' || *src == '\\') 4094 ++src; 4095 continue; 4096 } 4097 } 4098 } 4099 4100 if (len) 4101 { 4102 append_string( dst, src, len ); 4103 src += len; 4104 } 4105 4106 if (*src == '?' || *src == '#' || !*src) 4107 { 4108 if (scheme == URL_SCHEME_UNKNOWN && !has_initial_slash) 4109 is_dots = false; 4110 4111 if (is_dots && scheme_is_always_relative( scheme, flags )) 4112 append_char( dst, get_slash_dir( scheme, flags, 0, dst )); 4113 } 4114 else /* slash */ 4115 { 4116 append_char( dst, get_slash_dir( scheme, flags, *src++, dst )); 4117 } 4118 } 4119 4120 /* If the source was non-empty but collapsed to an empty string, output a 4121 * single slash. */ 4122 if (!dst->len && src_end != url) 4123 append_char( dst, '/' ); 4124 4125 /* UNKNOWN and FILE schemes usually reorder the ? before the #, but others 4126 * emit them in the original order. */ 4127 if (query && hash && scheme != URL_SCHEME_FILE && scheme != URL_SCHEME_INVALID && scheme != URL_SCHEME_UNKNOWN) 4128 { 4129 if (query < hash) 4130 { 4131 append_string( dst, query, query_len ); 4132 append_string( dst, hash, hash_len ); 4133 } 4134 else 4135 { 4136 append_string( dst, hash, hash_len ); 4137 append_string( dst, query, query_len ); 4138 } 4139 } 4140 else if (!(scheme == URL_SCHEME_FILE && (flags & URL_FILE_USE_PATHURL))) 4141 { 4142 if (query) 4143 append_string( dst, query, query_len ); 4144 4145 if (hash) 4146 append_string( dst, hash, hash_len ); 4147 } 4148 4149 append_char( dst, 0 ); 4150} 4151 4152HRESULT WINAPI UrlCanonicalizeW(const WCHAR *src_url, WCHAR *canonicalized, DWORD *canonicalized_len, DWORD flags) 4153{ 4154 struct string_buffer rewritten = {0}; 4155 DWORD escape_flags; 4156 HRESULT hr = S_OK; 4157 const WCHAR *src; 4158 WCHAR *url, *dst; 4159 DWORD len; 4160 4161 TRACE("%s, %p, %p, %#lx\n", wine_dbgstr_w(src_url), canonicalized, canonicalized_len, flags); 4162 4163 if (!src_url || !canonicalized || !canonicalized_len || !*canonicalized_len) 4164 return E_INVALIDARG; 4165 4166 if (!*src_url) 4167 { 4168 *canonicalized = 0; 4169 return S_OK; 4170 } 4171 4172 /* PATHURL takes precedence. */ 4173 if (flags & URL_FILE_USE_PATHURL) 4174 flags &= ~URL_WININET_COMPATIBILITY; 4175 4176 /* strip initial and final C0 control characters and space */ 4177 src = src_url; 4178 while (*src > 0 && *src <= 0x20) 4179 ++src; 4180 len = wcslen( src ); 4181 while (len && src[len - 1] > 0 && src[len - 1] <= 0x20) 4182 --len; 4183 4184 if (!(url = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) ))) 4185 return E_OUTOFMEMORY; 4186 4187 dst = url; 4188 for (size_t i = 0; i < len; ++i) 4189 { 4190 if (src[i] != '\t' && src[i] != '\n' && src[i] != '\r') 4191 *dst++ = src[i]; 4192 } 4193 *dst++ = 0; 4194 4195 rewrite_url( &rewritten, url, &flags ); 4196 4197 if (flags & URL_UNESCAPE) 4198 { 4199 len = rewritten.len; 4200 UrlUnescapeW( rewritten.string, NULL, &len, URL_UNESCAPE_INPLACE); 4201 rewritten.len = wcslen( rewritten.string ) + 1; 4202 } 4203 4204 /* URL_ESCAPE_SEGMENT_ONLY seems to be ignored. */ 4205 escape_flags = flags & (URL_ESCAPE_UNSAFE | URL_ESCAPE_SPACES_ONLY | URL_ESCAPE_PERCENT | 4206 URL_DONT_ESCAPE_EXTRA_INFO); 4207 4208 if (escape_flags) 4209 { 4210 escape_flags &= ~URL_ESCAPE_UNSAFE; 4211 hr = UrlEscapeW( rewritten.string, canonicalized, canonicalized_len, escape_flags ); 4212 } 4213 else 4214 { 4215 /* No escaping needed, just copy the string */ 4216 if (rewritten.len <= *canonicalized_len) 4217 { 4218 memcpy( canonicalized, rewritten.string, rewritten.len * sizeof(WCHAR) ); 4219 *canonicalized_len = rewritten.len - 1; 4220 } 4221 else 4222 { 4223 hr = E_POINTER; 4224 *canonicalized_len = rewritten.len; 4225 } 4226 } 4227 4228 heap_free( rewritten.string ); 4229 heap_free( url ); 4230 4231 if (hr == S_OK) 4232 TRACE("result %s\n", wine_dbgstr_w(canonicalized)); 4233 4234 return hr; 4235} 4236 4237HRESULT WINAPI UrlApplySchemeA(const char *url, char *out, DWORD *out_len, DWORD flags) 4238{ 4239 LPWSTR inW, outW; 4240 HRESULT hr; 4241 DWORD len; 4242 4243 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_a(url), out, out_len, out_len ? *out_len : 0, flags); 4244 4245 if (!url || !out || !out_len) 4246 return E_INVALIDARG; 4247 4248 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR)); 4249 outW = inW + INTERNET_MAX_URL_LENGTH; 4250 4251 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH); 4252 len = INTERNET_MAX_URL_LENGTH; 4253 4254 hr = UrlApplySchemeW(inW, outW, &len, flags); 4255 if (hr != S_OK) 4256 { 4257 heap_free(inW); 4258 return hr; 4259 } 4260 4261 len = WideCharToMultiByte(CP_ACP, 0, outW, -1, NULL, 0, NULL, NULL); 4262 if (len > *out_len) 4263 { 4264 hr = E_POINTER; 4265 goto cleanup; 4266 } 4267 4268 WideCharToMultiByte(CP_ACP, 0, outW, -1, out, *out_len, NULL, NULL); 4269 len--; 4270 4271cleanup: 4272 *out_len = len; 4273 heap_free(inW); 4274 return hr; 4275} 4276 4277static HRESULT url_guess_scheme(const WCHAR *url, WCHAR *out, DWORD *out_len) 4278{ 4279 WCHAR reg_path[MAX_PATH], value[MAX_PATH], data[MAX_PATH]; 4280 DWORD value_len, data_len, dwType, i; 4281 WCHAR Wxx, Wyy; 4282 HKEY newkey; 4283 INT index; 4284 BOOL j; 4285 4286 MultiByteToWideChar(CP_ACP, 0, 4287 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes", -1, reg_path, MAX_PATH); 4288 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey); 4289 index = 0; 4290 while (value_len = data_len = MAX_PATH, 4291 RegEnumValueW(newkey, index, value, &value_len, 0, &dwType, (LPVOID)data, &data_len) == 0) 4292 { 4293 TRACE("guess %d %s is %s\n", index, wine_dbgstr_w(value), wine_dbgstr_w(data)); 4294 4295 j = FALSE; 4296 for (i = 0; i < value_len; ++i) 4297 { 4298 Wxx = url[i]; 4299 Wyy = value[i]; 4300 /* remember that TRUE is not-equal */ 4301 j = ChrCmpIW(Wxx, Wyy); 4302 if (j) break; 4303 } 4304 if ((i == value_len) && !j) 4305 { 4306 if (lstrlenW(data) + lstrlenW(url) + 1 > *out_len) 4307 { 4308 *out_len = lstrlenW(data) + lstrlenW(url) + 1; 4309 RegCloseKey(newkey); 4310 return E_POINTER; 4311 } 4312 lstrcpyW(out, data); 4313 lstrcatW(out, url); 4314 *out_len = lstrlenW(out); 4315 TRACE("matched and set to %s\n", wine_dbgstr_w(out)); 4316 RegCloseKey(newkey); 4317 return S_OK; 4318 } 4319 index++; 4320 } 4321 RegCloseKey(newkey); 4322 return E_FAIL; 4323} 4324 4325static HRESULT url_create_from_path(const WCHAR *path, WCHAR *url, DWORD *url_len) 4326{ 4327 PARSEDURLW parsed_url; 4328 WCHAR *new_url; 4329 DWORD needed; 4330 HRESULT hr; 4331 4332 parsed_url.cbSize = sizeof(parsed_url); 4333 if (ParseURLW(path, &parsed_url) == S_OK) 4334 { 4335 if (parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) 4336 { 4337 needed = lstrlenW(path); 4338 if (needed >= *url_len) 4339 { 4340 *url_len = needed + 1; 4341 return E_POINTER; 4342 } 4343 else 4344 { 4345 *url_len = needed; 4346 return S_FALSE; 4347 } 4348 } 4349 } 4350 4351 new_url = heap_alloc((lstrlenW(path) + 9) * sizeof(WCHAR)); /* "file:///" + path length + 1 */ 4352 lstrcpyW(new_url, L"file:"); 4353 if (is_drive_spec( path )) lstrcatW(new_url, L"///"); 4354 lstrcatW(new_url, path); 4355 hr = UrlEscapeW(new_url, url, url_len, URL_ESCAPE_PERCENT); 4356 heap_free(new_url); 4357 return hr; 4358} 4359 4360static HRESULT url_apply_default_scheme(const WCHAR *url, WCHAR *out, DWORD *length) 4361{ 4362 DWORD data_len, dwType; 4363 WCHAR data[MAX_PATH]; 4364 HKEY newkey; 4365 4366 /* get and prepend default */ 4367 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix", 4368 0, 1, &newkey); 4369 data_len = sizeof(data); 4370 RegQueryValueExW(newkey, NULL, 0, &dwType, (BYTE *)data, &data_len); 4371 RegCloseKey(newkey); 4372 if (lstrlenW(data) + lstrlenW(url) + 1 > *length) 4373 { 4374 *length = lstrlenW(data) + lstrlenW(url) + 1; 4375 return E_POINTER; 4376 } 4377 lstrcpyW(out, data); 4378 lstrcatW(out, url); 4379 *length = lstrlenW(out); 4380 TRACE("used default %s\n", wine_dbgstr_w(out)); 4381 return S_OK; 4382} 4383 4384HRESULT WINAPI UrlApplySchemeW(const WCHAR *url, WCHAR *out, DWORD *length, DWORD flags) 4385{ 4386 PARSEDURLW in_scheme; 4387 DWORD res1; 4388 HRESULT hr; 4389 4390 TRACE("%s, %p, %p:out size %ld, %#lx\n", wine_dbgstr_w(url), out, length, length ? *length : 0, flags); 4391 4392 if (!url || !out || !length) 4393 return E_INVALIDARG; 4394 4395 if (flags & URL_APPLY_GUESSFILE) 4396 { 4397 if ((*length > 1 && ':' == url[1]) || PathIsUNCW(url)) 4398 { 4399 res1 = *length; 4400 hr = url_create_from_path(url, out, &res1); 4401 if (hr == S_OK || hr == E_POINTER) 4402 { 4403 *length = res1; 4404 return hr; 4405 } 4406 else if (hr == S_FALSE) 4407 { 4408 return hr; 4409 } 4410 } 4411 } 4412 4413 in_scheme.cbSize = sizeof(in_scheme); 4414 /* See if the base has a scheme */ 4415 res1 = ParseURLW(url, &in_scheme); 4416 if (res1) 4417 { 4418 /* no scheme in input, need to see if we need to guess */ 4419 if (flags & URL_APPLY_GUESSSCHEME) 4420 { 4421 if ((hr = url_guess_scheme(url, out, length)) != E_FAIL) 4422 return hr; 4423 } 4424 } 4425 4426 /* If we are here, then either invalid scheme, 4427 * or no scheme and can't/failed guess. 4428 */ 4429 if ((((res1 == 0) && (flags & URL_APPLY_FORCEAPPLY)) || ((res1 != 0)) ) && (flags & URL_APPLY_DEFAULT)) 4430 return url_apply_default_scheme(url, out, length); 4431 4432 return S_FALSE; 4433} 4434 4435INT WINAPI UrlCompareA(const char *url1, const char *url2, BOOL ignore_slash) 4436{ 4437 INT ret, len, len1, len2; 4438 4439 if (!ignore_slash) 4440 return strcmp(url1, url2); 4441 len1 = strlen(url1); 4442 if (url1[len1-1] == '/') len1--; 4443 len2 = strlen(url2); 4444 if (url2[len2-1] == '/') len2--; 4445 if (len1 == len2) 4446 return strncmp(url1, url2, len1); 4447 len = min(len1, len2); 4448 ret = strncmp(url1, url2, len); 4449 if (ret) return ret; 4450 if (len1 > len2) return 1; 4451 return -1; 4452} 4453 4454INT WINAPI UrlCompareW(const WCHAR *url1, const WCHAR *url2, BOOL ignore_slash) 4455{ 4456 size_t len, len1, len2; 4457 INT ret; 4458 4459 if (!ignore_slash) 4460 return lstrcmpW(url1, url2); 4461 len1 = lstrlenW(url1); 4462 if (url1[len1-1] == '/') len1--; 4463 len2 = lstrlenW(url2); 4464 if (url2[len2-1] == '/') len2--; 4465 if (len1 == len2) 4466 return wcsncmp(url1, url2, len1); 4467 len = min(len1, len2); 4468 ret = wcsncmp(url1, url2, len); 4469 if (ret) return ret; 4470 if (len1 > len2) return 1; 4471 return -1; 4472} 4473 4474HRESULT WINAPI UrlFixupW(const WCHAR *url, WCHAR *translatedUrl, DWORD maxChars) 4475{ 4476 DWORD srcLen; 4477 4478 FIXME("%s, %p, %ld stub\n", wine_dbgstr_w(url), translatedUrl, maxChars); 4479 4480 if (!url) 4481 return E_FAIL; 4482 4483 srcLen = lstrlenW(url) + 1; 4484 4485 /* For now just copy the URL directly */ 4486 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen); 4487 4488 return S_OK; 4489} 4490 4491const char * WINAPI UrlGetLocationA(const char *url) 4492{ 4493 PARSEDURLA base; 4494 4495 base.cbSize = sizeof(base); 4496 if (ParseURLA(url, &base) != S_OK) return NULL; /* invalid scheme */ 4497 4498 /* if scheme is file: then never return pointer */ 4499 if (!strncmp(base.pszProtocol, "file", min(4, base.cchProtocol))) 4500 return NULL; 4501 4502 /* Look for '#' and return its addr */ 4503 return strchr(base.pszSuffix, '#'); 4504} 4505 4506const WCHAR * WINAPI UrlGetLocationW(const WCHAR *url) 4507{ 4508 PARSEDURLW base; 4509 4510 base.cbSize = sizeof(base); 4511 if (ParseURLW(url, &base) != S_OK) return NULL; /* invalid scheme */ 4512 4513 /* if scheme is file: then never return pointer */ 4514 if (!wcsncmp(base.pszProtocol, L"file", min(4, base.cchProtocol))) 4515 return NULL; 4516 4517 /* Look for '#' and return its addr */ 4518 return wcschr(base.pszSuffix, '#'); 4519} 4520 4521HRESULT WINAPI UrlGetPartA(const char *url, char *out, DWORD *out_len, DWORD part, DWORD flags) 4522{ 4523 LPWSTR inW, outW; 4524 DWORD len, len2; 4525 HRESULT hr; 4526 4527 if (!url || !out || !out_len || !*out_len) 4528 return E_INVALIDARG; 4529 4530 inW = heap_alloc(2 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR)); 4531 outW = inW + INTERNET_MAX_URL_LENGTH; 4532 4533 MultiByteToWideChar(CP_ACP, 0, url, -1, inW, INTERNET_MAX_URL_LENGTH); 4534 4535 len = INTERNET_MAX_URL_LENGTH; 4536 hr = UrlGetPartW(inW, outW, &len, part, flags); 4537 if (FAILED(hr)) 4538 { 4539 heap_free(inW); 4540 return hr; 4541 } 4542 4543 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, NULL, 0, NULL, NULL); 4544 if (len2 > *out_len) 4545 { 4546 *out_len = len2; 4547 heap_free(inW); 4548 return E_POINTER; 4549 } 4550 len2 = WideCharToMultiByte(CP_ACP, 0, outW, len + 1, out, *out_len, NULL, NULL); 4551 *out_len = len2 - 1; 4552 heap_free(inW); 4553 if (hr == S_OK && !*out_len) hr = S_FALSE; 4554 return hr; 4555} 4556 4557static const WCHAR *parse_url_element( const WCHAR *url, const WCHAR *separators ) 4558{ 4559 const WCHAR *p; 4560 4561 if ((p = wcspbrk( url, separators ))) 4562 return p; 4563 return url + wcslen( url ); 4564} 4565 4566static void parse_url( const WCHAR *url, struct parsed_url *pl ) 4567{ 4568 const WCHAR *work; 4569 4570 memset(pl, 0, sizeof(*pl)); 4571 pl->scheme = url; 4572 work = parse_scheme( pl->scheme ); 4573 if (work < url + 2 || *work != ':') return; 4574 pl->scheme_len = work - pl->scheme; 4575 work++; 4576 pl->scheme_number = get_scheme_code(pl->scheme, pl->scheme_len); 4577 if (!is_slash( work[0] ) || !is_slash( work[1] )) 4578 { 4579 if (pl->scheme_number != URL_SCHEME_FILE) 4580 pl->scheme_number = URL_SCHEME_UNKNOWN; 4581 return; 4582 } 4583 work += 2; 4584 4585 if (pl->scheme_number != URL_SCHEME_FILE) 4586 { 4587 pl->username = work; 4588 work = parse_url_element( pl->username, L":@/\\?#" ); 4589 pl->username_len = work - pl->username; 4590 if (*work == ':') 4591 { 4592 pl->password = work + 1; 4593 work = parse_url_element( pl->password, L"@/\\?#" ); 4594 pl->password_len = work - pl->password; 4595 if (*work == '@') 4596 { 4597 work++; 4598 } 4599 else 4600 { 4601 /* what we just parsed must be the hostname and port 4602 * so reset pointers and clear then let it parse */ 4603 pl->username_len = pl->password_len = 0; 4604 work = pl->username; 4605 pl->username = pl->password = 0; 4606 } 4607 } 4608 else if (*work == '@') 4609 { 4610 /* no password */ 4611 pl->password_len = 0; 4612 pl->password = 0; 4613 work++; 4614 } 4615 else 4616 { 4617 /* what was parsed was hostname, so reset pointers and let it parse */ 4618 pl->username_len = pl->password_len = 0; 4619 work = pl->username; 4620 pl->username = pl->password = 0; 4621 } 4622 } 4623 4624 pl->hostname = work; 4625 if (pl->scheme_number == URL_SCHEME_FILE) 4626 { 4627 work = parse_url_element( pl->hostname, L"/\\?#" ); 4628 pl->hostname_len = work - pl->hostname; 4629 if (pl->hostname_len >= 2 && pl->hostname[1] == ':') 4630 pl->hostname_len = 0; 4631 } 4632 else 4633 { 4634 work = parse_url_element( pl->hostname, L":/\\?#" ); 4635 pl->hostname_len = work - pl->hostname; 4636 4637 if (*work == ':') 4638 { 4639 pl->port = work + 1; 4640 work = parse_url_element( pl->port, L"/\\?#" ); 4641 pl->port_len = work - pl->port; 4642 } 4643 } 4644 4645 if ((pl->query = wcschr( work, '?' ))) 4646 { 4647 ++pl->query; 4648 pl->query_len = lstrlenW(pl->query); 4649 } 4650} 4651 4652HRESULT WINAPI UrlGetPartW(const WCHAR *url, WCHAR *out, DWORD *out_len, DWORD part, DWORD flags) 4653{ 4654 LPCWSTR addr, schaddr; 4655 struct parsed_url pl; 4656 DWORD size, schsize; 4657 4658 TRACE("%s, %p, %p(%ld), %#lx, %#lx\n", wine_dbgstr_w(url), out, out_len, *out_len, part, flags); 4659 4660 if (!url || !out || !out_len || !*out_len) 4661 return E_INVALIDARG; 4662 4663 parse_url(url, &pl); 4664 4665 switch (pl.scheme_number) 4666 { 4667 case URL_SCHEME_FTP: 4668 case URL_SCHEME_GOPHER: 4669 case URL_SCHEME_HTTP: 4670 case URL_SCHEME_HTTPS: 4671 case URL_SCHEME_TELNET: 4672 case URL_SCHEME_NEWS: 4673 case URL_SCHEME_NNTP: 4674 case URL_SCHEME_SNEWS: 4675 break; 4676 4677 case URL_SCHEME_FILE: 4678 if (part != URL_PART_SCHEME && part != URL_PART_QUERY && part != URL_PART_HOSTNAME) 4679 return E_FAIL; 4680 break; 4681 4682 default: 4683 if (part != URL_PART_SCHEME && part != URL_PART_QUERY) 4684 return E_FAIL; 4685 } 4686 4687 switch (part) 4688 { 4689 case URL_PART_SCHEME: 4690 flags &= ~URL_PARTFLAG_KEEPSCHEME; 4691 addr = pl.scheme; 4692 size = pl.scheme_len; 4693 break; 4694 4695 case URL_PART_HOSTNAME: 4696 addr = pl.hostname; 4697 size = pl.hostname_len; 4698 break; 4699 4700 case URL_PART_USERNAME: 4701 if (!pl.username) 4702 return E_INVALIDARG; 4703 addr = pl.username; 4704 size = pl.username_len; 4705 break; 4706 4707 case URL_PART_PASSWORD: 4708 if (!pl.password) 4709 return E_INVALIDARG; 4710 addr = pl.password; 4711 size = pl.password_len; 4712 break; 4713 4714 case URL_PART_PORT: 4715 if (!pl.port) 4716 return E_INVALIDARG; 4717 addr = pl.port; 4718 size = pl.port_len; 4719 break; 4720 4721 case URL_PART_QUERY: 4722 flags &= ~URL_PARTFLAG_KEEPSCHEME; 4723 addr = pl.query; 4724 size = pl.query_len; 4725 break; 4726 4727 default: 4728 return E_INVALIDARG; 4729 } 4730 4731 if (flags == URL_PARTFLAG_KEEPSCHEME && pl.scheme_number != URL_SCHEME_FILE) 4732 { 4733 if (!pl.scheme || !pl.scheme_len) 4734 return E_FAIL; 4735 schaddr = pl.scheme; 4736 schsize = pl.scheme_len; 4737 if (*out_len < schsize + size + 2) 4738 { 4739 *out_len = schsize + size + 2; 4740 return E_POINTER; 4741 } 4742 memcpy(out, schaddr, schsize*sizeof(WCHAR)); 4743 out[schsize] = ':'; 4744 memcpy(out + schsize+1, addr, size*sizeof(WCHAR)); 4745 out[schsize+1+size] = 0; 4746 *out_len = schsize + 1 + size; 4747 } 4748 else 4749 { 4750 if (*out_len < size + 1) 4751 { 4752 *out_len = size + 1; 4753 return E_POINTER; 4754 } 4755 4756 if (part == URL_PART_SCHEME) 4757 { 4758 unsigned int i; 4759 4760 for (i = 0; i < size; ++i) 4761 out[i] = tolower( addr[i] ); 4762 } 4763 else 4764 { 4765 memcpy( out, addr, size * sizeof(WCHAR) ); 4766 } 4767 out[size] = 0; 4768 *out_len = size; 4769 } 4770 TRACE("len=%ld %s\n", *out_len, wine_dbgstr_w(out)); 4771 4772 return S_OK; 4773} 4774 4775BOOL WINAPI UrlIsA(const char *url, URLIS Urlis) 4776{ 4777 const char *last; 4778 PARSEDURLA base; 4779 4780 TRACE("%s, %d\n", debugstr_a(url), Urlis); 4781 4782 if (!url) 4783 return FALSE; 4784 4785 switch (Urlis) { 4786 4787 case URLIS_OPAQUE: 4788 base.cbSize = sizeof(base); 4789 if (ParseURLA(url, &base) != S_OK) return FALSE; /* invalid scheme */ 4790 return scheme_is_opaque( base.nScheme ); 4791 4792 case URLIS_FILEURL: 4793 return (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, url, 5, "file:", 5) == CSTR_EQUAL); 4794 4795 case URLIS_DIRECTORY: 4796 last = url + strlen(url) - 1; 4797 return (last >= url && (*last == '/' || *last == '\\' )); 4798 4799 case URLIS_URL: 4800 return PathIsURLA(url); 4801 4802 case URLIS_NOHISTORY: 4803 case URLIS_APPLIABLE: 4804 case URLIS_HASQUERY: 4805 default: 4806 FIXME("(%s %d): stub\n", debugstr_a(url), Urlis); 4807 } 4808 4809 return FALSE; 4810} 4811 4812BOOL WINAPI UrlIsW(const WCHAR *url, URLIS Urlis) 4813{ 4814 const WCHAR *last; 4815 PARSEDURLW base; 4816 4817 TRACE("%s, %d\n", debugstr_w(url), Urlis); 4818 4819 if (!url) 4820 return FALSE; 4821 4822 switch (Urlis) 4823 { 4824 case URLIS_OPAQUE: 4825 base.cbSize = sizeof(base); 4826 if (ParseURLW(url, &base) != S_OK) return FALSE; /* invalid scheme */ 4827 switch (base.nScheme) 4828 { 4829 case URL_SCHEME_MAILTO: 4830 case URL_SCHEME_SHELL: 4831 case URL_SCHEME_JAVASCRIPT: 4832 case URL_SCHEME_VBSCRIPT: 4833 case URL_SCHEME_ABOUT: 4834 return TRUE; 4835 } 4836 return FALSE; 4837 4838 case URLIS_FILEURL: 4839 return !wcsnicmp( url, L"file:", 5 ); 4840 4841 case URLIS_DIRECTORY: 4842 last = url + lstrlenW(url) - 1; 4843 return (last >= url && (*last == '/' || *last == '\\')); 4844 4845 case URLIS_URL: 4846 return PathIsURLW(url); 4847 4848 case URLIS_NOHISTORY: 4849 case URLIS_APPLIABLE: 4850 case URLIS_HASQUERY: 4851 default: 4852 FIXME("(%s %d): stub\n", debugstr_w(url), Urlis); 4853 } 4854 4855 return FALSE; 4856} 4857 4858BOOL WINAPI UrlIsOpaqueA(const char *url) 4859{ 4860 return UrlIsA(url, URLIS_OPAQUE); 4861} 4862 4863BOOL WINAPI UrlIsOpaqueW(const WCHAR *url) 4864{ 4865 return UrlIsW(url, URLIS_OPAQUE); 4866} 4867 4868BOOL WINAPI UrlIsNoHistoryA(const char *url) 4869{ 4870 return UrlIsA(url, URLIS_NOHISTORY); 4871} 4872 4873BOOL WINAPI UrlIsNoHistoryW(const WCHAR *url) 4874{ 4875 return UrlIsW(url, URLIS_NOHISTORY); 4876} 4877 4878HRESULT WINAPI UrlCreateFromPathA(const char *path, char *url, DWORD *url_len, DWORD reserved) 4879{ 4880 WCHAR bufW[INTERNET_MAX_URL_LENGTH]; 4881 DWORD lenW = ARRAY_SIZE(bufW), lenA; 4882 UNICODE_STRING pathW; 4883 WCHAR *urlW = bufW; 4884 HRESULT hr; 4885 4886 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, path)) 4887 return E_INVALIDARG; 4888 4889 if ((hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved)) == E_POINTER) 4890 { 4891 urlW = heap_alloc(lenW * sizeof(WCHAR)); 4892 hr = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, reserved); 4893 } 4894 4895 if (SUCCEEDED(hr)) 4896 { 4897 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR)); 4898 if (*url_len > lenA) 4899 { 4900 RtlUnicodeToMultiByteN(url, *url_len - 1, &lenA, urlW, lenW * sizeof(WCHAR)); 4901 url[lenA] = 0; 4902 *url_len = lenA; 4903 } 4904 else 4905 { 4906 *url_len = lenA + 1; 4907 hr = E_POINTER; 4908 } 4909 } 4910 if (urlW != bufW) 4911 heap_free(urlW); 4912 RtlFreeUnicodeString(&pathW); 4913 return hr; 4914} 4915 4916HRESULT WINAPI UrlCreateFromPathW(const WCHAR *path, WCHAR *url, DWORD *url_len, DWORD reserved) 4917{ 4918 HRESULT hr; 4919 4920 TRACE("%s, %p, %p, %#lx\n", debugstr_w(path), url, url_len, reserved); 4921 4922 if (reserved || !url || !url_len) 4923 return E_INVALIDARG; 4924 4925 hr = url_create_from_path(path, url, url_len); 4926 if (hr == S_FALSE) 4927 lstrcpyW(url, path); 4928 4929 return hr; 4930} 4931 4932HRESULT WINAPI UrlCombineA(const char *base, const char *relative, char *combined, DWORD *combined_len, DWORD flags) 4933{ 4934 WCHAR *baseW, *relativeW, *combinedW; 4935 DWORD len, len2; 4936 HRESULT hr; 4937 4938 TRACE("%s, %s, %ld, %#lx\n", debugstr_a(base), debugstr_a(relative), combined_len ? *combined_len : 0, flags); 4939 4940 if (!base || !relative || !combined_len) 4941 return E_INVALIDARG; 4942 4943 baseW = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR)); 4944 relativeW = baseW + INTERNET_MAX_URL_LENGTH; 4945 combinedW = relativeW + INTERNET_MAX_URL_LENGTH; 4946 4947 MultiByteToWideChar(CP_ACP, 0, base, -1, baseW, INTERNET_MAX_URL_LENGTH); 4948 MultiByteToWideChar(CP_ACP, 0, relative, -1, relativeW, INTERNET_MAX_URL_LENGTH); 4949 len = *combined_len; 4950 4951 hr = UrlCombineW(baseW, relativeW, combined ? combinedW : NULL, &len, flags); 4952 if (hr != S_OK) 4953 { 4954 *combined_len = len; 4955 heap_free(baseW); 4956 return hr; 4957 } 4958 4959 len2 = WideCharToMultiByte(CP_ACP, 0, combinedW, len, NULL, 0, NULL, NULL); 4960 if (len2 > *combined_len) 4961 { 4962 *combined_len = len2; 4963 heap_free(baseW); 4964 return E_POINTER; 4965 } 4966 WideCharToMultiByte(CP_ACP, 0, combinedW, len+1, combined, *combined_len + 1, NULL, NULL); 4967 *combined_len = len2; 4968 heap_free(baseW); 4969 return S_OK; 4970} 4971 4972HRESULT WINAPI UrlCombineW(const WCHAR *baseW, const WCHAR *relativeW, WCHAR *combined, DWORD *combined_len, DWORD flags) 4973{ 4974 DWORD i, len, process_case = 0, myflags, sizeloc = 0; 4975 LPWSTR work, preliminary, mbase, canonicalized; 4976 PARSEDURLW base, relative; 4977 HRESULT hr; 4978 4979 TRACE("%s, %s, %ld, %#lx\n", debugstr_w(baseW), debugstr_w(relativeW), combined_len ? *combined_len : 0, flags); 4980 4981 if (!baseW || !relativeW || !combined_len) 4982 return E_INVALIDARG; 4983 4984 base.cbSize = sizeof(base); 4985 relative.cbSize = sizeof(relative); 4986 4987 /* Get space for duplicates of the input and the output */ 4988 preliminary = heap_alloc(3 * INTERNET_MAX_URL_LENGTH * sizeof(WCHAR)); 4989 mbase = preliminary + INTERNET_MAX_URL_LENGTH; 4990 canonicalized = mbase + INTERNET_MAX_URL_LENGTH; 4991 *preliminary = '\0'; 4992 4993 /* Canonicalize the base input prior to looking for the scheme */ 4994 myflags = flags & (URL_DONT_SIMPLIFY | URL_UNESCAPE); 4995 len = INTERNET_MAX_URL_LENGTH; 4996 UrlCanonicalizeW(baseW, mbase, &len, myflags); 4997 4998 /* See if the base has a scheme */ 4999 if (ParseURLW(mbase, &base) != S_OK) 5000 { 5001 /* If base has no scheme return relative. */ 5002 TRACE("no scheme detected in Base\n"); 5003 process_case = 1; 5004 } 5005 else do 5006 { 5007 BOOL manual_search = FALSE; 5008 5009 work = (LPWSTR)base.pszProtocol; 5010 for (i = 0; i < base.cchProtocol; ++i) 5011 work[i] = RtlDowncaseUnicodeChar(work[i]); 5012 5013 /* mk is a special case */ 5014 if (base.nScheme == URL_SCHEME_MK) 5015 { 5016 WCHAR *ptr = wcsstr(base.pszSuffix, L"::"); 5017 if (ptr) 5018 { 5019 int delta; 5020 5021 ptr += 2; 5022 delta = ptr-base.pszSuffix; 5023 base.cchProtocol += delta; 5024 base.pszSuffix += delta; 5025 base.cchSuffix -= delta; 5026 } 5027 } 5028 else 5029 { 5030 /* get size of location field (if it exists) */ 5031 work = (LPWSTR)base.pszSuffix; 5032 sizeloc = 0; 5033 if (*work++ == '/') 5034 { 5035 if (*work++ == '/') 5036 { 5037 /* At this point have start of location and 5038 * it ends at next '/' or end of string. 5039 */ 5040 while (*work && (*work != '/')) work++; 5041 sizeloc = (DWORD)(work - base.pszSuffix); 5042 } 5043 } 5044 } 5045 5046 /* If there is a '?', then the remaining part can only contain a 5047 * query string or fragment, so start looking for the last leaf 5048 * from the '?'. Otherwise, if there is a '#' and the characters 5049 * immediately preceding it are ".htm[l]", then begin looking for 5050 * the last leaf starting from the '#'. Otherwise the '#' is not 5051 * meaningful and just start looking from the end. */ 5052 if ((work = wcspbrk(base.pszSuffix + sizeloc, L"#?"))) 5053 { 5054 if (*work == '?' || base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS) 5055 manual_search = TRUE; 5056 else if (work - base.pszSuffix > 4) 5057 { 5058 if (!wcsnicmp(work - 4, L".htm", 4)) manual_search = TRUE; 5059 } 5060 5061 if (!manual_search && work - base.pszSuffix > 5) 5062 { 5063 if (!wcsnicmp(work - 5, L".html", 5)) manual_search = TRUE; 5064 } 5065 } 5066 5067 if (manual_search) 5068 { 5069 /* search backwards starting from the current position */ 5070 while (*work != '/' && work > base.pszSuffix + sizeloc) 5071 --work; 5072 base.cchSuffix = work - base.pszSuffix + 1; 5073 } 5074 else 5075 { 5076 /* search backwards starting from the end of the string */ 5077 work = wcsrchr((base.pszSuffix+sizeloc), '/'); 5078 if (work) 5079 { 5080 len = (DWORD)(work - base.pszSuffix + 1); 5081 base.cchSuffix = len; 5082 } 5083 else 5084 base.cchSuffix = sizeloc; 5085 } 5086 5087 /* 5088 * At this point: 5089 * .pszSuffix points to location (starting with '//') 5090 * .cchSuffix length of location (above) and rest less the last 5091 * leaf (if any) 5092 * sizeloc length of location (above) up to but not including 5093 * the last '/' 5094 */ 5095 5096 if (ParseURLW(relativeW, &relative) != S_OK) 5097 { 5098 /* No scheme in relative */ 5099 TRACE("no scheme detected in Relative\n"); 5100 relative.pszSuffix = relativeW; /* case 3,4,5 depends on this */ 5101 relative.cchSuffix = lstrlenW( relativeW ); 5102 if (*relativeW == ':') 5103 { 5104 /* Case that is either left alone or uses base. */ 5105 if (flags & URL_PLUGGABLE_PROTOCOL) 5106 { 5107 process_case = 5; 5108 break; 5109 } 5110 process_case = 1; 5111 break; 5112 } 5113 if (is_drive_spec( relativeW )) 5114 { 5115 /* case that becomes "file:///" */ 5116 lstrcpyW(preliminary, L"file:///"); 5117 process_case = 1; 5118 break; 5119 } 5120 if ((relativeW[0] == '/' || relativeW[0] == '\\') && 5121 (relativeW[1] == '/' || relativeW[1] == '\\')) 5122 { 5123 /* Relative has location and the rest. */ 5124 process_case = 3; 5125 break; 5126 } 5127 if (*relativeW == '/' || *relativeW == '\\') 5128 { 5129 /* Relative is root to location. */ 5130 process_case = 4; 5131 break; 5132 } 5133 if (*relativeW == '#') 5134 { 5135 if (!(work = wcschr(base.pszSuffix+base.cchSuffix, '#'))) 5136 work = (LPWSTR)base.pszSuffix + lstrlenW(base.pszSuffix); 5137 5138 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR)); 5139 preliminary[work-base.pszProtocol] = '\0'; 5140 process_case = 1; 5141 break; 5142 } 5143 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3; 5144 break; 5145 } 5146 else 5147 { 5148 work = (LPWSTR)relative.pszProtocol; 5149 for (i = 0; i < relative.cchProtocol; ++i) 5150 work[i] = RtlDowncaseUnicodeChar(work[i]); 5151 } 5152 5153 /* Handle cases where relative has scheme. */ 5154 if ((base.cchProtocol == relative.cchProtocol) && !wcsncmp(base.pszProtocol, relative.pszProtocol, base.cchProtocol)) 5155 { 5156 /* since the schemes are the same */ 5157 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/') 5158 { 5159 /* Relative replaces location and what follows. */ 5160 process_case = 3; 5161 break; 5162 } 5163 if (*relative.pszSuffix == '/') 5164 { 5165 /* Relative is root to location */ 5166 process_case = 4; 5167 break; 5168 } 5169 /* replace either just location if base's location starts with a 5170 * slash or otherwise everything */ 5171 process_case = (*base.pszSuffix == '/') ? 5 : 1; 5172 break; 5173 } 5174 5175 if (*relative.pszSuffix == '/' && *(relative.pszSuffix+1) == '/') 5176 { 5177 /* Relative replaces scheme, location, and following and handles PLUGGABLE */ 5178 process_case = 2; 5179 break; 5180 } 5181 process_case = 1; 5182 break; 5183 } while (FALSE); /* a little trick to allow easy exit from nested if's */ 5184 5185 hr = S_OK; 5186 switch (process_case) 5187 { 5188 case 1: 5189 /* Return relative appended to whatever is in combined (which may the string "file:///" */ 5190 lstrcatW(preliminary, relativeW); 5191 break; 5192 5193 case 2: 5194 /* Relative replaces scheme and location */ 5195 lstrcpyW(preliminary, relativeW); 5196 break; 5197 5198 case 3: 5199 /* Return the base scheme with relative. Basically keeps the scheme and replaces the domain and following. */ 5200 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR)); 5201 work = preliminary + base.cchProtocol + 1; 5202 lstrcpyW(work, relative.pszSuffix); 5203 break; 5204 5205 case 4: 5206 /* Return the base scheme and location but everything after the location is relative. (Replace document from root on.) */ 5207 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR)); 5208 work = preliminary + base.cchProtocol + 1 + sizeloc; 5209 if (flags & URL_PLUGGABLE_PROTOCOL) 5210 *(work++) = '/'; 5211 lstrcpyW(work, relative.pszSuffix); 5212 break; 5213 5214 case 5: 5215 /* Return the base without its document (if any) and append relative after its scheme. */ 5216 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1 + base.cchSuffix)*sizeof(WCHAR)); 5217 work = preliminary + base.cchProtocol + 1 + base.cchSuffix - 1; 5218 if (*work++ != '/') 5219 *(work++) = '/'; 5220 lstrcpyW(work, relative.pszSuffix); 5221 break; 5222 5223 default: 5224 FIXME("Unexpected case %ld.\n", process_case); 5225 hr = E_INVALIDARG; 5226 } 5227 5228 if (hr == S_OK) 5229 { 5230 if (*combined_len == 0) 5231 *combined_len = 1; 5232 hr = UrlCanonicalizeW(preliminary, canonicalized, combined_len, flags & ~URL_FILE_USE_PATHURL); 5233 if (SUCCEEDED(hr) && combined) 5234 lstrcpyW( combined, canonicalized ); 5235 5236 TRACE("return-%ld len=%ld, %s\n", process_case, *combined_len, debugstr_w(combined)); 5237 } 5238 5239 heap_free(preliminary); 5240 return hr; 5241} 5242 5243HRESULT WINAPI HashData(const unsigned char *src, DWORD src_len, unsigned char *dest, DWORD dest_len) 5244{ 5245 INT src_count = src_len - 1, dest_count = dest_len - 1; 5246 5247 if (!src || !dest) 5248 return E_INVALIDARG; 5249 5250 while (dest_count >= 0) 5251 { 5252 dest[dest_count] = (dest_count & 0xff); 5253 dest_count--; 5254 } 5255 5256 while (src_count >= 0) 5257 { 5258 dest_count = dest_len - 1; 5259 while (dest_count >= 0) 5260 { 5261 dest[dest_count] = hashdata_lookup[src[src_count] ^ dest[dest_count]]; 5262 dest_count--; 5263 } 5264 src_count--; 5265 } 5266 5267 return S_OK; 5268} 5269 5270HRESULT WINAPI UrlHashA(const char *url, unsigned char *dest, DWORD dest_len) 5271{ 5272 __TRY 5273 { 5274 HashData((const BYTE *)url, (int)strlen(url), dest, dest_len); 5275 } 5276 __EXCEPT_PAGE_FAULT 5277 { 5278 return E_INVALIDARG; 5279 } 5280 __ENDTRY 5281 return S_OK; 5282} 5283 5284HRESULT WINAPI UrlHashW(const WCHAR *url, unsigned char *dest, DWORD dest_len) 5285{ 5286 char urlA[MAX_PATH]; 5287 5288 TRACE("%s, %p, %ld\n", debugstr_w(url), dest, dest_len); 5289 5290 __TRY 5291 { 5292 WideCharToMultiByte(CP_ACP, 0, url, -1, urlA, MAX_PATH, NULL, NULL); 5293 HashData((const BYTE *)urlA, (int)strlen(urlA), dest, dest_len); 5294 } 5295 __EXCEPT_PAGE_FAULT 5296 { 5297 return E_INVALIDARG; 5298 } 5299 __ENDTRY 5300 return S_OK; 5301} 5302 5303BOOL WINAPI IsInternetESCEnabled(void) 5304{ 5305 FIXME(": stub\n"); 5306 return FALSE; 5307} 5308 5309#endif // !STATIC_PATHCCH