1// HashCalc.cpp 2 3#include "StdAfx.h" 4 5#include "../../../../C/Alloc.h" 6#include "../../../../C/CpuArch.h" 7 8#include "../../../Common/DynLimBuf.h" 9#include "../../../Common/IntToString.h" 10#include "../../../Common/StringToInt.h" 11 12#include "../../Common/FileStreams.h" 13#include "../../Common/ProgressUtils.h" 14#include "../../Common/StreamObjects.h" 15#include "../../Common/StreamUtils.h" 16 17#include "../../Archive/Common/ItemNameUtils.h" 18#include "../../Archive/IArchive.h" 19 20#include "EnumDirItems.h" 21#include "HashCalc.h" 22 23using namespace NWindows; 24 25#ifdef Z7_EXTERNAL_CODECS 26extern const CExternalCodecs *g_ExternalCodecs_Ptr; 27#endif 28 29class CHashMidBuf 30{ 31 void *_data; 32public: 33 CHashMidBuf(): _data(NULL) {} 34 operator void *() { return _data; } 35 bool Alloc(size_t size) 36 { 37 if (_data) 38 return false; 39 _data = ::MidAlloc(size); 40 return _data != NULL; 41 } 42 ~CHashMidBuf() { ::MidFree(_data); } 43}; 44 45static const char * const k_DefaultHashMethod = "CRC32"; 46 47HRESULT CHashBundle::SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &hashMethods) 48{ 49 UStringVector names = hashMethods; 50 if (names.IsEmpty()) 51 names.Add(UString(k_DefaultHashMethod)); 52 53 CRecordVector<CMethodId> ids; 54 CObjectVector<COneMethodInfo> methods; 55 56 unsigned i; 57 for (i = 0; i < names.Size(); i++) 58 { 59 COneMethodInfo m; 60 RINOK(m.ParseMethodFromString(names[i])) 61 62 if (m.MethodName.IsEmpty()) 63 m.MethodName = k_DefaultHashMethod; 64 65 if (m.MethodName == "*") 66 { 67 CRecordVector<CMethodId> tempMethods; 68 GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods); 69 methods.Clear(); 70 ids.Clear(); 71 FOR_VECTOR (t, tempMethods) 72 { 73 unsigned index = ids.AddToUniqueSorted(tempMethods[t]); 74 if (ids.Size() != methods.Size()) 75 methods.Insert(index, m); 76 } 77 break; 78 } 79 else 80 { 81 // m.MethodName.RemoveChar(L'-'); 82 CMethodId id; 83 if (!FindHashMethod(EXTERNAL_CODECS_LOC_VARS m.MethodName, id)) 84 return E_NOTIMPL; 85 unsigned index = ids.AddToUniqueSorted(id); 86 if (ids.Size() != methods.Size()) 87 methods.Insert(index, m); 88 } 89 } 90 91 for (i = 0; i < ids.Size(); i++) 92 { 93 CMyComPtr<IHasher> hasher; 94 AString name; 95 RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher)) 96 if (!hasher) 97 throw "Can't create hasher"; 98 const COneMethodInfo &m = methods[i]; 99 { 100 CMyComPtr<ICompressSetCoderProperties> scp; 101 hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); 102 if (scp) 103 RINOK(m.SetCoderProps(scp, NULL)) 104 } 105 const UInt32 digestSize = hasher->GetDigestSize(); 106 if (digestSize > k_HashCalc_DigestSize_Max) 107 return E_NOTIMPL; 108 CHasherState &h = Hashers.AddNew(); 109 h.DigestSize = digestSize; 110 h.Hasher = hasher; 111 h.Name = name; 112 for (unsigned k = 0; k < k_HashCalc_NumGroups; k++) 113 h.InitDigestGroup(k); 114 } 115 116 return S_OK; 117} 118 119void CHashBundle::InitForNewFile() 120{ 121 CurSize = 0; 122 FOR_VECTOR (i, Hashers) 123 { 124 CHasherState &h = Hashers[i]; 125 h.Hasher->Init(); 126 h.InitDigestGroup(k_HashCalc_Index_Current); 127 } 128} 129 130void CHashBundle::Update(const void *data, UInt32 size) 131{ 132 CurSize += size; 133 FOR_VECTOR (i, Hashers) 134 Hashers[i].Hasher->Update(data, size); 135} 136 137void CHashBundle::SetSize(UInt64 size) 138{ 139 CurSize = size; 140} 141 142static void AddDigests(Byte *dest, const Byte *src, UInt32 size) 143{ 144 unsigned next = 0; 145 /* 146 // we could use big-endian addition for sha-1 and sha-256 147 // but another hashers are little-endian 148 if (size > 8) 149 { 150 for (unsigned i = size; i != 0;) 151 { 152 i--; 153 next += (unsigned)dest[i] + (unsigned)src[i]; 154 dest[i] = (Byte)next; 155 next >>= 8; 156 } 157 } 158 else 159 */ 160 { 161 for (unsigned i = 0; i < size; i++) 162 { 163 next += (unsigned)dest[i] + (unsigned)src[i]; 164 dest[i] = (Byte)next; 165 next >>= 8; 166 } 167 } 168 169 // we use little-endian to store extra bytes 170 dest += k_HashCalc_DigestSize_Max; 171 for (unsigned i = 0; i < k_HashCalc_ExtraSize; i++) 172 { 173 next += (unsigned)dest[i]; 174 dest[i] = (Byte)next; 175 next >>= 8; 176 } 177} 178 179void CHasherState::AddDigest(unsigned groupIndex, const Byte *data) 180{ 181 NumSums[groupIndex]++; 182 AddDigests(Digests[groupIndex], data, DigestSize); 183} 184 185void CHashBundle::Final(bool isDir, bool isAltStream, const UString &path) 186{ 187 if (isDir) 188 NumDirs++; 189 else if (isAltStream) 190 { 191 NumAltStreams++; 192 AltStreamsSize += CurSize; 193 } 194 else 195 { 196 NumFiles++; 197 FilesSize += CurSize; 198 } 199 200 Byte pre[16]; 201 memset(pre, 0, sizeof(pre)); 202 if (isDir) 203 pre[0] = 1; 204 205 FOR_VECTOR (i, Hashers) 206 { 207 CHasherState &h = Hashers[i]; 208 if (!isDir) 209 { 210 h.Hasher->Final(h.Digests[0]); // k_HashCalc_Index_Current 211 if (!isAltStream) 212 h.AddDigest(k_HashCalc_Index_DataSum, h.Digests[0]); 213 } 214 215 h.Hasher->Init(); 216 h.Hasher->Update(pre, sizeof(pre)); 217 h.Hasher->Update(h.Digests[0], h.DigestSize); 218 219 for (unsigned k = 0; k < path.Len(); k++) 220 { 221 wchar_t c = path[k]; 222 223 // 21.04: we want same hash for linux and windows paths 224 #if CHAR_PATH_SEPARATOR != '/' 225 if (c == CHAR_PATH_SEPARATOR) 226 c = '/'; 227 // if (c == (wchar_t)('\\' + 0xf000)) c = '\\'; // to debug WSL 228 // if (c > 0xf000 && c < 0xf080) c -= 0xf000; // to debug WSL 229 #endif 230 231 Byte temp[2] = { (Byte)(c & 0xFF), (Byte)((c >> 8) & 0xFF) }; 232 h.Hasher->Update(temp, 2); 233 } 234 235 Byte tempDigest[k_HashCalc_DigestSize_Max]; 236 h.Hasher->Final(tempDigest); 237 if (!isAltStream) 238 h.AddDigest(k_HashCalc_Index_NamesSum, tempDigest); 239 h.AddDigest(k_HashCalc_Index_StreamsSum, tempDigest); 240 } 241} 242 243 244static void CSum_Name_OriginalToEscape(const AString &src, AString &dest) 245{ 246 dest.Empty(); 247 for (unsigned i = 0; i < src.Len();) 248 { 249 char c = src[i++]; 250 if (c == '\n') 251 { 252 dest += '\\'; 253 c = 'n'; 254 } 255 else if (c == '\\') 256 dest += '\\'; 257 dest += c; 258 } 259} 260 261 262static bool CSum_Name_EscapeToOriginal(const char *s, AString &dest) 263{ 264 bool isOK = true; 265 dest.Empty(); 266 for (;;) 267 { 268 char c = *s++; 269 if (c == 0) 270 break; 271 if (c == '\\') 272 { 273 const char c1 = *s; 274 if (c1 == 'n') 275 { 276 c = '\n'; 277 s++; 278 } 279 else if (c1 == '\\') 280 { 281 c = c1; 282 s++; 283 } 284 else 285 { 286 // original md5sum returns NULL for such bad strings 287 isOK = false; 288 } 289 } 290 dest += c; 291 } 292 return isOK; 293} 294 295 296 297static void SetSpacesAndNul(char *s, unsigned num) 298{ 299 for (unsigned i = 0; i < num; i++) 300 s[i] = ' '; 301 s[num] = 0; 302} 303 304static const unsigned kHashColumnWidth_Min = 4 * 2; 305 306static unsigned GetColumnWidth(unsigned digestSize) 307{ 308 const unsigned width = digestSize * 2; 309 return width < kHashColumnWidth_Min ? kHashColumnWidth_Min: width; 310} 311 312 313static void AddHashResultLine( 314 AString &_s, 315 // bool showHash, 316 // UInt64 fileSize, bool showSize, 317 const CObjectVector<CHasherState> &hashers 318 // unsigned digestIndex, = k_HashCalc_Index_Current 319 ) 320{ 321 FOR_VECTOR (i, hashers) 322 { 323 const CHasherState &h = hashers[i]; 324 char s[k_HashCalc_DigestSize_Max * 2 + 64]; 325 s[0] = 0; 326 // if (showHash) 327 HashHexToString(s, h.Digests[k_HashCalc_Index_Current], h.DigestSize); 328 const unsigned pos = (unsigned)strlen(s); 329 const int numSpaces = (int)GetColumnWidth(h.DigestSize) - (int)pos; 330 if (numSpaces > 0) 331 SetSpacesAndNul(s + pos, (unsigned)numSpaces); 332 if (i != 0) 333 _s.Add_Space(); 334 _s += s; 335 } 336 337 /* 338 if (showSize) 339 { 340 _s.Add_Space(); 341 static const unsigned kSizeField_Len = 13; // same as in HashCon.cpp 342 char s[kSizeField_Len + 32]; 343 char *p = s; 344 SetSpacesAndNul(s, kSizeField_Len); 345 p = s + kSizeField_Len; 346 ConvertUInt64ToString(fileSize, p); 347 int numSpaces = (int)kSizeField_Len - (int)strlen(p); 348 if (numSpaces > 0) 349 p -= (unsigned)numSpaces; 350 _s += p; 351 } 352 */ 353} 354 355 356static void Add_LF(CDynLimBuf &hashFileString, const CHashOptionsLocal &options) 357{ 358 hashFileString += (char)(options.HashMode_Zero.Val ? 0 : '\n'); 359} 360 361 362 363 364static void WriteLine(CDynLimBuf &hashFileString, 365 const CHashOptionsLocal &options, 366 const UString &path2, 367 bool isDir, 368 const AString &methodName, 369 const AString &hashesString) 370{ 371 if (options.HashMode_OnlyHash.Val) 372 { 373 hashFileString += hashesString; 374 Add_LF(hashFileString, options); 375 return; 376 } 377 378 UString path = path2; 379 380 bool isBin = false; 381 const bool zeroMode = options.HashMode_Zero.Val; 382 const bool tagMode = options.HashMode_Tag.Val; 383 384#if CHAR_PATH_SEPARATOR != '/' 385 path.Replace(WCHAR_PATH_SEPARATOR, L'/'); 386 // path.Replace((wchar_t)('\\' + 0xf000), L'\\'); // to debug WSL 387#endif 388 389 AString utf8; 390 ConvertUnicodeToUTF8(path, utf8); 391 392 AString esc; 393 CSum_Name_OriginalToEscape(utf8, esc); 394 395 if (!zeroMode) 396 { 397 if (esc != utf8) 398 { 399 /* Original md5sum writes escape in that case. 400 We do same for compatibility with original md5sum. */ 401 hashFileString += '\\'; 402 } 403 } 404 405 if (isDir && !esc.IsEmpty() && esc.Back() != '/') 406 esc += '/'; 407 408 if (tagMode) 409 { 410 if (!methodName.IsEmpty()) 411 { 412 hashFileString += methodName; 413 hashFileString += ' '; 414 } 415 hashFileString += '('; 416 hashFileString += esc; 417 hashFileString += ')'; 418 hashFileString += " = "; 419 } 420 421 hashFileString += hashesString; 422 423 if (!tagMode) 424 { 425 hashFileString += ' '; 426 hashFileString += (char)(isBin ? '*' : ' '); 427 hashFileString += esc; 428 } 429 430 Add_LF(hashFileString, options); 431} 432 433 434 435static void WriteLine(CDynLimBuf &hashFileString, 436 const CHashOptionsLocal &options, 437 const UString &path, 438 bool isDir, 439 const CHashBundle &hb) 440{ 441 AString methodName; 442 if (!hb.Hashers.IsEmpty()) 443 methodName = hb.Hashers[0].Name; 444 445 AString hashesString; 446 AddHashResultLine(hashesString, hb.Hashers); 447 WriteLine(hashFileString, options, path, isDir, methodName, hashesString); 448} 449 450 451HRESULT HashCalc( 452 DECL_EXTERNAL_CODECS_LOC_VARS 453 const NWildcard::CCensor &censor, 454 const CHashOptions &options, 455 AString &errorInfo, 456 IHashCallbackUI *callback) 457{ 458 CDirItems dirItems; 459 dirItems.Callback = callback; 460 461 if (options.StdInMode) 462 { 463 CDirItem di; 464 di.Size = (UInt64)(Int64)-1; 465 di.SetAsFile(); 466 dirItems.Items.Add(di); 467 } 468 else 469 { 470 RINOK(callback->StartScanning()) 471 472 dirItems.SymLinks = options.SymLinks.Val; 473 dirItems.ScanAltStreams = options.AltStreamsMode; 474 dirItems.ExcludeDirItems = censor.ExcludeDirItems; 475 dirItems.ExcludeFileItems = censor.ExcludeFileItems; 476 477 dirItems.ShareForWrite = options.OpenShareForWrite; 478 479 HRESULT res = EnumerateItems(censor, 480 options.PathMode, 481 UString(), 482 dirItems); 483 484 if (res != S_OK) 485 { 486 if (res != E_ABORT) 487 errorInfo = "Scanning error"; 488 return res; 489 } 490 RINOK(callback->FinishScanning(dirItems.Stat)) 491 } 492 493 unsigned i; 494 CHashBundle hb; 495 RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods)) 496 // hb.Init(); 497 498 hb.NumErrors = dirItems.Stat.NumErrors; 499 500 UInt64 totalSize = 0; 501 if (options.StdInMode) 502 { 503 RINOK(callback->SetNumFiles(1)) 504 } 505 else 506 { 507 totalSize = dirItems.Stat.GetTotalBytes(); 508 RINOK(callback->SetTotal(totalSize)) 509 } 510 511 const UInt32 kBufSize = 1 << 15; 512 CHashMidBuf buf; 513 if (!buf.Alloc(kBufSize)) 514 return E_OUTOFMEMORY; 515 516 UInt64 completeValue = 0; 517 518 RINOK(callback->BeforeFirstFile(hb)) 519 520 /* 521 CDynLimBuf hashFileString((size_t)1 << 31); 522 const bool needGenerate = !options.HashFilePath.IsEmpty(); 523 */ 524 525 for (i = 0; i < dirItems.Items.Size(); i++) 526 { 527 CMyComPtr<ISequentialInStream> inStream; 528 UString path; 529 bool isDir = false; 530 bool isAltStream = false; 531 532 if (options.StdInMode) 533 { 534 inStream = new CStdInFileStream; 535 } 536 else 537 { 538 path = dirItems.GetLogPath(i); 539 const CDirItem &di = dirItems.Items[i]; 540 #ifdef _WIN32 541 isAltStream = di.IsAltStream; 542 #endif 543 544 #ifndef UNDER_CE 545 // if (di.AreReparseData()) 546 if (di.ReparseData.Size() != 0) 547 { 548 CBufInStream *inStreamSpec = new CBufInStream(); 549 inStream = inStreamSpec; 550 inStreamSpec->Init(di.ReparseData, di.ReparseData.Size()); 551 } 552 else 553 #endif 554 { 555 CInFileStream *inStreamSpec = new CInFileStream; 556 inStreamSpec->Set_PreserveATime(options.PreserveATime); 557 inStream = inStreamSpec; 558 isDir = di.IsDir(); 559 if (!isDir) 560 { 561 const FString phyPath = dirItems.GetPhyPath(i); 562 if (!inStreamSpec->OpenShared(phyPath, options.OpenShareForWrite)) 563 { 564 HRESULT res = callback->OpenFileError(phyPath, ::GetLastError()); 565 hb.NumErrors++; 566 if (res != S_FALSE) 567 return res; 568 continue; 569 } 570 if (!options.StdInMode) 571 { 572 UInt64 curSize = 0; 573 if (inStreamSpec->GetSize(&curSize) == S_OK) 574 { 575 if (curSize > di.Size) 576 { 577 totalSize += curSize - di.Size; 578 RINOK(callback->SetTotal(totalSize)) 579 // printf("\ntotal = %d MiB\n", (unsigned)(totalSize >> 20)); 580 } 581 } 582 } 583 // inStreamSpec->ReloadProps(); 584 } 585 } 586 } 587 588 RINOK(callback->GetStream(path, isDir)) 589 UInt64 fileSize = 0; 590 591 hb.InitForNewFile(); 592 593 if (!isDir) 594 { 595 for (UInt32 step = 0;; step++) 596 { 597 if ((step & 0xFF) == 0) 598 { 599 // printf("\ncompl = %d\n", (unsigned)(completeValue >> 20)); 600 RINOK(callback->SetCompleted(&completeValue)) 601 } 602 UInt32 size; 603 RINOK(inStream->Read(buf, kBufSize, &size)) 604 if (size == 0) 605 break; 606 hb.Update(buf, size); 607 fileSize += size; 608 completeValue += size; 609 } 610 } 611 612 hb.Final(isDir, isAltStream, path); 613 614 /* 615 if (needGenerate 616 && (options.HashMode_Dirs.Val || !isDir)) 617 { 618 WriteLine(hashFileString, 619 options, 620 path, // change it 621 isDir, 622 hb); 623 624 if (hashFileString.IsError()) 625 return E_OUTOFMEMORY; 626 } 627 */ 628 629 RINOK(callback->SetOperationResult(fileSize, hb, !isDir)) 630 RINOK(callback->SetCompleted(&completeValue)) 631 } 632 633 /* 634 if (needGenerate) 635 { 636 NFile::NIO::COutFile file; 637 if (!file.Create(us2fs(options.HashFilePath), true)) // createAlways 638 return GetLastError_noZero_HRESULT(); 639 if (!file.WriteFull(hashFileString, hashFileString.Len())) 640 return GetLastError_noZero_HRESULT(); 641 } 642 */ 643 644 return callback->AfterLastFile(hb); 645} 646 647 648static inline char GetHex_Upper(unsigned v) 649{ 650 return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10))); 651} 652 653static inline char GetHex_Lower(unsigned v) 654{ 655 return (char)((v < 10) ? ('0' + v) : ('a' + (v - 10))); 656} 657 658void HashHexToString(char *dest, const Byte *data, UInt32 size) 659{ 660 dest[size * 2] = 0; 661 662 if (!data) 663 { 664 for (UInt32 i = 0; i < size; i++) 665 { 666 dest[0] = ' '; 667 dest[1] = ' '; 668 dest += 2; 669 } 670 return; 671 } 672 673 if (size <= 8) 674 { 675 dest += size * 2; 676 for (UInt32 i = 0; i < size; i++) 677 { 678 const unsigned b = data[i]; 679 dest -= 2; 680 dest[0] = GetHex_Upper((b >> 4) & 0xF); 681 dest[1] = GetHex_Upper(b & 0xF); 682 } 683 } 684 else 685 { 686 for (UInt32 i = 0; i < size; i++) 687 { 688 const unsigned b = data[i]; 689 dest[0] = GetHex_Lower((b >> 4) & 0xF); 690 dest[1] = GetHex_Lower(b & 0xF); 691 dest += 2; 692 } 693 } 694} 695 696void CHasherState::WriteToString(unsigned digestIndex, char *s) const 697{ 698 HashHexToString(s, Digests[digestIndex], DigestSize); 699 700 if (digestIndex != 0 && NumSums[digestIndex] != 1) 701 { 702 unsigned numExtraBytes = GetNumExtraBytes_for_Group(digestIndex); 703 if (numExtraBytes > 4) 704 numExtraBytes = 8; 705 else // if (numExtraBytes >= 0) 706 numExtraBytes = 4; 707 // if (numExtraBytes != 0) 708 { 709 s += strlen(s); 710 *s++ = '-'; 711 // *s = 0; 712 HashHexToString(s, GetExtraData_for_Group(digestIndex), numExtraBytes); 713 } 714 } 715} 716 717 718 719// ---------- Hash Handler ---------- 720 721namespace NHash { 722 723static size_t ParseHexString(const char *s, Byte *dest) throw() 724{ 725 size_t num; 726 for (num = 0;; num++, s += 2) 727 { 728 unsigned c = (Byte)s[0]; 729 unsigned v0; 730 if (c >= '0' && c <= '9') v0 = (c - '0'); 731 else if (c >= 'A' && c <= 'F') v0 = 10 + (c - 'A'); 732 else if (c >= 'a' && c <= 'f') v0 = 10 + (c - 'a'); 733 else 734 return num; 735 c = (Byte)s[1]; 736 unsigned v1; 737 if (c >= '0' && c <= '9') v1 = (c - '0'); 738 else if (c >= 'A' && c <= 'F') v1 = 10 + (c - 'A'); 739 else if (c >= 'a' && c <= 'f') v1 = 10 + (c - 'a'); 740 else 741 return num; 742 if (dest) 743 dest[num] = (Byte)(v1 | (v0 << 4)); 744 } 745} 746 747 748#define IsWhite(c) ((c) == ' ' || (c) == '\t') 749 750bool CHashPair::IsDir() const 751{ 752 if (Name.IsEmpty() || Name.Back() != '/') 753 return false; 754 // here we expect that Dir items contain only zeros or no Hash 755 for (size_t i = 0; i < Hash.Size(); i++) 756 if (Hash[i] != 0) 757 return false; 758 return true; 759} 760 761 762bool CHashPair::ParseCksum(const char *s) 763{ 764 const char *end; 765 766 const UInt32 crc = ConvertStringToUInt32(s, &end); 767 if (*end != ' ') 768 return false; 769 end++; 770 771 const UInt64 size = ConvertStringToUInt64(end, &end); 772 if (*end != ' ') 773 return false; 774 end++; 775 776 Name = end; 777 778 Hash.Alloc(4); 779 SetBe32(Hash, crc) 780 781 Size_from_Arc = size; 782 Size_from_Arc_Defined = true; 783 784 return true; 785} 786 787 788 789static const char *SkipWhite(const char *s) 790{ 791 while (IsWhite(*s)) 792 s++; 793 return s; 794} 795 796static const char * const k_CsumMethodNames[] = 797{ 798 "sha256" 799 , "sha224" 800// , "sha512/224" 801// , "sha512/256" 802 , "sha512" 803 , "sha384" 804 , "sha1" 805 , "md5" 806 , "blake2b" 807 , "crc64" 808 , "crc32" 809 , "cksum" 810}; 811 812static UString GetMethod_from_FileName(const UString &name) 813{ 814 AString s; 815 ConvertUnicodeToUTF8(name, s); 816 const int dotPos = s.ReverseFind_Dot(); 817 const char *src = s.Ptr(); 818 bool isExtension = false; 819 if (dotPos >= 0) 820 { 821 isExtension = true; 822 src = s.Ptr(dotPos + 1); 823 } 824 const char *m = ""; 825 unsigned i; 826 for (i = 0; i < Z7_ARRAY_SIZE(k_CsumMethodNames); i++) 827 { 828 m = k_CsumMethodNames[i]; 829 if (isExtension) 830 { 831 if (StringsAreEqual_Ascii(src, m)) 832 break; 833 } 834 else if (IsString1PrefixedByString2_NoCase_Ascii(src, m)) 835 if (StringsAreEqual_Ascii(src + strlen(m), "sums")) 836 break; 837 } 838 UString res; 839 if (i != Z7_ARRAY_SIZE(k_CsumMethodNames)) 840 res = m; 841 return res; 842} 843 844 845bool CHashPair::Parse(const char *s) 846{ 847 // here we keep compatibility with original md5sum / shasum 848 bool escape = false; 849 850 s = SkipWhite(s); 851 852 if (*s == '\\') 853 { 854 s++; 855 escape = true; 856 } 857 858 // const char *kMethod = GetMethod_from_FileName(s); 859 // if (kMethod) 860 if (ParseHexString(s, NULL) < 4) 861 { 862 // BSD-style checksum line 863 { 864 const char *s2 = s; 865 for (; *s2 != 0; s2++) 866 { 867 const char c = *s2; 868 if (c == 0) 869 return false; 870 if (c == ' ' || c == '(') 871 break; 872 } 873 Method.SetFrom(s, (unsigned)(s2 - s)); 874 s = s2; 875 } 876 IsBSD = true; 877 if (*s == ' ') 878 s++; 879 if (*s != '(') 880 return false; 881 s++; 882 { 883 const char *s2 = s; 884 for (; *s2 != 0; s2++) 885 {} 886 for (;;) 887 { 888 s2--; 889 if (s2 < s) 890 return false; 891 if (*s2 == ')') 892 break; 893 } 894 Name.SetFrom(s, (unsigned)(s2 - s)); 895 s = s2 + 1; 896 } 897 898 s = SkipWhite(s); 899 if (*s != '=') 900 return false; 901 s++; 902 s = SkipWhite(s); 903 } 904 905 { 906 const size_t num = ParseHexString(s, NULL); 907 Hash.Alloc(num); 908 ParseHexString(s, Hash); 909 const size_t numChars = num * 2; 910 HashString.SetFrom(s, (unsigned)numChars); 911 s += numChars; 912 } 913 914 if (IsBSD) 915 { 916 if (*s != 0) 917 return false; 918 if (escape) 919 { 920 const AString temp (Name); 921 return CSum_Name_EscapeToOriginal(temp, Name); 922 } 923 return true; 924 } 925 926 if (*s == 0) 927 return true; 928 929 if (*s != ' ') 930 return false; 931 s++; 932 const char c = *s; 933 if (c != ' ' 934 && c != '*' 935 && c != 'U' // shasum Universal 936 && c != '^' // shasum 0/1 937 ) 938 return false; 939 Mode = c; 940 s++; 941 if (escape) 942 return CSum_Name_EscapeToOriginal(s, Name); 943 Name = s; 944 return true; 945} 946 947 948static bool GetLine(CByteBuffer &buf, bool zeroMode, bool cr_lf_Mode, size_t &posCur, AString &s) 949{ 950 s.Empty(); 951 size_t pos = posCur; 952 const Byte *p = buf; 953 unsigned numDigits = 0; 954 for (; pos < buf.Size(); pos++) 955 { 956 const Byte b = p[pos]; 957 if (b == 0) 958 { 959 numDigits = 1; 960 break; 961 } 962 if (zeroMode) 963 continue; 964 if (b == 0x0a) 965 { 966 numDigits = 1; 967 break; 968 } 969 if (!cr_lf_Mode) 970 continue; 971 if (b == 0x0d) 972 { 973 if (pos + 1 >= buf.Size()) 974 { 975 numDigits = 1; 976 break; 977 // return false; 978 } 979 if (p[pos + 1] == 0x0a) 980 { 981 numDigits = 2; 982 break; 983 } 984 } 985 } 986 s.SetFrom((const char *)(p + posCur), (unsigned)(pos - posCur)); 987 posCur = pos + numDigits; 988 return true; 989} 990 991 992static bool Is_CR_LF_Data(const Byte *buf, size_t size) 993{ 994 bool isCrLf = false; 995 for (size_t i = 0; i < size;) 996 { 997 const Byte b = buf[i]; 998 if (b == 0x0a) 999 return false; 1000 if (b == 0x0d) 1001 { 1002 if (i == size - 1) 1003 return false; 1004 if (buf[i + 1] != 0x0a) 1005 return false; 1006 isCrLf = true; 1007 i += 2; 1008 } 1009 else 1010 i++; 1011 } 1012 return isCrLf; 1013} 1014 1015 1016static const Byte kArcProps[] = 1017{ 1018 // kpidComment, 1019 kpidCharacts 1020}; 1021 1022static const Byte kProps[] = 1023{ 1024 kpidPath, 1025 kpidSize, 1026 kpidPackSize, 1027 kpidMethod 1028}; 1029 1030static const Byte kRawProps[] = 1031{ 1032 kpidChecksum 1033}; 1034 1035 1036Z7_COM7F_IMF(CHandler::GetParent(UInt32 /* index */ , UInt32 *parent, UInt32 *parentType)) 1037{ 1038 *parentType = NParentType::kDir; 1039 *parent = (UInt32)(Int32)-1; 1040 return S_OK; 1041} 1042 1043Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) 1044{ 1045 *numProps = Z7_ARRAY_SIZE(kRawProps); 1046 return S_OK; 1047} 1048 1049Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) 1050{ 1051 *propID = kRawProps[index]; 1052 *name = NULL; 1053 return S_OK; 1054} 1055 1056Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) 1057{ 1058 *data = NULL; 1059 *dataSize = 0; 1060 *propType = 0; 1061 1062 if (propID == kpidChecksum) 1063 { 1064 const CHashPair &hp = HashPairs[index]; 1065 if (hp.Hash.Size() > 0) 1066 { 1067 *data = hp.Hash; 1068 *dataSize = (UInt32)hp.Hash.Size(); 1069 *propType = NPropDataType::kRaw; 1070 } 1071 return S_OK; 1072 } 1073 1074 return S_OK; 1075} 1076 1077IMP_IInArchive_Props 1078IMP_IInArchive_ArcProps 1079 1080Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) 1081{ 1082 *numItems = HashPairs.Size(); 1083 return S_OK; 1084} 1085 1086static void Add_OptSpace_String(UString &dest, const char *src) 1087{ 1088 dest.Add_Space_if_NotEmpty(); 1089 dest += src; 1090} 1091 1092Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) 1093{ 1094 NWindows::NCOM::CPropVariant prop; 1095 switch (propID) 1096 { 1097 case kpidPhySize: if (_phySize != 0) prop = _phySize; break; 1098 /* 1099 case kpidErrorFlags: 1100 { 1101 UInt32 v = 0; 1102 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; 1103 // if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; 1104 if (v != 0) 1105 prop = v; 1106 break; 1107 } 1108 */ 1109 case kpidCharacts: 1110 { 1111 UString s; 1112 if (_hashSize_Defined) 1113 { 1114 s.Add_Space_if_NotEmpty(); 1115 s.Add_UInt32(_hashSize * 8); 1116 s += "-bit"; 1117 } 1118 if (!_nameExtenstion.IsEmpty()) 1119 { 1120 s.Add_Space_if_NotEmpty(); 1121 s += _nameExtenstion; 1122 } 1123 if (_is_PgpMethod) 1124 { 1125 Add_OptSpace_String(s, "PGP"); 1126 if (!_pgpMethod.IsEmpty()) 1127 { 1128 s += ":"; 1129 s += _pgpMethod; 1130 } 1131 } 1132 if (_is_ZeroMode) 1133 Add_OptSpace_String(s, "ZERO"); 1134 if (_are_there_Tags) 1135 Add_OptSpace_String(s, "TAG"); 1136 if (_are_there_Dirs) 1137 Add_OptSpace_String(s, "DIRS"); 1138 prop = s; 1139 break; 1140 } 1141 1142 case kpidReadOnly: 1143 { 1144 if (_isArc) 1145 if (!CanUpdate()) 1146 prop = true; 1147 break; 1148 } 1149 } 1150 prop.Detach(value); 1151 return S_OK; 1152} 1153 1154 1155Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) 1156{ 1157 // COM_TRY_BEGIN 1158 NWindows::NCOM::CPropVariant prop; 1159 CHashPair &hp = HashPairs[index]; 1160 switch (propID) 1161 { 1162 case kpidIsDir: 1163 { 1164 prop = hp.IsDir(); 1165 break; 1166 } 1167 case kpidPath: 1168 { 1169 UString path; 1170 hp.Get_UString_Path(path); 1171 1172 NArchive::NItemName::ReplaceToOsSlashes_Remove_TailSlash(path, 1173 true); // useBackslashReplacement 1174 1175 prop = path; 1176 break; 1177 } 1178 case kpidSize: 1179 { 1180 // client needs processed size of last file 1181 if (hp.Size_from_Disk_Defined) 1182 prop = (UInt64)hp.Size_from_Disk; 1183 else if (hp.Size_from_Arc_Defined) 1184 prop = (UInt64)hp.Size_from_Arc; 1185 break; 1186 } 1187 case kpidPackSize: 1188 { 1189 prop = (UInt64)hp.Hash.Size(); 1190 break; 1191 } 1192 case kpidMethod: 1193 { 1194 if (!hp.Method.IsEmpty()) 1195 prop = hp.Method; 1196 break; 1197 } 1198 } 1199 prop.Detach(value); 1200 return S_OK; 1201 // COM_TRY_END 1202} 1203 1204 1205static HRESULT ReadStream_to_Buf(IInStream *stream, CByteBuffer &buf, IArchiveOpenCallback *openCallback) 1206{ 1207 buf.Free(); 1208 UInt64 len; 1209 RINOK(InStream_AtBegin_GetSize(stream, len)) 1210 if (len == 0 || len >= ((UInt64)1 << 31)) 1211 return S_FALSE; 1212 buf.Alloc((size_t)len); 1213 UInt64 pos = 0; 1214 // return ReadStream_FALSE(stream, buf, (size_t)len); 1215 for (;;) 1216 { 1217 const UInt32 kBlockSize = ((UInt32)1 << 24); 1218 const UInt32 curSize = (len < kBlockSize) ? (UInt32)len : kBlockSize; 1219 UInt32 processedSizeLoc; 1220 RINOK(stream->Read((Byte *)buf + pos, curSize, &processedSizeLoc)) 1221 if (processedSizeLoc == 0) 1222 return E_FAIL; 1223 len -= processedSizeLoc; 1224 pos += processedSizeLoc; 1225 if (len == 0) 1226 return S_OK; 1227 if (openCallback) 1228 { 1229 const UInt64 files = 0; 1230 RINOK(openCallback->SetCompleted(&files, &pos)) 1231 } 1232 } 1233} 1234 1235 1236Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback)) 1237{ 1238 COM_TRY_BEGIN 1239 { 1240 Close(); 1241 1242 CByteBuffer buf; 1243 RINOK(ReadStream_to_Buf(stream, buf, openCallback)) 1244 1245 CObjectVector<CHashPair> &pairs = HashPairs; 1246 1247 bool zeroMode = false; 1248 bool cr_lf_Mode = false; 1249 { 1250 for (size_t i = 0; i < buf.Size(); i++) 1251 if (buf[i] == 0) 1252 { 1253 zeroMode = true; 1254 break; 1255 } 1256 } 1257 _is_ZeroMode = zeroMode; 1258 if (!zeroMode) 1259 cr_lf_Mode = Is_CR_LF_Data(buf, buf.Size()); 1260 1261 if (openCallback) 1262 { 1263 Z7_DECL_CMyComPtr_QI_FROM( 1264 IArchiveOpenVolumeCallback, 1265 openVolumeCallback, openCallback) 1266 if (openVolumeCallback) 1267 { 1268 NCOM::CPropVariant prop; 1269 RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) 1270 if (prop.vt == VT_BSTR) 1271 _nameExtenstion = GetMethod_from_FileName(prop.bstrVal); 1272 } 1273 } 1274 1275 bool cksumMode = false; 1276 if (_nameExtenstion.IsEqualTo_Ascii_NoCase("cksum")) 1277 cksumMode = true; 1278 _is_CksumMode = cksumMode; 1279 1280 size_t pos = 0; 1281 AString s; 1282 bool minusMode = false; 1283 unsigned numLines = 0; 1284 1285 while (pos < buf.Size()) 1286 { 1287 if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s)) 1288 return S_FALSE; 1289 numLines++; 1290 if (s.IsEmpty()) 1291 continue; 1292 1293 if (s.IsPrefixedBy_Ascii_NoCase("; ")) 1294 { 1295 if (numLines != 1) 1296 return S_FALSE; 1297 // comment line of FileVerifier++ 1298 continue; 1299 } 1300 1301 if (s.IsPrefixedBy_Ascii_NoCase("-----")) 1302 { 1303 if (minusMode) 1304 break; // end of pgp mode 1305 minusMode = true; 1306 if (s.IsPrefixedBy_Ascii_NoCase("-----BEGIN PGP SIGNED MESSAGE")) 1307 { 1308 if (_is_PgpMethod) 1309 return S_FALSE; 1310 if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s)) 1311 return S_FALSE; 1312 const char *kStart = "Hash: "; 1313 if (!s.IsPrefixedBy_Ascii_NoCase(kStart)) 1314 return S_FALSE; 1315 _pgpMethod = s.Ptr((unsigned)strlen(kStart)); 1316 _is_PgpMethod = true; 1317 } 1318 continue; 1319 } 1320 1321 CHashPair pair; 1322 pair.FullLine = s; 1323 if (cksumMode) 1324 { 1325 if (!pair.ParseCksum(s)) 1326 return S_FALSE; 1327 } 1328 else if (!pair.Parse(s)) 1329 return S_FALSE; 1330 pairs.Add(pair); 1331 } 1332 1333 { 1334 unsigned hashSize = 0; 1335 bool hashSize_Dismatch = false; 1336 for (unsigned i = 0; i < HashPairs.Size(); i++) 1337 { 1338 const CHashPair &hp = HashPairs[i]; 1339 if (i == 0) 1340 hashSize = (unsigned)hp.Hash.Size(); 1341 else 1342 if (hashSize != hp.Hash.Size()) 1343 hashSize_Dismatch = true; 1344 1345 if (hp.IsBSD) 1346 _are_there_Tags = true; 1347 if (!_are_there_Dirs && hp.IsDir()) 1348 _are_there_Dirs = true; 1349 } 1350 if (!hashSize_Dismatch && hashSize != 0) 1351 { 1352 _hashSize = hashSize; 1353 _hashSize_Defined = true; 1354 } 1355 } 1356 1357 _phySize = buf.Size(); 1358 _isArc = true; 1359 return S_OK; 1360 } 1361 COM_TRY_END 1362} 1363 1364 1365void CHandler::ClearVars() 1366{ 1367 _phySize = 0; 1368 _isArc = false; 1369 _is_CksumMode = false; 1370 _is_PgpMethod = false; 1371 _is_ZeroMode = false; 1372 _are_there_Tags = false; 1373 _are_there_Dirs = false; 1374 _hashSize_Defined = false; 1375 _hashSize = 0; 1376} 1377 1378 1379Z7_COM7F_IMF(CHandler::Close()) 1380{ 1381 ClearVars(); 1382 _nameExtenstion.Empty(); 1383 _pgpMethod.Empty(); 1384 HashPairs.Clear(); 1385 return S_OK; 1386} 1387 1388 1389static bool CheckDigests(const Byte *a, const Byte *b, size_t size) 1390{ 1391 if (size <= 8) 1392 { 1393 /* we use reversed order for one digest, when text representation 1394 uses big-order for crc-32 and crc-64 */ 1395 for (size_t i = 0; i < size; i++) 1396 if (a[i] != b[size - 1 - i]) 1397 return false; 1398 return true; 1399 } 1400 { 1401 for (size_t i = 0; i < size; i++) 1402 if (a[i] != b[i]) 1403 return false; 1404 return true; 1405 } 1406} 1407 1408 1409static void AddDefaultMethod(UStringVector &methods, unsigned size) 1410{ 1411 const char *m = NULL; 1412 if (size == 32) m = "sha256"; 1413 else if (size == 20) m = "sha1"; 1414 else if (size == 16) m = "md5"; 1415 else if (size == 8) m = "crc64"; 1416 else if (size == 4) m = "crc32"; 1417 else 1418 return; 1419 #ifdef Z7_EXTERNAL_CODECS 1420 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; 1421 #endif 1422 CMethodId id; 1423 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS 1424 AString(m), id)) 1425 methods.Add(UString(m)); 1426} 1427 1428 1429Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, 1430 Int32 testMode, IArchiveExtractCallback *extractCallback)) 1431{ 1432 COM_TRY_BEGIN 1433 1434 /* 1435 if (testMode == 0) 1436 return E_NOTIMPL; 1437 */ 1438 1439 const bool allFilesMode = (numItems == (UInt32)(Int32)-1); 1440 if (allFilesMode) 1441 numItems = HashPairs.Size(); 1442 if (numItems == 0) 1443 return S_OK; 1444 1445 #ifdef Z7_EXTERNAL_CODECS 1446 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; 1447 #endif 1448 1449 CHashBundle hb_Glob; 1450 // UStringVector methods = options.Methods; 1451 UStringVector methods; 1452 1453 if (methods.IsEmpty() && !_nameExtenstion.IsEmpty()) 1454 { 1455 AString utf; 1456 ConvertUnicodeToUTF8(_nameExtenstion, utf); 1457 CMethodId id; 1458 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS utf, id)) 1459 methods.Add(_nameExtenstion); 1460 } 1461 1462 if (methods.IsEmpty() && !_pgpMethod.IsEmpty()) 1463 { 1464 CMethodId id; 1465 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS _pgpMethod, id)) 1466 methods.Add(UString(_pgpMethod)); 1467 } 1468 1469 if (methods.IsEmpty() && _pgpMethod.IsEmpty() && _hashSize_Defined) 1470 AddDefaultMethod(methods, _hashSize); 1471 1472 RINOK(hb_Glob.SetMethods( 1473 EXTERNAL_CODECS_LOC_VARS 1474 methods)) 1475 1476 Z7_DECL_CMyComPtr_QI_FROM( 1477 IArchiveUpdateCallbackFile, 1478 updateCallbackFile, extractCallback) 1479 if (!updateCallbackFile) 1480 return E_NOTIMPL; 1481 { 1482 Z7_DECL_CMyComPtr_QI_FROM( 1483 IArchiveGetDiskProperty, 1484 GetDiskProperty, extractCallback) 1485 if (GetDiskProperty) 1486 { 1487 UInt64 totalSize = 0; 1488 UInt32 i; 1489 for (i = 0; i < numItems; i++) 1490 { 1491 const UInt32 index = allFilesMode ? i : indices[i]; 1492 const CHashPair &hp = HashPairs[index]; 1493 if (hp.IsDir()) 1494 continue; 1495 { 1496 NCOM::CPropVariant prop; 1497 RINOK(GetDiskProperty->GetDiskProperty(index, kpidSize, &prop)) 1498 if (prop.vt != VT_UI8) 1499 continue; 1500 totalSize += prop.uhVal.QuadPart; 1501 } 1502 } 1503 RINOK(extractCallback->SetTotal(totalSize)) 1504 // RINOK(Hash_SetTotalUnpacked->Hash_SetTotalUnpacked(indices, numItems)); 1505 } 1506 } 1507 1508 const UInt32 kBufSize = 1 << 15; 1509 CHashMidBuf buf; 1510 if (!buf.Alloc(kBufSize)) 1511 return E_OUTOFMEMORY; 1512 1513 CLocalProgress *lps = new CLocalProgress; 1514 CMyComPtr<ICompressProgressInfo> progress = lps; 1515 lps->Init(extractCallback, false); 1516 lps->InSize = lps->OutSize = 0; 1517 1518 UInt32 i; 1519 for (i = 0; i < numItems; i++) 1520 { 1521 RINOK(lps->SetCur()) 1522 const UInt32 index = allFilesMode ? i : indices[i]; 1523 1524 CHashPair &hp = HashPairs[index]; 1525 1526 UString path; 1527 hp.Get_UString_Path(path); 1528 1529 CMyComPtr<ISequentialInStream> inStream; 1530 const bool isDir = hp.IsDir(); 1531 if (!isDir) 1532 { 1533 RINOK(updateCallbackFile->GetStream2(index, &inStream, NUpdateNotifyOp::kHashRead)) 1534 if (!inStream) 1535 { 1536 continue; // we have shown error in GetStream2() 1537 } 1538 // askMode = NArchive::NExtract::NAskMode::kSkip; 1539 } 1540 1541 Int32 askMode = testMode ? 1542 NArchive::NExtract::NAskMode::kTest : 1543 NArchive::NExtract::NAskMode::kExtract; 1544 1545 CMyComPtr<ISequentialOutStream> realOutStream; 1546 RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) 1547 1548 /* PrepareOperation() can expect kExtract to set 1549 Attrib and security of output file */ 1550 askMode = NArchive::NExtract::NAskMode::kReadExternal; 1551 1552 extractCallback->PrepareOperation(askMode); 1553 1554 const bool isAltStream = false; 1555 1556 UInt64 fileSize = 0; 1557 1558 CHashBundle hb_Loc; 1559 1560 CHashBundle *hb_Use = &hb_Glob; 1561 1562 HRESULT res_SetMethods = S_OK; 1563 1564 UStringVector methods_loc; 1565 1566 if (!hp.Method.IsEmpty()) 1567 { 1568 hb_Use = &hb_Loc; 1569 CMethodId id; 1570 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS hp.Method, id)) 1571 { 1572 methods_loc.Add(UString(hp.Method)); 1573 RINOK(hb_Loc.SetMethods( 1574 EXTERNAL_CODECS_LOC_VARS 1575 methods_loc)) 1576 } 1577 else 1578 res_SetMethods = E_NOTIMPL; 1579 } 1580 else if (methods.IsEmpty()) 1581 { 1582 AddDefaultMethod(methods_loc, (unsigned)hp.Hash.Size()); 1583 if (!methods_loc.IsEmpty()) 1584 { 1585 hb_Use = &hb_Loc; 1586 RINOK(hb_Loc.SetMethods( 1587 EXTERNAL_CODECS_LOC_VARS 1588 methods_loc)) 1589 } 1590 } 1591 1592 const bool isSupportedMode = hp.IsSupportedMode(); 1593 hb_Use->InitForNewFile(); 1594 1595 if (inStream) 1596 { 1597 for (UInt32 step = 0;; step++) 1598 { 1599 if ((step & 0xFF) == 0) 1600 { 1601 RINOK(progress->SetRatioInfo(NULL, &fileSize)) 1602 } 1603 UInt32 size; 1604 RINOK(inStream->Read(buf, kBufSize, &size)) 1605 if (size == 0) 1606 break; 1607 hb_Use->Update(buf, size); 1608 if (realOutStream) 1609 { 1610 RINOK(WriteStream(realOutStream, buf, size)) 1611 } 1612 fileSize += size; 1613 } 1614 1615 hp.Size_from_Disk = fileSize; 1616 hp.Size_from_Disk_Defined = true; 1617 } 1618 1619 realOutStream.Release(); 1620 inStream.Release(); 1621 1622 lps->InSize += hp.Hash.Size(); 1623 lps->OutSize += fileSize; 1624 1625 hb_Use->Final(isDir, isAltStream, path); 1626 1627 Int32 opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod; 1628 if (isSupportedMode 1629 && res_SetMethods != E_NOTIMPL 1630 && hb_Use->Hashers.Size() > 0 1631 ) 1632 { 1633 const CHasherState &hs = hb_Use->Hashers[0]; 1634 if (hs.DigestSize == hp.Hash.Size()) 1635 { 1636 opRes = NArchive::NExtract::NOperationResult::kCRCError; 1637 if (CheckDigests(hp.Hash, hs.Digests[0], hs.DigestSize)) 1638 if (!hp.Size_from_Arc_Defined || hp.Size_from_Arc == fileSize) 1639 opRes = NArchive::NExtract::NOperationResult::kOK; 1640 } 1641 } 1642 1643 RINOK(extractCallback->SetOperationResult(opRes)) 1644 } 1645 1646 return lps->SetCur(); 1647 1648 COM_TRY_END 1649} 1650 1651 1652// ---------- UPDATE ---------- 1653 1654struct CUpdateItem 1655{ 1656 int IndexInArc; 1657 unsigned IndexInClient; 1658 UInt64 Size; 1659 bool NewData; 1660 bool NewProps; 1661 bool IsDir; 1662 UString Path; 1663 1664 CUpdateItem(): Size(0), IsDir(false) {} 1665}; 1666 1667 1668static HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, 1669 UString &res, 1670 bool convertSlash) 1671{ 1672 NCOM::CPropVariant prop; 1673 RINOK(callback->GetProperty(index, propId, &prop)) 1674 if (prop.vt == VT_BSTR) 1675 { 1676 res = prop.bstrVal; 1677 if (convertSlash) 1678 NArchive::NItemName::ReplaceSlashes_OsToUnix(res); 1679 } 1680 else if (prop.vt != VT_EMPTY) 1681 return E_INVALIDARG; 1682 return S_OK; 1683} 1684 1685 1686Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) 1687{ 1688 *type = NFileTimeType::kUnix; 1689 return S_OK; 1690} 1691 1692 1693Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, 1694 IArchiveUpdateCallback *callback)) 1695{ 1696 COM_TRY_BEGIN 1697 1698 if (_isArc && !CanUpdate()) 1699 return E_NOTIMPL; 1700 1701 /* 1702 Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp, 1703 reportArcProp, callback) 1704 */ 1705 1706 CObjectVector<CUpdateItem> updateItems; 1707 1708 UInt64 complexity = 0; 1709 1710 UInt32 i; 1711 for (i = 0; i < numItems; i++) 1712 { 1713 CUpdateItem ui; 1714 Int32 newData; 1715 Int32 newProps; 1716 UInt32 indexInArc; 1717 1718 if (!callback) 1719 return E_FAIL; 1720 1721 RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)) 1722 1723 ui.NewProps = IntToBool(newProps); 1724 ui.NewData = IntToBool(newData); 1725 ui.IndexInArc = (int)indexInArc; 1726 ui.IndexInClient = i; 1727 if (IntToBool(newProps)) 1728 { 1729 { 1730 NCOM::CPropVariant prop; 1731 RINOK(callback->GetProperty(i, kpidIsDir, &prop)) 1732 if (prop.vt == VT_EMPTY) 1733 ui.IsDir = false; 1734 else if (prop.vt != VT_BOOL) 1735 return E_INVALIDARG; 1736 else 1737 ui.IsDir = (prop.boolVal != VARIANT_FALSE); 1738 } 1739 1740 RINOK(GetPropString(callback, i, kpidPath, ui.Path, 1741 true)) // convertSlash 1742 /* 1743 if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/') 1744 ui.Name += '/'; 1745 */ 1746 } 1747 1748 if (IntToBool(newData)) 1749 { 1750 NCOM::CPropVariant prop; 1751 RINOK(callback->GetProperty(i, kpidSize, &prop)) 1752 if (prop.vt == VT_UI8) 1753 { 1754 ui.Size = prop.uhVal.QuadPart; 1755 complexity += ui.Size; 1756 } 1757 else if (prop.vt == VT_EMPTY) 1758 ui.Size = (UInt64)(Int64)-1; 1759 else 1760 return E_INVALIDARG; 1761 } 1762 1763 updateItems.Add(ui); 1764 } 1765 1766 if (complexity != 0) 1767 { 1768 RINOK(callback->SetTotal(complexity)) 1769 } 1770 1771 #ifdef Z7_EXTERNAL_CODECS 1772 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; 1773 #endif 1774 1775 CHashBundle hb; 1776 UStringVector methods; 1777 if (!_methods.IsEmpty()) 1778 { 1779 FOR_VECTOR(k, _methods) 1780 { 1781 methods.Add(_methods[k]); 1782 } 1783 } 1784 else if (_crcSize_WasSet) 1785 { 1786 AddDefaultMethod(methods, _crcSize); 1787 } 1788 else 1789 { 1790 Z7_DECL_CMyComPtr_QI_FROM( 1791 IArchiveGetRootProps, 1792 getRootProps, callback) 1793 if (getRootProps) 1794 { 1795 NCOM::CPropVariant prop; 1796 RINOK(getRootProps->GetRootProp(kpidArcFileName, &prop)) 1797 if (prop.vt == VT_BSTR) 1798 { 1799 const UString method = GetMethod_from_FileName(prop.bstrVal); 1800 if (!method.IsEmpty()) 1801 methods.Add(method); 1802 } 1803 } 1804 } 1805 1806 RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS methods)) 1807 1808 CLocalProgress *lps = new CLocalProgress; 1809 CMyComPtr<ICompressProgressInfo> progress = lps; 1810 lps->Init(callback, true); 1811 1812 const UInt32 kBufSize = 1 << 15; 1813 CHashMidBuf buf; 1814 if (!buf.Alloc(kBufSize)) 1815 return E_OUTOFMEMORY; 1816 1817 CDynLimBuf hashFileString((size_t)1 << 31); 1818 1819 CHashOptionsLocal options = _options; 1820 1821 if (_isArc) 1822 { 1823 if (!options.HashMode_Zero.Def && _is_ZeroMode) 1824 options.HashMode_Zero.Val = true; 1825 if (!options.HashMode_Tag.Def && _are_there_Tags) 1826 options.HashMode_Tag.Val = true; 1827 if (!options.HashMode_Dirs.Def && _are_there_Dirs) 1828 options.HashMode_Dirs.Val = true; 1829 } 1830 if (options.HashMode_OnlyHash.Val && updateItems.Size() != 1) 1831 options.HashMode_OnlyHash.Val = false; 1832 1833 lps->OutSize = 0; 1834 complexity = 0; 1835 1836 for (i = 0; i < updateItems.Size(); i++) 1837 { 1838 lps->InSize = complexity; 1839 RINOK(lps->SetCur()) 1840 1841 const CUpdateItem &ui = updateItems[i]; 1842 1843 /* 1844 CHashPair item; 1845 if (!ui.NewProps) 1846 item = HashPairs[(unsigned)ui.IndexInArc]; 1847 */ 1848 1849 if (ui.NewData) 1850 { 1851 UInt64 currentComplexity = ui.Size; 1852 UInt64 fileSize = 0; 1853 1854 CMyComPtr<ISequentialInStream> fileInStream; 1855 bool needWrite = true; 1856 { 1857 HRESULT res = callback->GetStream(ui.IndexInClient, &fileInStream); 1858 1859 if (res == S_FALSE) 1860 needWrite = false; 1861 else 1862 { 1863 RINOK(res) 1864 1865 if (fileInStream) 1866 { 1867 Z7_DECL_CMyComPtr_QI_FROM( 1868 IStreamGetSize, 1869 streamGetSize, fileInStream) 1870 if (streamGetSize) 1871 { 1872 UInt64 size; 1873 if (streamGetSize->GetSize(&size) == S_OK) 1874 currentComplexity = size; 1875 } 1876 /* 1877 Z7_DECL_CMyComPtr_QI_FROM( 1878 IStreamGetProps, 1879 getProps, fileInStream) 1880 if (getProps) 1881 { 1882 FILETIME mTime; 1883 UInt64 size2; 1884 if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK) 1885 { 1886 currentComplexity = size2; 1887 // item.MTime = NWindows::NTime::FileTimeToUnixTime64(mTime);; 1888 } 1889 } 1890 */ 1891 } 1892 else 1893 { 1894 currentComplexity = 0; 1895 } 1896 } 1897 } 1898 1899 hb.InitForNewFile(); 1900 const bool isDir = ui.IsDir; 1901 1902 if (needWrite && fileInStream && !isDir) 1903 { 1904 for (UInt32 step = 0;; step++) 1905 { 1906 if ((step & 0xFF) == 0) 1907 { 1908 RINOK(progress->SetRatioInfo(&fileSize, NULL)) 1909 // RINOK(callback->SetCompleted(&completeValue)); 1910 } 1911 UInt32 size; 1912 RINOK(fileInStream->Read(buf, kBufSize, &size)) 1913 if (size == 0) 1914 break; 1915 hb.Update(buf, size); 1916 fileSize += size; 1917 } 1918 currentComplexity = fileSize; 1919 } 1920 1921 fileInStream.Release(); 1922 const bool isAltStream = false; 1923 hb.Final(isDir, isAltStream, ui.Path); 1924 1925 if (options.HashMode_Dirs.Val || !isDir) 1926 { 1927 if (!hb.Hashers.IsEmpty()) 1928 lps->OutSize += hb.Hashers[0].DigestSize; 1929 WriteLine(hashFileString, 1930 options, 1931 ui.Path, 1932 isDir, 1933 hb); 1934 if (hashFileString.IsError()) 1935 return E_OUTOFMEMORY; 1936 } 1937 1938 complexity += currentComplexity; 1939 1940 /* 1941 if (reportArcProp) 1942 { 1943 PROPVARIANT prop; 1944 prop.vt = VT_EMPTY; 1945 prop.wReserved1 = 0; 1946 1947 NCOM::PropVarEm_Set_UInt64(&prop, fileSize); 1948 RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidSize, &prop)); 1949 1950 for (unsigned k = 0; k < hb.Hashers.Size(); k++) 1951 { 1952 const CHasherState &hs = hb.Hashers[k]; 1953 1954 if (hs.DigestSize == 4 && hs.Name.IsEqualTo_Ascii_NoCase("crc32")) 1955 { 1956 NCOM::PropVarEm_Set_UInt32(&prop, GetUi32(hs.Digests[k_HashCalc_Index_Current])); 1957 RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidCRC, &prop)); 1958 } 1959 else 1960 { 1961 RINOK(reportArcProp->ReportRawProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, 1962 kpidChecksum, hs.Digests[k_HashCalc_Index_Current], 1963 hs.DigestSize, NPropDataType::kRaw)); 1964 } 1965 RINOK(reportArcProp->ReportFinished(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, NArchive::NUpdate::NOperationResult::kOK)); 1966 } 1967 } 1968 */ 1969 RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) 1970 } 1971 else 1972 { 1973 // old data 1974 const CHashPair &existItem = HashPairs[(unsigned)ui.IndexInArc]; 1975 if (ui.NewProps) 1976 { 1977 WriteLine(hashFileString, 1978 options, 1979 ui.Path, 1980 ui.IsDir, 1981 existItem.Method, existItem.HashString 1982 ); 1983 } 1984 else 1985 { 1986 hashFileString += existItem.FullLine; 1987 Add_LF(hashFileString, options); 1988 } 1989 } 1990 if (hashFileString.IsError()) 1991 return E_OUTOFMEMORY; 1992 } 1993 1994 RINOK(WriteStream(outStream, hashFileString, hashFileString.Len())) 1995 1996 return S_OK; 1997 COM_TRY_END 1998} 1999 2000 2001 2002HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value) 2003{ 2004 UString name = nameSpec; 2005 name.MakeLower_Ascii(); 2006 if (name.IsEmpty()) 2007 return E_INVALIDARG; 2008 2009 if (name.IsEqualTo("m")) // "hm" hash method 2010 { 2011 // COneMethodInfo omi; 2012 // RINOK(omi.ParseMethodFromPROPVARIANT(L"", value)); 2013 // _methods.Add(omi.MethodName); // change it. use omi.PropsString 2014 if (value.vt != VT_BSTR) 2015 return E_INVALIDARG; 2016 UString s (value.bstrVal); 2017 _methods.Add(s); 2018 return S_OK; 2019 } 2020 2021 if (name.IsEqualTo("flags")) 2022 { 2023 if (value.vt != VT_BSTR) 2024 return E_INVALIDARG; 2025 if (!_options.ParseString(value.bstrVal)) 2026 return E_INVALIDARG; 2027 return S_OK; 2028 } 2029 2030 if (name.IsPrefixedBy_Ascii_NoCase("crc")) 2031 { 2032 name.Delete(0, 3); 2033 _crcSize = 4; 2034 _crcSize_WasSet = true; 2035 return ParsePropToUInt32(name, value, _crcSize); 2036 } 2037 2038 // common properties 2039 if (name.IsPrefixedBy_Ascii_NoCase("mt") 2040 || name.IsPrefixedBy_Ascii_NoCase("memuse")) 2041 return S_OK; 2042 2043 return E_INVALIDARG; 2044} 2045 2046 2047Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) 2048{ 2049 COM_TRY_BEGIN 2050 2051 InitProps(); 2052 2053 for (UInt32 i = 0; i < numProps; i++) 2054 { 2055 RINOK(SetProperty(names[i], values[i])) 2056 } 2057 return S_OK; 2058 COM_TRY_END 2059} 2060 2061CHandler::CHandler() 2062{ 2063 ClearVars(); 2064 InitProps(); 2065} 2066 2067} 2068 2069 2070 2071static IInArchive *CreateHashHandler_In() { return new NHash::CHandler; } 2072static IOutArchive *CreateHashHandler_Out() { return new NHash::CHandler; } 2073 2074void Codecs_AddHashArcHandler(CCodecs *codecs) 2075{ 2076 { 2077 CArcInfoEx item; 2078 2079 item.Name = "Hash"; 2080 item.CreateInArchive = CreateHashHandler_In; 2081 item.CreateOutArchive = CreateHashHandler_Out; 2082 item.IsArcFunc = NULL; 2083 item.Flags = 2084 NArcInfoFlags::kKeepName 2085 | NArcInfoFlags::kStartOpen 2086 | NArcInfoFlags::kByExtOnlyOpen 2087 // | NArcInfoFlags::kPureStartOpen 2088 | NArcInfoFlags::kHashHandler 2089 ; 2090 2091 // ubuntu uses "SHA256SUMS" file 2092 item.AddExts(UString ( 2093 "sha256 sha512 sha224 sha384 sha1 sha md5" 2094 // "b2sum" 2095 " crc32 crc64" 2096 " asc" 2097 " cksum" 2098 ), 2099 UString()); 2100 2101 item.UpdateEnabled = (item.CreateOutArchive != NULL); 2102 item.SignatureOffset = 0; 2103 // item.Version = MY_VER_MIX; 2104 item.NewInterface = true; 2105 2106 item.Signatures.AddNew().CopyFrom(NULL, 0); 2107 2108 codecs->Formats.Add(item); 2109 } 2110} 2111