1 /* 2 * Copyright 2018 Nikolay Sivov 3 * Copyright 2018 Zhiyi Zhang 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 18 */ 19 20 #include <stdarg.h> 21 #include <string.h> 22 23 /* Wine code is still stuck in the past... */ 24 #ifdef __REACTOS__ 25 #define wcsnicmp _wcsnicmp 26 #endif 27 28 #include <windef.h> 29 #include <winbase.h> 30 31 /* The PathCch functions use size_t, but Wine's implementation uses SIZE_T, 32 * so temporarily change the define'd SIZE_T type to the compatible one... */ 33 #ifdef __REACTOS__ 34 #undef SIZE_T 35 #define SIZE_T size_t 36 #endif 37 38 /* This is the static implementation of the PathCch functions */ 39 #define STATIC_PATHCCH 40 #ifdef __GNUC__ // GCC doesn't support #pragma deprecated() 41 #undef DEPRECATE_SUPPORTED 42 #endif 43 #include <pathcch.h> 44 45 #include <strsafe.h> 46 47 #include "wine/debug.h" 48 49 WINE_DEFAULT_DEBUG_CHANNEL(path); 50 51 #ifdef __REACTOS__ 52 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || (DLL_EXPORT_VERSION < _WIN32_WINNT_VISTA) 53 /* wcsnlen is an NT6+ function. To cover all cases, use a private implementation */ 54 static inline size_t hacked_wcsnlen(const wchar_t* str, size_t size) 55 { 56 StringCchLengthW(str, size, &size); 57 return size; 58 } 59 #define wcsnlen hacked_wcsnlen 60 #endif 61 #endif /* __REACTOS__ */ 62 63 static BOOL is_drive_spec( const WCHAR *str ) 64 { 65 return isalpha( str[0] ) && str[1] == ':'; 66 } 67 68 #if 0 69 static BOOL is_escaped_drive_spec( const WCHAR *str ) 70 { 71 return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|'); 72 } 73 #endif 74 75 static BOOL is_prefixed_unc(const WCHAR *string) 76 { 77 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 ); 78 } 79 80 static BOOL is_prefixed_disk(const WCHAR *string) 81 { 82 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 ); 83 } 84 85 static BOOL is_prefixed_volume(const WCHAR *string) 86 { 87 const WCHAR *guid; 88 INT i = 0; 89 90 if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE; 91 92 guid = string + 10; 93 94 while (i <= 37) 95 { 96 switch (i) 97 { 98 case 0: 99 if (guid[i] != '{') return FALSE; 100 break; 101 case 9: 102 case 14: 103 case 19: 104 case 24: 105 if (guid[i] != '-') return FALSE; 106 break; 107 case 37: 108 if (guid[i] != '}') return FALSE; 109 break; 110 default: 111 if (!isxdigit(guid[i])) return FALSE; 112 break; 113 } 114 i++; 115 } 116 117 return TRUE; 118 } 119 120 /* Get the next character beyond end of the segment. 121 Return TRUE if the last segment ends with a backslash */ 122 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment) 123 { 124 while (*next && *next != '\\') next++; 125 if (*next == '\\') 126 { 127 *next_segment = next + 1; 128 return TRUE; 129 } 130 else 131 { 132 *next_segment = next; 133 return FALSE; 134 } 135 } 136 137 /* Find the last character of the root in a path, if there is one, without any segments */ 138 static const WCHAR *get_root_end(const WCHAR *path) 139 { 140 /* Find path root */ 141 if (is_prefixed_volume(path)) 142 return path[48] == '\\' ? path + 48 : path + 47; 143 else if (is_prefixed_unc(path)) 144 return path + 7; 145 else if (is_prefixed_disk(path)) 146 return path[6] == '\\' ? path + 6 : path + 5; 147 /* \\ */ 148 else if (path[0] == '\\' && path[1] == '\\') 149 return path + 1; 150 /* \ */ 151 else if (path[0] == '\\') 152 return path; 153 /* X:\ */ 154 else if (is_drive_spec( path )) 155 return path[2] == '\\' ? path + 2 : path + 1; 156 else 157 return NULL; 158 } 159 160 #ifdef __REACTOS__ 161 HRESULT 162 APIENTRY 163 PathAllocCanonicalize( 164 _In_ PCWSTR path_in, 165 _In_ /* PATHCCH_OPTIONS */ ULONG flags, 166 _Outptr_ PWSTR* path_out) 167 #else 168 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out) 169 #endif 170 { 171 WCHAR *buffer, *dst; 172 const WCHAR *src; 173 const WCHAR *root_end; 174 SIZE_T buffer_size, length; 175 176 TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out); 177 178 if (!path_in || !path_out 179 || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)) 180 || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS) 181 && !(flags & PATHCCH_ALLOW_LONG_PATHS)) 182 || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS))) 183 { 184 if (path_out) *path_out = NULL; 185 return E_INVALIDARG; 186 } 187 188 length = lstrlenW(path_in); 189 if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))) 190 || (length + 1 > PATHCCH_MAX_CCH)) 191 { 192 *path_out = NULL; 193 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 194 } 195 196 /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */ 197 if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS; 198 199 /* path length + possible \\?\ addition + possible \ addition + NUL */ 200 buffer_size = (length + 6) * sizeof(WCHAR); 201 buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size); 202 if (!buffer) 203 { 204 *path_out = NULL; 205 return E_OUTOFMEMORY; 206 } 207 208 src = path_in; 209 dst = buffer; 210 211 root_end = get_root_end(path_in); 212 if (root_end) root_end = buffer + (root_end - path_in); 213 214 /* Copy path root */ 215 if (root_end) 216 { 217 memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR)); 218 src += root_end - buffer + 1; 219 if(PathCchStripPrefix(dst, length + 6) == S_OK) 220 { 221 /* Fill in \ in X:\ if the \ is missing */ 222 if (is_drive_spec( dst ) && dst[2]!= '\\') 223 { 224 dst[2] = '\\'; 225 dst[3] = 0; 226 } 227 dst = buffer + lstrlenW(buffer); 228 root_end = dst; 229 } 230 else 231 dst += root_end - buffer + 1; 232 } 233 234 while (*src) 235 { 236 if (src[0] == '.') 237 { 238 if (src[1] == '.') 239 { 240 /* Keep one . after * */ 241 if (dst > buffer && dst[-1] == '*') 242 { 243 *dst++ = *src++; 244 continue; 245 } 246 247 /* Keep the .. if not surrounded by \ */ 248 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\')) 249 { 250 *dst++ = *src++; 251 *dst++ = *src++; 252 continue; 253 } 254 255 /* Remove the \ before .. if the \ is not part of root */ 256 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) 257 { 258 *--dst = '\0'; 259 /* Remove characters until a \ is encountered */ 260 while (dst > buffer) 261 { 262 if (dst[-1] == '\\') 263 { 264 *--dst = 0; 265 break; 266 } 267 else 268 *--dst = 0; 269 } 270 } 271 /* Remove the extra \ after .. if the \ before .. wasn't deleted */ 272 else if (src[2] == '\\') 273 src++; 274 275 src += 2; 276 } 277 else 278 { 279 /* Keep the . if not surrounded by \ */ 280 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\')) 281 { 282 *dst++ = *src++; 283 continue; 284 } 285 286 /* Remove the \ before . if the \ is not part of root */ 287 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--; 288 /* Remove the extra \ after . if the \ before . wasn't deleted */ 289 else if (src[1] == '\\') 290 src++; 291 292 src++; 293 } 294 295 /* If X:\ is not complete, then complete it */ 296 if (is_drive_spec( buffer ) && buffer[2] != '\\') 297 { 298 root_end = buffer + 2; 299 dst = buffer + 3; 300 buffer[2] = '\\'; 301 /* If next character is \, use the \ to fill in */ 302 if (src[0] == '\\') src++; 303 } 304 } 305 /* Copy over */ 306 else 307 *dst++ = *src++; 308 } 309 /* End the path */ 310 *dst = 0; 311 312 /* Strip multiple trailing . */ 313 if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS)) 314 { 315 while (dst > buffer && dst[-1] == '.') 316 { 317 /* Keep a . after * */ 318 if (dst - 1 > buffer && dst[-2] == '*') 319 break; 320 /* If . follow a : at the second character, remove the . and add a \ */ 321 else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1) 322 *--dst = '\\'; 323 else 324 *--dst = 0; 325 } 326 } 327 328 /* If result path is empty, fill in \ */ 329 if (!*buffer) 330 { 331 buffer[0] = '\\'; 332 buffer[1] = 0; 333 } 334 335 /* Extend the path if needed */ 336 length = lstrlenW(buffer); 337 if (((length + 1 > MAX_PATH && is_drive_spec( buffer )) 338 || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)) 339 && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS)) 340 { 341 memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR)); 342 buffer[0] = '\\'; 343 buffer[1] = '\\'; 344 buffer[2] = '?'; 345 buffer[3] = '\\'; 346 } 347 348 /* Add a trailing backslash to the path if needed */ 349 if (flags & PATHCCH_ENSURE_TRAILING_SLASH) 350 PathCchAddBackslash(buffer, buffer_size); 351 352 *path_out = buffer; 353 return S_OK; 354 } 355 356 #ifdef __REACTOS__ 357 HRESULT 358 APIENTRY 359 PathAllocCombine( 360 _In_opt_ PCWSTR path1, 361 _In_opt_ PCWSTR path2, 362 _In_ /* PATHCCH_OPTIONS */ ULONG flags, 363 _Outptr_ PWSTR* out) 364 #else 365 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out) 366 #endif 367 { 368 SIZE_T combined_length, length2; 369 WCHAR *combined_path; 370 BOOL add_backslash = FALSE; 371 HRESULT hr; 372 373 TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out); 374 375 if ((!path1 && !path2) || !out) 376 { 377 if (out) *out = NULL; 378 return E_INVALIDARG; 379 } 380 381 if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out); 382 383 /* If path2 is fully qualified, use path2 only */ 384 if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\')) 385 { 386 path1 = path2; 387 path2 = NULL; 388 add_backslash = (is_drive_spec(path1) && !path1[2]) 389 || (is_prefixed_disk(path1) && !path1[6]); 390 } 391 392 length2 = path2 ? lstrlenW(path2) : 0; 393 /* path1 length + path2 length + possible backslash + NULL */ 394 combined_length = lstrlenW(path1) + length2 + 2; 395 396 combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR)); 397 if (!combined_path) 398 { 399 *out = NULL; 400 return E_OUTOFMEMORY; 401 } 402 403 lstrcpyW(combined_path, path1); 404 PathCchStripPrefix(combined_path, combined_length); 405 if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); 406 407 if (path2 && path2[0]) 408 { 409 if (path2[0] == '\\' && path2[1] != '\\') 410 { 411 PathCchStripToRoot(combined_path, combined_length); 412 path2++; 413 } 414 415 PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); 416 lstrcatW(combined_path, path2); 417 } 418 419 hr = PathAllocCanonicalize(combined_path, flags, out); 420 HeapFree(GetProcessHeap(), 0, combined_path); 421 return hr; 422 } 423 424 #ifdef __REACTOS__ 425 HRESULT 426 APIENTRY 427 PathCchAddBackslash( 428 _Inout_updates_(size) PWSTR path, 429 _In_ size_t size) 430 #else 431 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size) 432 #endif 433 { 434 return PathCchAddBackslashEx(path, size, NULL, NULL); 435 } 436 437 #ifdef __REACTOS__ 438 HRESULT 439 APIENTRY 440 PathCchAddBackslashEx( 441 _Inout_updates_(size) PWSTR path, 442 _In_ size_t size, 443 _Outptr_opt_result_buffer_(*remaining) PWSTR* endptr, 444 _Out_opt_ size_t* remaining) 445 #else 446 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining) 447 #endif 448 { 449 BOOL needs_termination; 450 SIZE_T length; 451 452 TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining); 453 454 length = lstrlenW(path); 455 needs_termination = size && length && path[length - 1] != '\\'; 456 457 if (length >= (needs_termination ? size - 1 : size)) 458 { 459 if (endptr) *endptr = NULL; 460 if (remaining) *remaining = 0; 461 return STRSAFE_E_INSUFFICIENT_BUFFER; 462 } 463 464 if (!needs_termination) 465 { 466 if (endptr) *endptr = path + length; 467 if (remaining) *remaining = size - length; 468 return S_FALSE; 469 } 470 471 path[length++] = '\\'; 472 path[length] = 0; 473 474 if (endptr) *endptr = path + length; 475 if (remaining) *remaining = size - length; 476 477 return S_OK; 478 } 479 480 #ifdef __REACTOS__ 481 HRESULT 482 APIENTRY 483 PathCchAddExtension( 484 _Inout_updates_(size) PWSTR path, 485 _In_ size_t size, 486 _In_ PCWSTR extension) 487 #else 488 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) 489 #endif 490 { 491 const WCHAR *existing_extension, *next; 492 SIZE_T path_length, extension_length, dot_length; 493 BOOL has_dot; 494 HRESULT hr; 495 496 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); 497 498 if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG; 499 500 next = extension; 501 while (*next) 502 { 503 if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG; 504 next++; 505 } 506 507 has_dot = extension[0] == '.'; 508 509 hr = PathCchFindExtension(path, size, &existing_extension); 510 if (FAILED(hr)) return hr; 511 if (*existing_extension) return S_FALSE; 512 513 path_length = wcsnlen(path, size); 514 dot_length = has_dot ? 0 : 1; 515 extension_length = lstrlenW(extension); 516 517 if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER; 518 519 /* If extension is empty or only dot, return S_OK with path unchanged */ 520 if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK; 521 522 if (!has_dot) 523 { 524 path[path_length] = '.'; 525 path_length++; 526 } 527 528 lstrcpyW(path + path_length, extension); 529 return S_OK; 530 } 531 532 #ifdef __REACTOS__ 533 HRESULT 534 APIENTRY 535 PathCchAppend( 536 _Inout_updates_(size) PWSTR path1, 537 _In_ size_t size, 538 _In_opt_ PCWSTR path2) 539 #else 540 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2) 541 #endif 542 { 543 TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2)); 544 545 return PathCchAppendEx(path1, size, path2, PATHCCH_NONE); 546 } 547 548 #ifdef __REACTOS__ 549 HRESULT 550 APIENTRY 551 PathCchAppendEx( 552 _Inout_updates_(size) PWSTR path1, 553 _In_ size_t size, 554 _In_opt_ PCWSTR path2, 555 _In_ /* PATHCCH_OPTIONS */ ULONG flags) 556 #else 557 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags) 558 #endif 559 { 560 HRESULT hr; 561 WCHAR *result; 562 563 TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags); 564 565 if (!path1 || !size) return E_INVALIDARG; 566 567 /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs. 568 * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output 569 * buffer for PathCchCombineEx */ 570 result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); 571 if (!result) return E_OUTOFMEMORY; 572 573 /* Avoid the single backslash behavior with PathCchCombineEx when appending */ 574 if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++; 575 576 hr = PathCchCombineEx(result, size, path1, path2, flags); 577 if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR)); 578 579 HeapFree(GetProcessHeap(), 0, result); 580 return hr; 581 } 582 583 #ifdef __REACTOS__ 584 HRESULT 585 APIENTRY 586 PathCchCanonicalize( 587 _Out_writes_(size) PWSTR out, 588 _In_ size_t size, 589 _In_ PCWSTR in) 590 #else 591 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in) 592 #endif 593 { 594 TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in)); 595 596 /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ 597 if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\')) 598 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 599 600 return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE); 601 } 602 603 #ifdef __REACTOS__ 604 HRESULT 605 APIENTRY 606 PathCchCanonicalizeEx( 607 _Out_writes_(size) PWSTR out, 608 _In_ size_t size, 609 _In_ PCWSTR in, 610 _In_ /* PATHCCH_OPTIONS */ ULONG flags) 611 #else 612 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags) 613 #endif 614 { 615 WCHAR *buffer; 616 SIZE_T length; 617 HRESULT hr; 618 619 TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags); 620 621 if (!size) return E_INVALIDARG; 622 623 hr = PathAllocCanonicalize(in, flags, &buffer); 624 if (FAILED(hr)) return hr; 625 626 length = lstrlenW(buffer); 627 if (size < length + 1) 628 { 629 /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ 630 if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\'))) 631 hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); 632 else 633 hr = STRSAFE_E_INSUFFICIENT_BUFFER; 634 } 635 636 if (SUCCEEDED(hr)) 637 { 638 memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); 639 640 /* Fill a backslash at the end of X: */ 641 if (is_drive_spec( out ) && !out[2] && size > 3) 642 { 643 out[2] = '\\'; 644 out[3] = 0; 645 } 646 } 647 648 LocalFree(buffer); 649 return hr; 650 } 651 652 #ifdef __REACTOS__ 653 HRESULT 654 APIENTRY 655 PathCchCombine( 656 _Out_writes_(size) PWSTR out, 657 _In_ size_t size, 658 _In_opt_ PCWSTR path1, 659 _In_opt_ PCWSTR path2) 660 #else 661 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2) 662 #endif 663 { 664 TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2)); 665 666 return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE); 667 } 668 669 #ifdef __REACTOS__ 670 HRESULT 671 APIENTRY 672 PathCchCombineEx( 673 _Out_writes_(size) PWSTR out, 674 _In_ size_t size, 675 _In_opt_ PCWSTR path1, 676 _In_opt_ PCWSTR path2, 677 _In_ /* PATHCCH_OPTIONS */ ULONG flags) 678 #else 679 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags) 680 #endif 681 { 682 HRESULT hr; 683 WCHAR *buffer; 684 SIZE_T length; 685 686 TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags); 687 688 if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 689 690 hr = PathAllocCombine(path1, path2, flags, &buffer); 691 if (FAILED(hr)) 692 { 693 out[0] = 0; 694 return hr; 695 } 696 697 length = lstrlenW(buffer); 698 if (length + 1 > size) 699 { 700 out[0] = 0; 701 LocalFree(buffer); 702 return STRSAFE_E_INSUFFICIENT_BUFFER; 703 } 704 else 705 { 706 memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); 707 LocalFree(buffer); 708 return S_OK; 709 } 710 } 711 712 #ifdef __REACTOS__ 713 HRESULT 714 APIENTRY 715 PathCchFindExtension( 716 _In_reads_(size) PCWSTR path, 717 _In_ size_t size, 718 _Outptr_ PCWSTR* extension) 719 #else 720 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension) 721 #endif 722 { 723 const WCHAR *lastpoint = NULL; 724 SIZE_T counter = 0; 725 726 TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension); 727 728 if (!path || !size || size > PATHCCH_MAX_CCH) 729 { 730 *extension = NULL; 731 return E_INVALIDARG; 732 } 733 734 while (*path) 735 { 736 if (*path == '\\' || *path == ' ') 737 lastpoint = NULL; 738 else if (*path == '.') 739 lastpoint = path; 740 741 path++; 742 counter++; 743 if (counter == size || counter == PATHCCH_MAX_CCH) 744 { 745 *extension = NULL; 746 return E_INVALIDARG; 747 } 748 } 749 750 *extension = lastpoint ? lastpoint : path; 751 return S_OK; 752 } 753 754 #ifdef __REACTOS__ 755 BOOL 756 APIENTRY 757 PathCchIsRoot( 758 _In_opt_ PCWSTR path) 759 #else 760 BOOL WINAPI PathCchIsRoot(const WCHAR *path) 761 #endif 762 { 763 const WCHAR *root_end; 764 const WCHAR *next; 765 BOOL is_unc; 766 767 TRACE("%s\n", wine_dbgstr_w(path)); 768 769 if (!path || !*path) return FALSE; 770 771 root_end = get_root_end(path); 772 if (!root_end) return FALSE; 773 774 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')) 775 { 776 next = root_end + 1; 777 /* No extra segments */ 778 if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE; 779 780 /* Has first segment with an ending backslash but no remaining characters */ 781 if (get_next_segment(next, &next) && !*next) return FALSE; 782 /* Has first segment with no ending backslash */ 783 else if (!*next) 784 return TRUE; 785 /* Has first segment with an ending backslash and has remaining characters*/ 786 else 787 { 788 next++; 789 /* Second segment must have no backslash and no remaining characters */ 790 return !get_next_segment(next, &next) && !*next; 791 } 792 } 793 else if (*root_end == '\\' && !root_end[1]) 794 return TRUE; 795 else 796 return FALSE; 797 } 798 799 #ifdef __REACTOS__ 800 HRESULT 801 APIENTRY 802 PathCchRemoveBackslash( 803 _Inout_updates_(path_size) PWSTR path, 804 _In_ size_t path_size) 805 #else 806 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size) 807 #endif 808 { 809 WCHAR *path_end; 810 SIZE_T free_size; 811 812 TRACE("%s %Iu\n", debugstr_w(path), path_size); 813 814 return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size); 815 } 816 817 #ifdef __REACTOS__ 818 HRESULT 819 APIENTRY 820 PathCchRemoveBackslashEx( 821 _Inout_updates_(path_size) PWSTR path, 822 _In_ size_t path_size, 823 _Outptr_opt_result_buffer_(*free_size) PWSTR* path_end, 824 _Out_opt_ size_t* free_size) 825 #else 826 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size) 827 #endif 828 { 829 const WCHAR *root_end; 830 SIZE_T path_length; 831 832 TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size); 833 834 if (!path_size || !path_end || !free_size) 835 { 836 if (path_end) *path_end = NULL; 837 if (free_size) *free_size = 0; 838 return E_INVALIDARG; 839 } 840 841 path_length = wcsnlen(path, path_size); 842 if (path_length == path_size && !path[path_length]) return E_INVALIDARG; 843 844 root_end = get_root_end(path); 845 if (path_length > 0 && path[path_length - 1] == '\\') 846 { 847 *path_end = path + path_length - 1; 848 *free_size = path_size - path_length + 1; 849 /* If the last character is beyond end of root */ 850 if (!root_end || path + path_length - 1 > root_end) 851 { 852 path[path_length - 1] = 0; 853 return S_OK; 854 } 855 else 856 return S_FALSE; 857 } 858 else 859 { 860 *path_end = path + path_length; 861 *free_size = path_size - path_length; 862 return S_FALSE; 863 } 864 } 865 866 #ifdef __REACTOS__ 867 HRESULT 868 APIENTRY 869 PathCchRemoveExtension( 870 _Inout_updates_(size) PWSTR path, 871 _In_ size_t size) 872 #else 873 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size) 874 #endif 875 { 876 const WCHAR *extension; 877 WCHAR *next; 878 HRESULT hr; 879 880 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 881 882 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 883 884 hr = PathCchFindExtension(path, size, &extension); 885 if (FAILED(hr)) return hr; 886 887 next = path + (extension - path); 888 while (next - path < size && *next) *next++ = 0; 889 890 return next == extension ? S_FALSE : S_OK; 891 } 892 893 #ifdef __REACTOS__ 894 HRESULT 895 APIENTRY 896 PathCchRemoveFileSpec( 897 _Inout_updates_(size) PWSTR path, 898 _In_ size_t size) 899 #else 900 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size) 901 #endif 902 { 903 const WCHAR *root_end = NULL; 904 SIZE_T length; 905 WCHAR *last; 906 907 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 908 909 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 910 911 if (PathCchIsRoot(path)) return S_FALSE; 912 913 PathCchSkipRoot(path, &root_end); 914 915 /* The backslash at the end of UNC and \\* are not considered part of root in this case */ 916 if (root_end && root_end > path && root_end[-1] == '\\' 917 && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))) 918 root_end--; 919 920 length = lstrlenW(path); 921 last = path + length - 1; 922 while (last >= path && (!root_end || last >= root_end)) 923 { 924 if (last - path >= size) return E_INVALIDARG; 925 926 if (*last == '\\') 927 { 928 *last-- = 0; 929 break; 930 } 931 932 *last-- = 0; 933 } 934 935 return last != path + length - 1 ? S_OK : S_FALSE; 936 } 937 938 #ifdef __REACTOS__ 939 HRESULT 940 APIENTRY 941 PathCchRenameExtension( 942 _Inout_updates_(size) PWSTR path, 943 _In_ size_t size, 944 _In_ PCWSTR extension) 945 #else 946 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) 947 #endif 948 { 949 HRESULT hr; 950 951 TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); 952 953 hr = PathCchRemoveExtension(path, size); 954 if (FAILED(hr)) return hr; 955 956 hr = PathCchAddExtension(path, size, extension); 957 return FAILED(hr) ? hr : S_OK; 958 } 959 960 #ifdef __REACTOS__ 961 HRESULT 962 APIENTRY 963 PathCchSkipRoot( 964 _In_ PCWSTR path, 965 _Outptr_ PCWSTR* root_end) 966 #else 967 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end) 968 #endif 969 { 970 TRACE("%s %p\n", debugstr_w(path), root_end); 971 972 if (!path || !path[0] || !root_end 973 || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path) 974 && !is_prefixed_disk(path))) 975 return E_INVALIDARG; 976 977 *root_end = get_root_end(path); 978 if (*root_end) 979 { 980 (*root_end)++; 981 if (is_prefixed_unc(path)) 982 { 983 get_next_segment(*root_end, root_end); 984 get_next_segment(*root_end, root_end); 985 } 986 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') 987 { 988 /* Skip share server */ 989 get_next_segment(*root_end, root_end); 990 /* If mount point is empty, don't skip over mount point */ 991 if (**root_end != '\\') get_next_segment(*root_end, root_end); 992 } 993 } 994 995 return *root_end ? S_OK : E_INVALIDARG; 996 } 997 998 #ifdef __REACTOS__ 999 HRESULT 1000 APIENTRY 1001 PathCchStripPrefix( 1002 _Inout_updates_(size) PWSTR path, 1003 _In_ size_t size) 1004 #else 1005 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size) 1006 #endif 1007 { 1008 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 1009 1010 if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 1011 1012 if (is_prefixed_unc(path)) 1013 { 1014 /* \\?\UNC\a -> \\a */ 1015 if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG; 1016 lstrcpyW(path + 2, path + 8); 1017 return S_OK; 1018 } 1019 else if (is_prefixed_disk(path)) 1020 { 1021 /* \\?\C:\ -> C:\ */ 1022 if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG; 1023 lstrcpyW(path, path + 4); 1024 return S_OK; 1025 } 1026 else 1027 return S_FALSE; 1028 } 1029 1030 #ifdef __REACTOS__ 1031 HRESULT 1032 APIENTRY 1033 PathCchStripToRoot( 1034 _Inout_updates_(size) PWSTR path, 1035 _In_ size_t size) 1036 #else 1037 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size) 1038 #endif 1039 { 1040 const WCHAR *root_end; 1041 WCHAR *segment_end; 1042 BOOL is_unc; 1043 1044 TRACE("%s %Iu\n", wine_dbgstr_w(path), size); 1045 1046 if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; 1047 1048 /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped, 1049 * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b 1050 * \\\\a\\b\\c -> \\\\a\\b */ 1051 if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')) 1052 { 1053 root_end = is_unc ? path + 8 : path + 3; 1054 if (!get_next_segment(root_end, &root_end)) return S_FALSE; 1055 if (!get_next_segment(root_end, &root_end)) return S_FALSE; 1056 1057 if (root_end - path >= size) return E_INVALIDARG; 1058 1059 segment_end = path + (root_end - path) - 1; 1060 *segment_end = 0; 1061 return S_OK; 1062 } 1063 else if (PathCchSkipRoot(path, &root_end) == S_OK) 1064 { 1065 if (root_end - path >= size) return E_INVALIDARG; 1066 1067 segment_end = path + (root_end - path); 1068 if (!*segment_end) return S_FALSE; 1069 1070 *segment_end = 0; 1071 return S_OK; 1072 } 1073 else 1074 return E_INVALIDARG; 1075 } 1076 1077 #ifdef __REACTOS__ 1078 BOOL 1079 APIENTRY 1080 PathIsUNCEx( 1081 _In_ PCWSTR path, 1082 _Outptr_opt_ PCWSTR* server) 1083 #else 1084 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server) 1085 #endif 1086 { 1087 const WCHAR *result = NULL; 1088 1089 TRACE("%s %p\n", wine_dbgstr_w(path), server); 1090 1091 if (is_prefixed_unc(path)) 1092 result = path + 8; 1093 else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') 1094 result = path + 2; 1095 1096 if (server) *server = result; 1097 return !!result; 1098 } 1099