1 // Windows/FileName.cpp 2 3 #include "StdAfx.h" 4 5 #include "FileName.h" 6 7 #ifndef _UNICODE 8 extern bool g_IsNT; 9 #endif 10 11 namespace NWindows { 12 namespace NFile { 13 namespace NName { 14 15 #ifndef USE_UNICODE_FSTRING 16 void NormalizeDirPathPrefix(FString &dirPath) 17 { 18 if (dirPath.IsEmpty()) 19 return; 20 if (dirPath.Back() != FCHAR_PATH_SEPARATOR) 21 dirPath += FCHAR_PATH_SEPARATOR; 22 } 23 #endif 24 25 void NormalizeDirPathPrefix(UString &dirPath) 26 { 27 if (dirPath.IsEmpty()) 28 return; 29 if (dirPath.Back() != WCHAR_PATH_SEPARATOR) 30 dirPath += WCHAR_PATH_SEPARATOR; 31 } 32 33 34 #ifdef _WIN32 35 36 const wchar_t *kSuperPathPrefix = L"\\\\?\\"; 37 static const wchar_t *kSuperUncPrefix = L"\\\\?\\UNC\\"; 38 39 #define IS_DEVICE_PATH(s) ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '.' && (s)[3] == '\\') 40 #define IS_SUPER_PREFIX(s) ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '?' && (s)[3] == '\\') 41 #define IS_SUPER_OR_DEVICE_PATH(s) ((s)[0] == '\\' && (s)[1] == '\\' && ((s)[2] == '?' || (s)[2] == '.') && (s)[3] == '\\') 42 #define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z') 43 44 #define IS_UNC_WITH_SLASH(s) ( \ 45 ((s)[0] == 'U' || (s)[0] == 'u') && \ 46 ((s)[1] == 'N' || (s)[1] == 'n') && \ 47 ((s)[2] == 'C' || (s)[2] == 'c') && \ 48 (s)[3] == '\\') 49 50 bool IsDevicePath(CFSTR s) throw() 51 { 52 #ifdef UNDER_CE 53 54 s = s; 55 return false; 56 /* 57 // actually we don't know the way to open device file in WinCE. 58 unsigned len = MyStringLen(s); 59 if (len < 5 || len > 5 || memcmp(s, FTEXT("DSK"), 3 * sizeof(FChar)) != 0) 60 return false; 61 if (s[4] != ':') 62 return false; 63 // for reading use SG_REQ sg; if (DeviceIoControl(dsk, IOCTL_DISK_READ)); 64 */ 65 66 #else 67 68 if (!IS_DEVICE_PATH(s)) 69 return false; 70 unsigned len = MyStringLen(s); 71 if (len == 6 && s[5] == ':') 72 return true; 73 if (len < 18 || len > 22 || memcmp(s + kDevicePathPrefixSize, FTEXT("PhysicalDrive"), 13 * sizeof(FChar)) != 0) 74 return false; 75 for (unsigned i = 17; i < len; i++) 76 if (s[i] < '0' || s[i] > '9') 77 return false; 78 return true; 79 80 #endif 81 } 82 83 bool IsSuperUncPath(CFSTR s) throw() { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } 84 85 bool IsDrivePath(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; } 86 bool IsSuperPath(const wchar_t *s) throw() { return IS_SUPER_PREFIX(s); } 87 bool IsSuperOrDevicePath(const wchar_t *s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } 88 // bool IsSuperUncPath(const wchar_t *s) { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } 89 90 #ifndef USE_UNICODE_FSTRING 91 bool IsDrivePath(CFSTR s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; } 92 bool IsSuperPath(CFSTR s) throw() { return IS_SUPER_PREFIX(s); } 93 bool IsSuperOrDevicePath(CFSTR s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } 94 #endif // USE_UNICODE_FSTRING 95 96 bool IsAbsolutePath(const wchar_t *s) throw() 97 { 98 return s[0] == WCHAR_PATH_SEPARATOR || IsDrivePath(s); 99 } 100 101 static const unsigned kDrivePrefixSize = 3; /* c:\ */ 102 103 #ifndef USE_UNICODE_FSTRING 104 105 static unsigned GetRootPrefixSize_Of_NetworkPath(CFSTR s) throw() 106 { 107 // Network path: we look "server\path\" as root prefix 108 int pos = FindCharPosInString(s, '\\'); 109 if (pos < 0) 110 return 0; 111 int pos2 = FindCharPosInString(s + pos + 1, '\\'); 112 if (pos2 < 0) 113 return 0; 114 return pos + pos2 + 2; 115 } 116 117 static unsigned GetRootPrefixSize_Of_SimplePath(CFSTR s) throw() 118 { 119 if (IsDrivePath(s)) 120 return kDrivePrefixSize; 121 if (s[0] != '\\' || s[1] != '\\') 122 return 0; 123 unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); 124 return (size == 0) ? 0 : 2 + size; 125 } 126 127 static unsigned GetRootPrefixSize_Of_SuperPath(CFSTR s) throw() 128 { 129 if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) 130 { 131 unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); 132 return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; 133 } 134 // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" 135 int pos = FindCharPosInString(s + kSuperPathPrefixSize, FCHAR_PATH_SEPARATOR); 136 if (pos < 0) 137 return 0; 138 return kSuperPathPrefixSize + pos + 1; 139 } 140 141 unsigned GetRootPrefixSize(CFSTR s) throw() 142 { 143 if (IS_DEVICE_PATH(s)) 144 return kDevicePathPrefixSize; 145 if (IsSuperPath(s)) 146 return GetRootPrefixSize_Of_SuperPath(s); 147 return GetRootPrefixSize_Of_SimplePath(s); 148 } 149 150 #endif // USE_UNICODE_FSTRING 151 152 static unsigned GetRootPrefixSize_Of_NetworkPath(const wchar_t *s) throw() 153 { 154 // Network path: we look "server\path\" as root prefix 155 int pos = FindCharPosInString(s, L'\\'); 156 if (pos < 0) 157 return 0; 158 int pos2 = FindCharPosInString(s + pos + 1, L'\\'); 159 if (pos2 < 0) 160 return 0; 161 return pos + pos2 + 2; 162 } 163 164 static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) throw() 165 { 166 if (IsDrivePath(s)) 167 return kDrivePrefixSize; 168 if (s[0] != '\\' || s[1] != '\\') 169 return 0; 170 unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); 171 return (size == 0) ? 0 : 2 + size; 172 } 173 174 static unsigned GetRootPrefixSize_Of_SuperPath(const wchar_t *s) throw() 175 { 176 if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) 177 { 178 unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); 179 return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; 180 } 181 // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" 182 int pos = FindCharPosInString(s + kSuperPathPrefixSize, L'\\'); 183 if (pos < 0) 184 return 0; 185 return kSuperPathPrefixSize + pos + 1; 186 } 187 188 unsigned GetRootPrefixSize(const wchar_t *s) throw() 189 { 190 if (IS_DEVICE_PATH(s)) 191 return kDevicePathPrefixSize; 192 if (IsSuperPath(s)) 193 return GetRootPrefixSize_Of_SuperPath(s); 194 return GetRootPrefixSize_Of_SimplePath(s); 195 } 196 197 #else // _WIN32 198 199 bool IsAbsolutePath(const wchar_t *s) throw() { return s[0] == WCHAR_PATH_SEPARATOR } 200 201 #ifndef USE_UNICODE_FSTRING 202 unsigned GetRootPrefixSize(CFSTR s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; } 203 #endif 204 unsigned GetRootPrefixSize(const wchar_t *s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; } 205 206 #endif // _WIN32 207 208 209 #ifndef UNDER_CE 210 211 static bool GetCurDir(UString &path) 212 { 213 path.Empty(); 214 DWORD needLength; 215 #ifndef _UNICODE 216 if (!g_IsNT) 217 { 218 TCHAR s[MAX_PATH + 2]; 219 s[0] = 0; 220 needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); 221 path = fs2us(fas2fs(s)); 222 } 223 else 224 #endif 225 { 226 WCHAR s[MAX_PATH + 2]; 227 s[0] = 0; 228 needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); 229 path = s; 230 } 231 return (needLength > 0 && needLength <= MAX_PATH); 232 } 233 234 static bool ResolveDotsFolders(UString &s) 235 { 236 #ifdef _WIN32 237 s.Replace(L'/', WCHAR_PATH_SEPARATOR); 238 #endif 239 for (int i = 0;;) 240 { 241 wchar_t c = s[i]; 242 if (c == 0) 243 return true; 244 if (c == '.' && (i == 0 || s[i - 1] == WCHAR_PATH_SEPARATOR)) 245 { 246 wchar_t c1 = s[i + 1]; 247 if (c1 == '.') 248 { 249 wchar_t c2 = s[i + 2]; 250 if (c2 == WCHAR_PATH_SEPARATOR || c2 == 0) 251 { 252 if (i == 0) 253 return false; 254 int k = i - 2; 255 for (; k >= 0; k--) 256 if (s[k] == WCHAR_PATH_SEPARATOR) 257 break; 258 unsigned num; 259 if (k >= 0) 260 { 261 num = i + 2 - k; 262 i = k; 263 } 264 else 265 { 266 num = (c2 == 0 ? (i + 2) : (i + 3)); 267 i = 0; 268 } 269 s.Delete(i, num); 270 continue; 271 } 272 } 273 else 274 { 275 if (c1 == WCHAR_PATH_SEPARATOR || c1 == 0) 276 { 277 unsigned num = 2; 278 if (i != 0) 279 i--; 280 else if (c1 == 0) 281 num = 1; 282 s.Delete(i, num); 283 continue; 284 } 285 } 286 } 287 i++; 288 } 289 } 290 291 #endif // UNDER_CE 292 293 #define LONG_PATH_DOTS_FOLDERS_PARSING 294 295 296 /* 297 Windows (at least 64-bit XP) can't resolve "." or ".." in paths that start with SuperPrefix \\?\ 298 To solve that problem we check such path: 299 - super path contains "." or ".." - we use kSuperPathType_UseOnlySuper 300 - super path doesn't contain "." or ".." - we use kSuperPathType_UseOnlyMain 301 */ 302 #ifdef LONG_PATH_DOTS_FOLDERS_PARSING 303 #ifndef UNDER_CE 304 static bool AreThereDotsFolders(CFSTR s) 305 { 306 for (unsigned i = 0;; i++) 307 { 308 FChar c = s[i]; 309 if (c == 0) 310 return false; 311 if (c == '.' && (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR)) 312 { 313 FChar c1 = s[i + 1]; 314 if (c1 == 0 || c1 == CHAR_PATH_SEPARATOR || 315 (c1 == '.' && (s[i + 2] == 0 || s[i + 2] == CHAR_PATH_SEPARATOR))) 316 return true; 317 } 318 } 319 } 320 #endif 321 #endif // LONG_PATH_DOTS_FOLDERS_PARSING 322 323 #ifdef WIN_LONG_PATH 324 325 /* 326 Most of Windows versions have problems, if some file or dir name 327 contains '.' or ' ' at the end of name (Bad Path). 328 To solve that problem, we always use Super Path ("\\?\" prefix and full path) 329 in such cases. Note that "." and ".." are not bad names. 330 331 There are 3 cases: 332 1) If the path is already Super Path, we use that path 333 2) If the path is not Super Path : 334 2.1) Bad Path; we use only Super Path. 335 2.2) Good Path; we use Main Path. If it fails, we use Super Path. 336 337 NeedToUseOriginalPath returns: 338 kSuperPathType_UseOnlyMain : Super already 339 kSuperPathType_UseOnlySuper : not Super, Bad Path 340 kSuperPathType_UseMainAndSuper : not Super, Good Path 341 */ 342 343 int GetUseSuperPathType(CFSTR s) throw() 344 { 345 if (IsSuperOrDevicePath(s)) 346 { 347 #ifdef LONG_PATH_DOTS_FOLDERS_PARSING 348 if ((s)[2] != '.') 349 if (AreThereDotsFolders(s + kSuperPathPrefixSize)) 350 return kSuperPathType_UseOnlySuper; 351 #endif 352 return kSuperPathType_UseOnlyMain; 353 } 354 355 for (unsigned i = 0;; i++) 356 { 357 FChar c = s[i]; 358 if (c == 0) 359 return kSuperPathType_UseMainAndSuper; 360 if (c == '.' || c == ' ') 361 { 362 FChar c2 = s[i + 1]; 363 if (c2 == 0 || c2 == CHAR_PATH_SEPARATOR) 364 { 365 // if it's "." or "..", it's not bad name. 366 if (c == '.') 367 { 368 if (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR) 369 continue; 370 if (s[i - 1] == '.') 371 { 372 if (i - 1 == 0 || s[i - 2] == CHAR_PATH_SEPARATOR) 373 continue; 374 } 375 } 376 return kSuperPathType_UseOnlySuper; 377 } 378 } 379 } 380 } 381 382 383 /* 384 returns false in two cases: 385 - if GetCurDir was used, and GetCurDir returned error. 386 - if we can't resolve ".." name. 387 if path is ".", "..", res is empty. 388 if it's Super Path already, res is empty. 389 for \**** , and if GetCurDir is not drive (c:\), res is empty 390 for absolute paths, returns true, res is Super path. 391 */ 392 393 394 static bool GetSuperPathBase(CFSTR s, UString &res) 395 { 396 res.Empty(); 397 398 FChar c = s[0]; 399 if (c == 0) 400 return true; 401 if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) 402 return true; 403 404 if (IsSuperOrDevicePath(s)) 405 { 406 #ifdef LONG_PATH_DOTS_FOLDERS_PARSING 407 408 if ((s)[2] == '.') 409 return true; 410 411 // we will return true here, so we will try to use these problem paths. 412 413 if (!AreThereDotsFolders(s + kSuperPathPrefixSize)) 414 return true; 415 416 UString temp = fs2us(s); 417 unsigned fixedSize = GetRootPrefixSize_Of_SuperPath(temp); 418 if (fixedSize == 0) 419 return true; 420 421 UString rem = &temp[fixedSize]; 422 if (!ResolveDotsFolders(rem)) 423 return true; 424 425 temp.DeleteFrom(fixedSize); 426 res += temp; 427 res += rem; 428 429 #endif 430 431 return true; 432 } 433 434 if (c == CHAR_PATH_SEPARATOR) 435 { 436 if (s[1] == CHAR_PATH_SEPARATOR) 437 { 438 UString temp = fs2us(s + 2); 439 unsigned fixedSize = GetRootPrefixSize_Of_NetworkPath(temp); 440 if (fixedSize == 0) // maybe we must ignore that error to allow short network paths? 441 return false; 442 UString rem = &temp[fixedSize]; 443 if (!ResolveDotsFolders(rem)) 444 return false; 445 res += kSuperUncPrefix; 446 temp.DeleteFrom(fixedSize); 447 res += temp; 448 res += rem; 449 return true; 450 } 451 } 452 else 453 { 454 if (IsDrivePath(s)) 455 { 456 UString temp = fs2us(s); 457 UString rem = &temp[kDrivePrefixSize]; 458 if (!ResolveDotsFolders(rem)) 459 return true; 460 res += kSuperPathPrefix; 461 temp.DeleteFrom(kDrivePrefixSize); 462 res += temp; 463 res += rem; 464 return true; 465 } 466 } 467 468 UString curDir; 469 if (!GetCurDir(curDir)) 470 return false; 471 if (curDir.Back() != WCHAR_PATH_SEPARATOR) 472 curDir += WCHAR_PATH_SEPARATOR; 473 474 unsigned fixedSizeStart = 0; 475 unsigned fixedSize = 0; 476 const wchar_t *superMarker = NULL; 477 if (IsSuperPath(curDir)) 478 { 479 fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); 480 if (fixedSize == 0) 481 return false; 482 } 483 else 484 { 485 if (IsDrivePath(curDir)) 486 { 487 superMarker = kSuperPathPrefix; 488 fixedSize = kDrivePrefixSize; 489 } 490 else 491 { 492 if (curDir[0] != CHAR_PATH_SEPARATOR || curDir[1] != CHAR_PATH_SEPARATOR) 493 return false; 494 fixedSizeStart = 2; 495 fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]); 496 if (fixedSize == 0) 497 return false; 498 superMarker = kSuperUncPrefix; 499 } 500 } 501 502 UString temp; 503 if (c == CHAR_PATH_SEPARATOR) 504 { 505 temp = fs2us(s + 1); 506 } 507 else 508 { 509 temp += &curDir[fixedSizeStart + fixedSize]; 510 temp += fs2us(s); 511 } 512 if (!ResolveDotsFolders(temp)) 513 return false; 514 if (superMarker) 515 res += superMarker; 516 res += curDir.Mid(fixedSizeStart, fixedSize); 517 res += temp; 518 return true; 519 } 520 521 522 /* 523 In that case if GetSuperPathBase doesn't return new path, we don't need 524 to use same path that was used as main path 525 526 GetSuperPathBase superPath.IsEmpty() onlyIfNew 527 false * * GetCurDir Error 528 true false * use Super path 529 true true true don't use any path, we already used mainPath 530 true true false use main path as Super Path, we don't try mainMath 531 That case is possible now if GetCurDir returns unknow 532 type of path (not drive and not network) 533 534 We can change that code if we want to try mainPath, if GetSuperPathBase returns error, 535 and we didn't try mainPath still. 536 If we want to work that way, we don't need to use GetSuperPathBase return code. 537 */ 538 539 bool GetSuperPath(CFSTR path, UString &superPath, bool onlyIfNew) 540 { 541 if (GetSuperPathBase(path, superPath)) 542 { 543 if (superPath.IsEmpty()) 544 { 545 // actually the only possible when onlyIfNew == true and superPath is empty 546 // is case when 547 548 if (onlyIfNew) 549 return false; 550 superPath = fs2us(path); 551 } 552 return true; 553 } 554 return false; 555 } 556 557 bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew) 558 { 559 if (!GetSuperPathBase(s1, d1) || 560 !GetSuperPathBase(s2, d2)) 561 return false; 562 if (d1.IsEmpty() && d2.IsEmpty() && onlyIfNew) 563 return false; 564 if (d1.IsEmpty()) d1 = fs2us(s1); 565 if (d2.IsEmpty()) d2 = fs2us(s2); 566 return true; 567 } 568 569 570 /* 571 // returns true, if we need additional use with New Super path. 572 bool GetSuperPath(CFSTR path, UString &superPath) 573 { 574 if (GetSuperPathBase(path, superPath)) 575 return !superPath.IsEmpty(); 576 return false; 577 } 578 */ 579 #endif // WIN_LONG_PATH 580 581 bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) 582 { 583 res = s; 584 585 #ifdef UNDER_CE 586 587 if (s[0] != CHAR_PATH_SEPARATOR) 588 { 589 if (!dirPrefix) 590 return false; 591 res = dirPrefix; 592 res += s; 593 } 594 595 #else 596 597 unsigned prefixSize = GetRootPrefixSize(s); 598 if (prefixSize != 0) 599 { 600 if (!AreThereDotsFolders(s + prefixSize)) 601 return true; 602 603 UString rem = fs2us(s + prefixSize); 604 if (!ResolveDotsFolders(rem)) 605 return true; // maybe false; 606 res.DeleteFrom(prefixSize); 607 res += us2fs(rem); 608 return true; 609 } 610 611 /* 612 FChar c = s[0]; 613 if (c == 0) 614 return true; 615 if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) 616 return true; 617 if (c == CHAR_PATH_SEPARATOR && s[1] == CHAR_PATH_SEPARATOR) 618 return true; 619 if (IsDrivePath(s)) 620 return true; 621 */ 622 623 UString curDir; 624 if (dirPrefix) 625 curDir = fs2us(dirPrefix); 626 else 627 { 628 if (!GetCurDir(curDir)) 629 return false; 630 } 631 if (!curDir.IsEmpty() && curDir.Back() != WCHAR_PATH_SEPARATOR) 632 curDir += WCHAR_PATH_SEPARATOR; 633 634 unsigned fixedSize = 0; 635 636 #ifdef _WIN32 637 638 if (IsSuperPath(curDir)) 639 { 640 fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); 641 if (fixedSize == 0) 642 return false; 643 } 644 else 645 { 646 if (IsDrivePath(curDir)) 647 fixedSize = kDrivePrefixSize; 648 else 649 { 650 if (curDir[0] != WCHAR_PATH_SEPARATOR || curDir[1] != WCHAR_PATH_SEPARATOR) 651 return false; 652 fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]); 653 if (fixedSize == 0) 654 return false; 655 fixedSize += 2; 656 } 657 } 658 659 #endif // _WIN32 660 661 UString temp; 662 if (s[0] == CHAR_PATH_SEPARATOR) 663 { 664 temp = fs2us(s + 1); 665 } 666 else 667 { 668 temp += curDir.Ptr(fixedSize); 669 temp += fs2us(s); 670 } 671 if (!ResolveDotsFolders(temp)) 672 return false; 673 curDir.DeleteFrom(fixedSize); 674 res = us2fs(curDir); 675 res += us2fs(temp); 676 677 #endif // UNDER_CE 678 679 return true; 680 } 681 682 bool GetFullPath(CFSTR path, FString &fullPath) 683 { 684 return GetFullPath(NULL, path, fullPath); 685 } 686 687 }}} 688