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
23 using namespace NWindows;
24
25 #ifdef Z7_EXTERNAL_CODECS
26 extern const CExternalCodecs *g_ExternalCodecs_Ptr;
27 #endif
28
29 class CHashMidBuf
30 {
31 void *_data;
32 public:
CHashMidBuf()33 CHashMidBuf(): _data(NULL) {}
operator void*()34 operator void *() { return _data; }
Alloc(size_t size)35 bool Alloc(size_t size)
36 {
37 if (_data)
38 return false;
39 _data = ::MidAlloc(size);
40 return _data != NULL;
41 }
~CHashMidBuf()42 ~CHashMidBuf() { ::MidFree(_data); }
43 };
44
45 static const char * const k_DefaultHashMethod = "CRC32";
46
SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector & hashMethods)47 HRESULT 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
InitForNewFile()119 void 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
Update(const void * data,UInt32 size)130 void 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
SetSize(UInt64 size)137 void CHashBundle::SetSize(UInt64 size)
138 {
139 CurSize = size;
140 }
141
AddDigests(Byte * dest,const Byte * src,UInt32 size)142 static 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
AddDigest(unsigned groupIndex,const Byte * data)179 void CHasherState::AddDigest(unsigned groupIndex, const Byte *data)
180 {
181 NumSums[groupIndex]++;
182 AddDigests(Digests[groupIndex], data, DigestSize);
183 }
184
Final(bool isDir,bool isAltStream,const UString & path)185 void 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
CSum_Name_OriginalToEscape(const AString & src,AString & dest)244 static 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.Add_Char('\\');
253 c = 'n';
254 }
255 else if (c == '\\')
256 dest.Add_Char('\\');
257 dest.Add_Char(c);
258 }
259 }
260
261
CSum_Name_EscapeToOriginal(const char * s,AString & dest)262 static 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.Add_Char(c);
291 }
292 return isOK;
293 }
294
295
296
SetSpacesAndNul(char * s,unsigned num)297 static void SetSpacesAndNul(char *s, unsigned num)
298 {
299 for (unsigned i = 0; i < num; i++)
300 s[i] = ' ';
301 s[num] = 0;
302 }
303
304 static const unsigned kHashColumnWidth_Min = 4 * 2;
305
GetColumnWidth(unsigned digestSize)306 static unsigned GetColumnWidth(unsigned digestSize)
307 {
308 const unsigned width = digestSize * 2;
309 return width < kHashColumnWidth_Min ? kHashColumnWidth_Min: width;
310 }
311
312
AddHashResultLine(AString & _s,const CObjectVector<CHasherState> & hashers)313 static 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
Add_LF(CDynLimBuf & hashFileString,const CHashOptionsLocal & options)356 static void Add_LF(CDynLimBuf &hashFileString, const CHashOptionsLocal &options)
357 {
358 hashFileString += (char)(options.HashMode_Zero.Val ? 0 : '\n');
359 }
360
361
362
363
WriteLine(CDynLimBuf & hashFileString,const CHashOptionsLocal & options,const UString & path2,bool isDir,const AString & methodName,const AString & hashesString)364 static 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.Add_Slash();
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
WriteLine(CDynLimBuf & hashFileString,const CHashOptionsLocal & options,const UString & path,bool isDir,const CHashBundle & hb)435 static 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
HashCalc(DECL_EXTERNAL_CODECS_LOC_VARS const NWildcard::CCensor & censor,const CHashOptions & options,AString & errorInfo,IHashCallbackUI * callback)451 HRESULT 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 if (!di.SetAs_StdInFile())
465 return GetLastError_noZero_HRESULT();
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 #if 1
535 inStream = new CStdInFileStream;
536 #else
537 if (!CreateStdInStream(inStream))
538 {
539 const DWORD lastError = ::GetLastError();
540 const HRESULT res = callback->OpenFileError(FString("stdin"), lastError);
541 hb.NumErrors++;
542 if (res != S_FALSE && res != S_OK)
543 return res;
544 continue;
545 }
546 #endif
547 }
548 else
549 {
550 path = dirItems.GetLogPath(i);
551 const CDirItem &di = dirItems.Items[i];
552 #ifdef _WIN32
553 isAltStream = di.IsAltStream;
554 #endif
555
556 #ifndef UNDER_CE
557 // if (di.AreReparseData())
558 if (di.ReparseData.Size() != 0)
559 {
560 CBufInStream *inStreamSpec = new CBufInStream();
561 inStream = inStreamSpec;
562 inStreamSpec->Init(di.ReparseData, di.ReparseData.Size());
563 }
564 else
565 #endif
566 {
567 CInFileStream *inStreamSpec = new CInFileStream;
568 inStreamSpec->Set_PreserveATime(options.PreserveATime);
569 inStream = inStreamSpec;
570 isDir = di.IsDir();
571 if (!isDir)
572 {
573 const FString phyPath = dirItems.GetPhyPath(i);
574 if (!inStreamSpec->OpenShared(phyPath, options.OpenShareForWrite))
575 {
576 const HRESULT res = callback->OpenFileError(phyPath, ::GetLastError());
577 hb.NumErrors++;
578 if (res != S_FALSE)
579 return res;
580 continue;
581 }
582 if (!options.StdInMode)
583 {
584 UInt64 curSize = 0;
585 if (inStreamSpec->GetSize(&curSize) == S_OK)
586 {
587 if (curSize > di.Size)
588 {
589 totalSize += curSize - di.Size;
590 RINOK(callback->SetTotal(totalSize))
591 // printf("\ntotal = %d MiB\n", (unsigned)(totalSize >> 20));
592 }
593 }
594 }
595 // inStreamSpec->ReloadProps();
596 }
597 }
598 }
599
600 RINOK(callback->GetStream(path, isDir))
601 UInt64 fileSize = 0;
602
603 hb.InitForNewFile();
604
605 if (!isDir)
606 {
607 for (UInt32 step = 0;; step++)
608 {
609 if ((step & 0xFF) == 0)
610 {
611 // printf("\ncompl = %d\n", (unsigned)(completeValue >> 20));
612 RINOK(callback->SetCompleted(&completeValue))
613 }
614 UInt32 size;
615 RINOK(inStream->Read(buf, kBufSize, &size))
616 if (size == 0)
617 break;
618 hb.Update(buf, size);
619 fileSize += size;
620 completeValue += size;
621 }
622 }
623
624 hb.Final(isDir, isAltStream, path);
625
626 /*
627 if (needGenerate
628 && (options.HashMode_Dirs.Val || !isDir))
629 {
630 WriteLine(hashFileString,
631 options,
632 path, // change it
633 isDir,
634 hb);
635
636 if (hashFileString.IsError())
637 return E_OUTOFMEMORY;
638 }
639 */
640
641 RINOK(callback->SetOperationResult(fileSize, hb, !isDir))
642 RINOK(callback->SetCompleted(&completeValue))
643 }
644
645 /*
646 if (needGenerate)
647 {
648 NFile::NIO::COutFile file;
649 if (!file.Create(us2fs(options.HashFilePath), true)) // createAlways
650 return GetLastError_noZero_HRESULT();
651 if (!file.WriteFull(hashFileString, hashFileString.Len()))
652 return GetLastError_noZero_HRESULT();
653 }
654 */
655
656 return callback->AfterLastFile(hb);
657 }
658
659
HashHexToString(char * dest,const Byte * data,size_t size)660 void HashHexToString(char *dest, const Byte *data, size_t size)
661 {
662 if (!data)
663 {
664 for (size_t i = 0; i < size; i++)
665 {
666 dest[0] = ' ';
667 dest[1] = ' ';
668 dest += 2;
669 }
670 *dest = 0;
671 return;
672 }
673
674 if (size > 8)
675 ConvertDataToHex_Lower(dest, data, size);
676 else if (size == 0)
677 {
678 *dest = 0;
679 return;
680 }
681 else
682 {
683 const char *dest_start = dest;
684 dest += size * 2;
685 *dest = 0;
686 do
687 {
688 const size_t b = *data++;
689 dest -= 2;
690 dest[0] = GET_HEX_CHAR_UPPER(b >> 4);
691 dest[1] = GET_HEX_CHAR_UPPER(b & 15);
692 }
693 while (dest != dest_start);
694 }
695 }
696
WriteToString(unsigned digestIndex,char * s) const697 void CHasherState::WriteToString(unsigned digestIndex, char *s) const
698 {
699 HashHexToString(s, Digests[digestIndex], DigestSize);
700
701 if (digestIndex != 0 && NumSums[digestIndex] != 1)
702 {
703 unsigned numExtraBytes = GetNumExtraBytes_for_Group(digestIndex);
704 if (numExtraBytes > 4)
705 numExtraBytes = 8;
706 else // if (numExtraBytes >= 0)
707 numExtraBytes = 4;
708 // if (numExtraBytes != 0)
709 {
710 s += strlen(s);
711 *s++ = '-';
712 // *s = 0;
713 HashHexToString(s, GetExtraData_for_Group(digestIndex), numExtraBytes);
714 }
715 }
716 }
717
718
719
720 // ---------- Hash Handler ----------
721
722 namespace NHash {
723
724 #define IsWhite(c) ((c) == ' ' || (c) == '\t')
725
IsDir() const726 bool CHashPair::IsDir() const
727 {
728 if (Name.IsEmpty() || Name.Back() != '/')
729 return false;
730 // here we expect that Dir items contain only zeros or no Hash
731 for (size_t i = 0; i < Hash.Size(); i++)
732 if (Hash.ConstData()[i] != 0)
733 return false;
734 return true;
735 }
736
737
ParseCksum(const char * s)738 bool CHashPair::ParseCksum(const char *s)
739 {
740 const char *end;
741
742 const UInt32 crc = ConvertStringToUInt32(s, &end);
743 if (*end != ' ')
744 return false;
745 end++;
746
747 const UInt64 size = ConvertStringToUInt64(end, &end);
748 if (*end != ' ')
749 return false;
750 end++;
751
752 Name = end;
753
754 Hash.Alloc(4);
755 SetBe32(Hash, crc)
756
757 Size_from_Arc = size;
758 Size_from_Arc_Defined = true;
759
760 return true;
761 }
762
763
764
SkipWhite(const char * s)765 static const char *SkipWhite(const char *s)
766 {
767 while (IsWhite(*s))
768 s++;
769 return s;
770 }
771
772 static const char * const k_CsumMethodNames[] =
773 {
774 "sha256"
775 , "sha224"
776 // , "sha512-224"
777 // , "sha512-256"
778 , "sha384"
779 , "sha512"
780 // , "sha3-224"
781 , "sha3-256"
782 // , "sha3-384"
783 // , "sha3-512"
784 // , "shake128"
785 // , "shake256"
786 , "sha1"
787 , "md5"
788 , "blake2sp"
789 , "blake2b"
790 , "xxh64"
791 , "crc64"
792 , "crc32"
793 , "cksum"
794 };
795
GetMethod_from_FileName(const UString & name)796 static UString GetMethod_from_FileName(const UString &name)
797 {
798 AString s;
799 ConvertUnicodeToUTF8(name, s);
800 const int dotPos = s.ReverseFind_Dot();
801 const char *src = s.Ptr();
802 bool isExtension = false;
803 if (dotPos >= 0)
804 {
805 isExtension = true;
806 src = s.Ptr(dotPos + 1);
807 }
808 const char *m = "";
809 unsigned i;
810 for (i = 0; i < Z7_ARRAY_SIZE(k_CsumMethodNames); i++)
811 {
812 m = k_CsumMethodNames[i];
813 if (isExtension)
814 {
815 if (StringsAreEqual_Ascii(src, m))
816 break;
817 }
818 else if (IsString1PrefixedByString2_NoCase_Ascii(src, m))
819 if (StringsAreEqual_Ascii(src + strlen(m), "sums"))
820 break;
821 }
822 UString res;
823 if (i != Z7_ARRAY_SIZE(k_CsumMethodNames))
824 res = m;
825 return res;
826 }
827
828
Parse(const char * s)829 bool CHashPair::Parse(const char *s)
830 {
831 // here we keep compatibility with original md5sum / shasum
832 bool escape = false;
833
834 s = SkipWhite(s);
835
836 if (*s == '\\')
837 {
838 s++;
839 escape = true;
840 }
841 Escape = escape;
842
843 // const char *kMethod = GetMethod_from_FileName(s);
844 // if (kMethod)
845 if ((size_t)(FindNonHexChar(s) - s) < 4)
846 {
847 // BSD-style checksum line
848 {
849 const char *s2 = s;
850 for (; *s2 != 0; s2++)
851 {
852 const char c = *s2;
853 if (c == 0)
854 return false;
855 if (c == ' ' || c == '(')
856 break;
857 }
858 Method.SetFrom(s, (unsigned)(s2 - s));
859 s = s2;
860 }
861 IsBSD = true;
862 if (*s == ' ')
863 s++;
864 if (*s != '(')
865 return false;
866 s++;
867 {
868 const char *s2 = s;
869 for (; *s2 != 0; s2++)
870 {}
871 for (;;)
872 {
873 s2--;
874 if (s2 < s)
875 return false;
876 if (*s2 == ')')
877 break;
878 }
879 Name.SetFrom(s, (unsigned)(s2 - s));
880 s = s2 + 1;
881 }
882
883 s = SkipWhite(s);
884 if (*s != '=')
885 return false;
886 s++;
887 s = SkipWhite(s);
888 }
889
890 {
891 const size_t numChars = (size_t)(FindNonHexChar(s) - s) & ~(size_t)1;
892 Hash.Alloc(numChars / 2);
893 if ((size_t)(ParseHexString(s, Hash) - Hash) != numChars / 2)
894 throw 101;
895 HashString.SetFrom(s, (unsigned)numChars);
896 s += numChars;
897 }
898
899 if (IsBSD)
900 {
901 if (*s != 0)
902 return false;
903 if (escape)
904 {
905 const AString temp (Name);
906 return CSum_Name_EscapeToOriginal(temp, Name);
907 }
908 return true;
909 }
910
911 if (*s == 0)
912 return true;
913
914 if (*s != ' ')
915 return false;
916 s++;
917 const char c = *s;
918 if (c != ' '
919 && c != '*'
920 && c != 'U' // shasum Universal
921 && c != '^' // shasum 0/1
922 )
923 return false;
924 Mode = c;
925 s++;
926 if (escape)
927 return CSum_Name_EscapeToOriginal(s, Name);
928 Name = s;
929 return true;
930 }
931
932
GetLine(CByteBuffer & buf,bool zeroMode,bool cr_lf_Mode,size_t & posCur,AString & s)933 static bool GetLine(CByteBuffer &buf, bool zeroMode, bool cr_lf_Mode, size_t &posCur, AString &s)
934 {
935 s.Empty();
936 size_t pos = posCur;
937 const Byte *p = buf;
938 unsigned numDigits = 0;
939 for (; pos < buf.Size(); pos++)
940 {
941 const Byte b = p[pos];
942 if (b == 0)
943 {
944 numDigits = 1;
945 break;
946 }
947 if (zeroMode)
948 continue;
949 if (b == 0x0a)
950 {
951 numDigits = 1;
952 break;
953 }
954 if (!cr_lf_Mode)
955 continue;
956 if (b == 0x0d)
957 {
958 if (pos + 1 >= buf.Size())
959 {
960 numDigits = 1;
961 break;
962 // return false;
963 }
964 if (p[pos + 1] == 0x0a)
965 {
966 numDigits = 2;
967 break;
968 }
969 }
970 }
971 s.SetFrom((const char *)(p + posCur), (unsigned)(pos - posCur));
972 posCur = pos + numDigits;
973 return true;
974 }
975
976
Is_CR_LF_Data(const Byte * buf,size_t size)977 static bool Is_CR_LF_Data(const Byte *buf, size_t size)
978 {
979 bool isCrLf = false;
980 for (size_t i = 0; i < size;)
981 {
982 const Byte b = buf[i];
983 if (b == 0x0a)
984 return false;
985 if (b == 0x0d)
986 {
987 if (i == size - 1)
988 return false;
989 if (buf[i + 1] != 0x0a)
990 return false;
991 isCrLf = true;
992 i += 2;
993 }
994 else
995 i++;
996 }
997 return isCrLf;
998 }
999
1000
1001 static const Byte kArcProps[] =
1002 {
1003 // kpidComment,
1004 kpidCharacts
1005 };
1006
1007 static const Byte kProps[] =
1008 {
1009 kpidPath,
1010 kpidSize,
1011 kpidPackSize,
1012 kpidMethod
1013 };
1014
1015 static const Byte kRawProps[] =
1016 {
1017 kpidChecksum
1018 };
1019
1020
Z7_COM7F_IMF(CHandler::GetParent (UInt32,UInt32 * parent,UInt32 * parentType))1021 Z7_COM7F_IMF(CHandler::GetParent(UInt32 /* index */ , UInt32 *parent, UInt32 *parentType))
1022 {
1023 *parentType = NParentType::kDir;
1024 *parent = (UInt32)(Int32)-1;
1025 return S_OK;
1026 }
1027
Z7_COM7F_IMF(CHandler::GetNumRawProps (UInt32 * numProps))1028 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
1029 {
1030 *numProps = Z7_ARRAY_SIZE(kRawProps);
1031 return S_OK;
1032 }
1033
Z7_COM7F_IMF(CHandler::GetRawPropInfo (UInt32 index,BSTR * name,PROPID * propID))1034 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID))
1035 {
1036 *propID = kRawProps[index];
1037 *name = NULL;
1038 return S_OK;
1039 }
1040
Z7_COM7F_IMF(CHandler::GetRawProp (UInt32 index,PROPID propID,const void ** data,UInt32 * dataSize,UInt32 * propType))1041 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1042 {
1043 *data = NULL;
1044 *dataSize = 0;
1045 *propType = 0;
1046
1047 if (propID == kpidChecksum)
1048 {
1049 const CHashPair &hp = HashPairs[index];
1050 if (hp.Hash.Size() > 0)
1051 {
1052 *data = hp.Hash;
1053 *dataSize = (UInt32)hp.Hash.Size();
1054 *propType = NPropDataType::kRaw;
1055 }
1056 return S_OK;
1057 }
1058
1059 return S_OK;
1060 }
1061
1062 IMP_IInArchive_Props
1063 IMP_IInArchive_ArcProps
1064
Z7_COM7F_IMF(CHandler::GetNumberOfItems (UInt32 * numItems))1065 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
1066 {
1067 *numItems = HashPairs.Size();
1068 return S_OK;
1069 }
1070
Add_OptSpace_String(UString & dest,const char * src)1071 static void Add_OptSpace_String(UString &dest, const char *src)
1072 {
1073 dest.Add_Space_if_NotEmpty();
1074 dest += src;
1075 }
1076
Z7_COM7F_IMF(CHandler::GetArchiveProperty (PROPID propID,PROPVARIANT * value))1077 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1078 {
1079 NCOM::CPropVariant prop;
1080 switch (propID)
1081 {
1082 case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
1083 /*
1084 case kpidErrorFlags:
1085 {
1086 UInt32 v = 0;
1087 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
1088 // if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
1089 if (v != 0)
1090 prop = v;
1091 break;
1092 }
1093 */
1094 case kpidCharacts:
1095 {
1096 UString s;
1097 if (_hashSize_Defined)
1098 {
1099 s.Add_Space_if_NotEmpty();
1100 s.Add_UInt32(_hashSize * 8);
1101 s += "-bit";
1102 }
1103 if (!_nameExtenstion.IsEmpty())
1104 {
1105 s.Add_Space_if_NotEmpty();
1106 s += _nameExtenstion;
1107 }
1108 if (_is_PgpMethod)
1109 {
1110 Add_OptSpace_String(s, "PGP");
1111 if (!_pgpMethod.IsEmpty())
1112 {
1113 s.Add_Colon();
1114 s += _pgpMethod;
1115 }
1116 }
1117 if (_is_ZeroMode)
1118 Add_OptSpace_String(s, "ZERO");
1119 if (_are_there_Tags)
1120 Add_OptSpace_String(s, "TAG");
1121 if (_are_there_Dirs)
1122 Add_OptSpace_String(s, "DIRS");
1123 prop = s;
1124 break;
1125 }
1126
1127 case kpidReadOnly:
1128 {
1129 if (_isArc)
1130 if (!CanUpdate())
1131 prop = true;
1132 break;
1133 }
1134 default: break;
1135 }
1136 prop.Detach(value);
1137 return S_OK;
1138 }
1139
1140
Z7_COM7F_IMF(CHandler::GetProperty (UInt32 index,PROPID propID,PROPVARIANT * value))1141 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1142 {
1143 // COM_TRY_BEGIN
1144 NCOM::CPropVariant prop;
1145 const CHashPair &hp = HashPairs[index];
1146 switch (propID)
1147 {
1148 case kpidIsDir:
1149 {
1150 prop = hp.IsDir();
1151 break;
1152 }
1153 case kpidPath:
1154 {
1155 UString path;
1156 hp.Get_UString_Path(path);
1157
1158 bool useBackslashReplacement = true;
1159 if (_supportWindowsBackslash && !hp.Escape && path.Find(L"\\\\") < 0)
1160 {
1161 #if WCHAR_PATH_SEPARATOR == L'/'
1162 path.Replace(L'\\', L'/');
1163 #else
1164 useBackslashReplacement = false;
1165 #endif
1166 }
1167 NArchive::NItemName::ReplaceToOsSlashes_Remove_TailSlash(
1168 path, useBackslashReplacement);
1169 prop = path;
1170 break;
1171 }
1172 case kpidSize:
1173 {
1174 // client needs processed size of last file
1175 if (hp.Size_from_Disk_Defined)
1176 prop = (UInt64)hp.Size_from_Disk;
1177 else if (hp.Size_from_Arc_Defined)
1178 prop = (UInt64)hp.Size_from_Arc;
1179 break;
1180 }
1181 case kpidPackSize:
1182 {
1183 prop = (UInt64)hp.Hash.Size();
1184 break;
1185 }
1186 case kpidMethod:
1187 {
1188 if (!hp.Method.IsEmpty())
1189 prop = hp.Method;
1190 break;
1191 }
1192 default: break;
1193 }
1194 prop.Detach(value);
1195 return S_OK;
1196 // COM_TRY_END
1197 }
1198
1199
ReadStream_to_Buf(IInStream * stream,CByteBuffer & buf,IArchiveOpenCallback * openCallback)1200 static HRESULT ReadStream_to_Buf(IInStream *stream, CByteBuffer &buf, IArchiveOpenCallback *openCallback)
1201 {
1202 buf.Free();
1203 UInt64 len;
1204 RINOK(InStream_AtBegin_GetSize(stream, len))
1205 if (len == 0 || len >= ((UInt64)1 << 31))
1206 return S_FALSE;
1207 buf.Alloc((size_t)len);
1208 UInt64 pos = 0;
1209 // return ReadStream_FALSE(stream, buf, (size_t)len);
1210 for (;;)
1211 {
1212 const UInt32 kBlockSize = ((UInt32)1 << 24);
1213 const UInt32 curSize = (len < kBlockSize) ? (UInt32)len : kBlockSize;
1214 UInt32 processedSizeLoc;
1215 RINOK(stream->Read((Byte *)buf + pos, curSize, &processedSizeLoc))
1216 if (processedSizeLoc == 0)
1217 return E_FAIL;
1218 len -= processedSizeLoc;
1219 pos += processedSizeLoc;
1220 if (len == 0)
1221 return S_OK;
1222 if (openCallback)
1223 {
1224 const UInt64 files = 0;
1225 RINOK(openCallback->SetCompleted(&files, &pos))
1226 }
1227 }
1228 }
1229
1230
Z7_COM7F_IMF(CHandler::Open (IInStream * stream,const UInt64 *,IArchiveOpenCallback * openCallback))1231 Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback))
1232 {
1233 COM_TRY_BEGIN
1234 {
1235 Close();
1236
1237 CByteBuffer buf;
1238 RINOK(ReadStream_to_Buf(stream, buf, openCallback))
1239
1240 CObjectVector<CHashPair> &pairs = HashPairs;
1241
1242 bool zeroMode = false;
1243 bool cr_lf_Mode = false;
1244 {
1245 for (size_t i = 0; i < buf.Size(); i++)
1246 if (buf.ConstData()[i] == 0)
1247 {
1248 zeroMode = true;
1249 break;
1250 }
1251 }
1252 _is_ZeroMode = zeroMode;
1253 if (!zeroMode)
1254 cr_lf_Mode = Is_CR_LF_Data(buf, buf.Size());
1255
1256 if (openCallback)
1257 {
1258 Z7_DECL_CMyComPtr_QI_FROM(
1259 IArchiveOpenVolumeCallback,
1260 openVolumeCallback, openCallback)
1261 if (openVolumeCallback)
1262 {
1263 NCOM::CPropVariant prop;
1264 RINOK(openVolumeCallback->GetProperty(kpidName, &prop))
1265 if (prop.vt == VT_BSTR)
1266 _nameExtenstion = GetMethod_from_FileName(prop.bstrVal);
1267 }
1268 }
1269
1270 bool cksumMode = false;
1271 if (_nameExtenstion.IsEqualTo_Ascii_NoCase("cksum"))
1272 cksumMode = true;
1273 _is_CksumMode = cksumMode;
1274
1275 size_t pos = 0;
1276 AString s;
1277 bool minusMode = false;
1278 unsigned numLines = 0;
1279
1280 while (pos < buf.Size())
1281 {
1282 if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s))
1283 return S_FALSE;
1284 numLines++;
1285 if (s.IsEmpty())
1286 continue;
1287
1288 if (s.IsPrefixedBy_Ascii_NoCase("; "))
1289 {
1290 if (numLines != 1)
1291 return S_FALSE;
1292 // comment line of FileVerifier++
1293 continue;
1294 }
1295
1296 if (s.IsPrefixedBy_Ascii_NoCase("-----"))
1297 {
1298 if (minusMode)
1299 break; // end of pgp mode
1300 minusMode = true;
1301 if (s.IsPrefixedBy_Ascii_NoCase("-----BEGIN PGP SIGNED MESSAGE"))
1302 {
1303 if (_is_PgpMethod)
1304 return S_FALSE;
1305 if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s))
1306 return S_FALSE;
1307 const char *kStart = "Hash: ";
1308 if (!s.IsPrefixedBy_Ascii_NoCase(kStart))
1309 return S_FALSE;
1310 _pgpMethod = s.Ptr((unsigned)strlen(kStart));
1311 _is_PgpMethod = true;
1312 }
1313 continue;
1314 }
1315
1316 CHashPair pair;
1317 pair.FullLine = s;
1318 if (cksumMode)
1319 {
1320 if (!pair.ParseCksum(s))
1321 return S_FALSE;
1322 }
1323 else if (!pair.Parse(s))
1324 return S_FALSE;
1325 pairs.Add(pair);
1326 }
1327
1328 {
1329 unsigned hashSize = 0;
1330 bool hashSize_Dismatch = false;
1331 for (unsigned i = 0; i < HashPairs.Size(); i++)
1332 {
1333 const CHashPair &hp = HashPairs[i];
1334 if (i == 0)
1335 hashSize = (unsigned)hp.Hash.Size();
1336 else
1337 if (hashSize != hp.Hash.Size())
1338 hashSize_Dismatch = true;
1339
1340 if (hp.IsBSD)
1341 _are_there_Tags = true;
1342 if (!_are_there_Dirs && hp.IsDir())
1343 _are_there_Dirs = true;
1344 }
1345 if (!hashSize_Dismatch && hashSize != 0)
1346 {
1347 _hashSize = hashSize;
1348 _hashSize_Defined = true;
1349 }
1350 }
1351
1352 _phySize = buf.Size();
1353 _isArc = true;
1354 return S_OK;
1355 }
1356 COM_TRY_END
1357 }
1358
1359
ClearVars()1360 void CHandler::ClearVars()
1361 {
1362 _phySize = 0;
1363 _isArc = false;
1364 _is_CksumMode = false;
1365 _is_PgpMethod = false;
1366 _is_ZeroMode = false;
1367 _are_there_Tags = false;
1368 _are_there_Dirs = false;
1369 _hashSize_Defined = false;
1370 _hashSize = 0;
1371 }
1372
1373
Z7_COM7F_IMF(CHandler::Close ())1374 Z7_COM7F_IMF(CHandler::Close())
1375 {
1376 ClearVars();
1377 _nameExtenstion.Empty();
1378 _pgpMethod.Empty();
1379 HashPairs.Clear();
1380 return S_OK;
1381 }
1382
1383
CheckDigests(const Byte * a,const Byte * b,size_t size)1384 static bool CheckDigests(const Byte *a, const Byte *b, size_t size)
1385 {
1386 if (size <= 8)
1387 {
1388 /* we use reversed order for one digest, when text representation
1389 uses big-order for crc-32 and crc-64 */
1390 for (size_t i = 0; i < size; i++)
1391 if (a[i] != b[size - 1 - i])
1392 return false;
1393 return true;
1394 }
1395 {
1396 for (size_t i = 0; i < size; i++)
1397 if (a[i] != b[i])
1398 return false;
1399 return true;
1400 }
1401 }
1402
1403
AddDefaultMethod(UStringVector & methods,unsigned size)1404 static void AddDefaultMethod(UStringVector &methods, unsigned size)
1405 {
1406 const char *m = NULL;
1407 if (size == 32) m = "sha256";
1408 else if (size == 20) m = "sha1";
1409 else if (size == 16) m = "md5";
1410 else if (size == 8) m = "crc64";
1411 else if (size == 4) m = "crc32";
1412 else
1413 return;
1414 #ifdef Z7_EXTERNAL_CODECS
1415 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1416 #endif
1417 CMethodId id;
1418 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS
1419 AString(m), id))
1420 methods.Add(UString(m));
1421 }
1422
1423
Z7_COM7F_IMF(CHandler::Extract (const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback))1424 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1425 Int32 testMode, IArchiveExtractCallback *extractCallback))
1426 {
1427 COM_TRY_BEGIN
1428
1429 /*
1430 if (testMode == 0)
1431 return E_NOTIMPL;
1432 */
1433
1434 const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1435 if (allFilesMode)
1436 numItems = HashPairs.Size();
1437 if (numItems == 0)
1438 return S_OK;
1439
1440 #ifdef Z7_EXTERNAL_CODECS
1441 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1442 #endif
1443
1444 CHashBundle hb_Glob;
1445 // UStringVector methods = options.Methods;
1446 UStringVector methods;
1447
1448 if (methods.IsEmpty() && !_nameExtenstion.IsEmpty())
1449 {
1450 AString utf;
1451 ConvertUnicodeToUTF8(_nameExtenstion, utf);
1452 CMethodId id;
1453 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS utf, id))
1454 methods.Add(_nameExtenstion);
1455 }
1456
1457 if (methods.IsEmpty() && !_pgpMethod.IsEmpty())
1458 {
1459 CMethodId id;
1460 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS _pgpMethod, id))
1461 methods.Add(UString(_pgpMethod));
1462 }
1463
1464 if (methods.IsEmpty() && _pgpMethod.IsEmpty() && _hashSize_Defined)
1465 AddDefaultMethod(methods, _hashSize);
1466
1467 RINOK(hb_Glob.SetMethods(
1468 EXTERNAL_CODECS_LOC_VARS
1469 methods))
1470
1471 Z7_DECL_CMyComPtr_QI_FROM(
1472 IArchiveUpdateCallbackFile,
1473 updateCallbackFile, extractCallback)
1474 if (!updateCallbackFile)
1475 return E_NOTIMPL;
1476 {
1477 Z7_DECL_CMyComPtr_QI_FROM(
1478 IArchiveGetDiskProperty,
1479 GetDiskProperty, extractCallback)
1480 if (GetDiskProperty)
1481 {
1482 UInt64 totalSize = 0;
1483 UInt32 i;
1484 for (i = 0; i < numItems; i++)
1485 {
1486 const UInt32 index = allFilesMode ? i : indices[i];
1487 const CHashPair &hp = HashPairs[index];
1488 if (hp.IsDir())
1489 continue;
1490 {
1491 NCOM::CPropVariant prop;
1492 RINOK(GetDiskProperty->GetDiskProperty(index, kpidSize, &prop))
1493 if (prop.vt != VT_UI8)
1494 continue;
1495 totalSize += prop.uhVal.QuadPart;
1496 }
1497 }
1498 RINOK(extractCallback->SetTotal(totalSize))
1499 // RINOK(Hash_SetTotalUnpacked->Hash_SetTotalUnpacked(indices, numItems));
1500 }
1501 }
1502
1503 const UInt32 kBufSize = 1 << 15;
1504 CHashMidBuf buf;
1505 if (!buf.Alloc(kBufSize))
1506 return E_OUTOFMEMORY;
1507
1508 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
1509 lps->Init(extractCallback, false);
1510
1511 for (UInt32 i = 0;; i++)
1512 {
1513 RINOK(lps->SetCur())
1514 if (i >= numItems)
1515 break;
1516 const UInt32 index = allFilesMode ? i : indices[i];
1517
1518 CHashPair &hp = HashPairs[index];
1519
1520 UString path;
1521 hp.Get_UString_Path(path);
1522
1523 CMyComPtr<ISequentialInStream> inStream;
1524 const bool isDir = hp.IsDir();
1525 if (!isDir)
1526 {
1527 RINOK(updateCallbackFile->GetStream2(index, &inStream, NUpdateNotifyOp::kHashRead))
1528 if (!inStream)
1529 {
1530 continue; // we have shown error in GetStream2()
1531 }
1532 // askMode = NArchive::NExtract::NAskMode::kSkip;
1533 }
1534
1535 Int32 askMode = testMode ?
1536 NArchive::NExtract::NAskMode::kTest :
1537 NArchive::NExtract::NAskMode::kExtract;
1538
1539 CMyComPtr<ISequentialOutStream> realOutStream;
1540 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
1541
1542 /* PrepareOperation() can expect kExtract to set
1543 Attrib and security of output file */
1544 askMode = NArchive::NExtract::NAskMode::kReadExternal;
1545
1546 RINOK(extractCallback->PrepareOperation(askMode))
1547
1548 const bool isAltStream = false;
1549
1550 UInt64 fileSize = 0;
1551
1552 CHashBundle hb_Loc;
1553
1554 CHashBundle *hb_Use = &hb_Glob;
1555
1556 HRESULT res_SetMethods = S_OK;
1557
1558 UStringVector methods_loc;
1559
1560 if (!hp.Method.IsEmpty())
1561 {
1562 hb_Use = &hb_Loc;
1563 CMethodId id;
1564 if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS hp.Method, id))
1565 {
1566 methods_loc.Add(UString(hp.Method));
1567 RINOK(hb_Loc.SetMethods(
1568 EXTERNAL_CODECS_LOC_VARS
1569 methods_loc))
1570 }
1571 else
1572 res_SetMethods = E_NOTIMPL;
1573 }
1574 else if (methods.IsEmpty())
1575 {
1576 AddDefaultMethod(methods_loc, (unsigned)hp.Hash.Size());
1577 if (!methods_loc.IsEmpty())
1578 {
1579 hb_Use = &hb_Loc;
1580 RINOK(hb_Loc.SetMethods(
1581 EXTERNAL_CODECS_LOC_VARS
1582 methods_loc))
1583 }
1584 }
1585
1586 const bool isSupportedMode = hp.IsSupportedMode();
1587 hb_Use->InitForNewFile();
1588
1589 if (inStream)
1590 {
1591 for (UInt32 step = 0;; step++)
1592 {
1593 if ((step & 0xFF) == 0)
1594 {
1595 RINOK(lps.Interface()->SetRatioInfo(NULL, &fileSize))
1596 }
1597 UInt32 size;
1598 RINOK(inStream->Read(buf, kBufSize, &size))
1599 if (size == 0)
1600 break;
1601 hb_Use->Update(buf, size);
1602 if (realOutStream)
1603 {
1604 RINOK(WriteStream(realOutStream, buf, size))
1605 }
1606 fileSize += size;
1607 }
1608
1609 hp.Size_from_Disk = fileSize;
1610 hp.Size_from_Disk_Defined = true;
1611 }
1612
1613 realOutStream.Release();
1614 inStream.Release();
1615
1616 lps->InSize += hp.Hash.Size();
1617 lps->OutSize += fileSize;
1618
1619 hb_Use->Final(isDir, isAltStream, path);
1620
1621 Int32 opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod;
1622 if (isSupportedMode
1623 && res_SetMethods != E_NOTIMPL
1624 && hb_Use->Hashers.Size() > 0
1625 )
1626 {
1627 const CHasherState &hs = hb_Use->Hashers[0];
1628 if (hs.DigestSize == hp.Hash.Size())
1629 {
1630 opRes = NArchive::NExtract::NOperationResult::kCRCError;
1631 if (CheckDigests(hp.Hash, hs.Digests[0], hs.DigestSize))
1632 if (!hp.Size_from_Arc_Defined || hp.Size_from_Arc == fileSize)
1633 opRes = NArchive::NExtract::NOperationResult::kOK;
1634 }
1635 }
1636
1637 RINOK(extractCallback->SetOperationResult(opRes))
1638 }
1639
1640 return S_OK;
1641 COM_TRY_END
1642 }
1643
1644
1645 // ---------- UPDATE ----------
1646
1647 struct CUpdateItem
1648 {
1649 int IndexInArc;
1650 unsigned IndexInClient;
1651 UInt64 Size;
1652 bool NewData;
1653 bool NewProps;
1654 bool IsDir;
1655 UString Path;
1656
CUpdateItemNHash::CUpdateItem1657 CUpdateItem(): Size(0), IsDir(false) {}
1658 };
1659
1660
GetPropString(IArchiveUpdateCallback * callback,UInt32 index,PROPID propId,UString & res,bool convertSlash)1661 static HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId,
1662 UString &res,
1663 bool convertSlash)
1664 {
1665 NCOM::CPropVariant prop;
1666 RINOK(callback->GetProperty(index, propId, &prop))
1667 if (prop.vt == VT_BSTR)
1668 {
1669 res = prop.bstrVal;
1670 if (convertSlash)
1671 NArchive::NItemName::ReplaceSlashes_OsToUnix(res);
1672 }
1673 else if (prop.vt != VT_EMPTY)
1674 return E_INVALIDARG;
1675 return S_OK;
1676 }
1677
1678
Z7_COM7F_IMF(CHandler::GetFileTimeType (UInt32 * type))1679 Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type))
1680 {
1681 *type = NFileTimeType::kUnix;
1682 return S_OK;
1683 }
1684
1685
Z7_COM7F_IMF(CHandler::UpdateItems (ISequentialOutStream * outStream,UInt32 numItems,IArchiveUpdateCallback * callback))1686 Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
1687 IArchiveUpdateCallback *callback))
1688 {
1689 COM_TRY_BEGIN
1690
1691 if (_isArc && !CanUpdate())
1692 return E_NOTIMPL;
1693
1694 /*
1695 Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp,
1696 reportArcProp, callback)
1697 */
1698
1699 CObjectVector<CUpdateItem> updateItems;
1700
1701 UInt64 complexity = 0;
1702
1703 UInt32 i;
1704 for (i = 0; i < numItems; i++)
1705 {
1706 CUpdateItem ui;
1707 Int32 newData;
1708 Int32 newProps;
1709 UInt32 indexInArc;
1710
1711 if (!callback)
1712 return E_FAIL;
1713
1714 RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc))
1715
1716 ui.NewProps = IntToBool(newProps);
1717 ui.NewData = IntToBool(newData);
1718 ui.IndexInArc = (int)indexInArc;
1719 ui.IndexInClient = i;
1720 if (IntToBool(newProps))
1721 {
1722 {
1723 NCOM::CPropVariant prop;
1724 RINOK(callback->GetProperty(i, kpidIsDir, &prop))
1725 if (prop.vt == VT_EMPTY)
1726 ui.IsDir = false;
1727 else if (prop.vt != VT_BOOL)
1728 return E_INVALIDARG;
1729 else
1730 ui.IsDir = (prop.boolVal != VARIANT_FALSE);
1731 }
1732
1733 RINOK(GetPropString(callback, i, kpidPath, ui.Path,
1734 true)) // convertSlash
1735 /*
1736 if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/')
1737 ui.Name += '/';
1738 */
1739 }
1740
1741 if (IntToBool(newData))
1742 {
1743 NCOM::CPropVariant prop;
1744 RINOK(callback->GetProperty(i, kpidSize, &prop))
1745 if (prop.vt == VT_UI8)
1746 {
1747 ui.Size = prop.uhVal.QuadPart;
1748 complexity += ui.Size;
1749 }
1750 else if (prop.vt == VT_EMPTY)
1751 ui.Size = (UInt64)(Int64)-1;
1752 else
1753 return E_INVALIDARG;
1754 }
1755
1756 updateItems.Add(ui);
1757 }
1758
1759 if (complexity != 0)
1760 {
1761 RINOK(callback->SetTotal(complexity))
1762 }
1763
1764 #ifdef Z7_EXTERNAL_CODECS
1765 const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1766 #endif
1767
1768 CHashBundle hb;
1769 UStringVector methods;
1770 if (!_methods.IsEmpty())
1771 {
1772 FOR_VECTOR(k, _methods)
1773 {
1774 methods.Add(_methods[k]);
1775 }
1776 }
1777 else if (_crcSize_WasSet)
1778 {
1779 AddDefaultMethod(methods, _crcSize);
1780 }
1781 else
1782 {
1783 Z7_DECL_CMyComPtr_QI_FROM(
1784 IArchiveGetRootProps,
1785 getRootProps, callback)
1786 if (getRootProps)
1787 {
1788 NCOM::CPropVariant prop;
1789 RINOK(getRootProps->GetRootProp(kpidArcFileName, &prop))
1790 if (prop.vt == VT_BSTR)
1791 {
1792 const UString method = GetMethod_from_FileName(prop.bstrVal);
1793 if (!method.IsEmpty())
1794 methods.Add(method);
1795 }
1796 }
1797 }
1798
1799 RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS methods))
1800
1801 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
1802 lps->Init(callback, true);
1803
1804 const UInt32 kBufSize = 1 << 15;
1805 CHashMidBuf buf;
1806 if (!buf.Alloc(kBufSize))
1807 return E_OUTOFMEMORY;
1808
1809 CDynLimBuf hashFileString((size_t)1 << 31);
1810
1811 CHashOptionsLocal options = _options;
1812
1813 if (_isArc)
1814 {
1815 if (!options.HashMode_Zero.Def && _is_ZeroMode)
1816 options.HashMode_Zero.Val = true;
1817 if (!options.HashMode_Tag.Def && _are_there_Tags)
1818 options.HashMode_Tag.Val = true;
1819 if (!options.HashMode_Dirs.Def && _are_there_Dirs)
1820 options.HashMode_Dirs.Val = true;
1821 }
1822 if (options.HashMode_OnlyHash.Val && updateItems.Size() != 1)
1823 options.HashMode_OnlyHash.Val = false;
1824
1825 complexity = 0;
1826
1827 for (i = 0; i < updateItems.Size(); i++)
1828 {
1829 lps->InSize = complexity;
1830 RINOK(lps->SetCur())
1831
1832 const CUpdateItem &ui = updateItems[i];
1833
1834 /*
1835 CHashPair item;
1836 if (!ui.NewProps)
1837 item = HashPairs[(unsigned)ui.IndexInArc];
1838 */
1839
1840 if (ui.NewData)
1841 {
1842 UInt64 currentComplexity = ui.Size;
1843 UInt64 fileSize = 0;
1844
1845 CMyComPtr<ISequentialInStream> fileInStream;
1846 bool needWrite = true;
1847 {
1848 HRESULT res = callback->GetStream(ui.IndexInClient, &fileInStream);
1849
1850 if (res == S_FALSE)
1851 needWrite = false;
1852 else
1853 {
1854 RINOK(res)
1855
1856 if (fileInStream)
1857 {
1858 Z7_DECL_CMyComPtr_QI_FROM(
1859 IStreamGetSize,
1860 streamGetSize, fileInStream)
1861 if (streamGetSize)
1862 {
1863 UInt64 size;
1864 if (streamGetSize->GetSize(&size) == S_OK)
1865 currentComplexity = size;
1866 }
1867 /*
1868 Z7_DECL_CMyComPtr_QI_FROM(
1869 IStreamGetProps,
1870 getProps, fileInStream)
1871 if (getProps)
1872 {
1873 FILETIME mTime;
1874 UInt64 size2;
1875 if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK)
1876 {
1877 currentComplexity = size2;
1878 // item.MTime = NTime::FileTimeToUnixTime64(mTime);;
1879 }
1880 }
1881 */
1882 }
1883 else
1884 {
1885 currentComplexity = 0;
1886 }
1887 }
1888 }
1889
1890 hb.InitForNewFile();
1891 const bool isDir = ui.IsDir;
1892
1893 if (needWrite && fileInStream && !isDir)
1894 {
1895 for (UInt32 step = 0;; step++)
1896 {
1897 if ((step & 0xFF) == 0)
1898 {
1899 RINOK(lps.Interface()->SetRatioInfo(&fileSize, NULL))
1900 // RINOK(callback->SetCompleted(&completeValue));
1901 }
1902 UInt32 size;
1903 RINOK(fileInStream->Read(buf, kBufSize, &size))
1904 if (size == 0)
1905 break;
1906 hb.Update(buf, size);
1907 fileSize += size;
1908 }
1909 currentComplexity = fileSize;
1910 }
1911
1912 fileInStream.Release();
1913 const bool isAltStream = false;
1914 hb.Final(isDir, isAltStream, ui.Path);
1915
1916 if (options.HashMode_Dirs.Val || !isDir)
1917 {
1918 if (!hb.Hashers.IsEmpty())
1919 lps->OutSize += hb.Hashers[0].DigestSize;
1920 WriteLine(hashFileString,
1921 options,
1922 ui.Path,
1923 isDir,
1924 hb);
1925 if (hashFileString.IsError())
1926 return E_OUTOFMEMORY;
1927 }
1928
1929 complexity += currentComplexity;
1930
1931 /*
1932 if (reportArcProp)
1933 {
1934 PROPVARIANT prop;
1935 prop.vt = VT_EMPTY;
1936 prop.wReserved1 = 0;
1937
1938 NCOM::PropVarEm_Set_UInt64(&prop, fileSize);
1939 RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidSize, &prop));
1940
1941 for (unsigned k = 0; k < hb.Hashers.Size(); k++)
1942 {
1943 const CHasherState &hs = hb.Hashers[k];
1944
1945 if (hs.DigestSize == 4 && hs.Name.IsEqualTo_Ascii_NoCase("crc32"))
1946 {
1947 NCOM::PropVarEm_Set_UInt32(&prop, GetUi32(hs.Digests[k_HashCalc_Index_Current]));
1948 RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidCRC, &prop));
1949 }
1950 else
1951 {
1952 RINOK(reportArcProp->ReportRawProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient,
1953 kpidChecksum, hs.Digests[k_HashCalc_Index_Current],
1954 hs.DigestSize, NPropDataType::kRaw));
1955 }
1956 RINOK(reportArcProp->ReportFinished(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, NArchive::NUpdate::NOperationResult::kOK));
1957 }
1958 }
1959 */
1960 RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK))
1961 }
1962 else
1963 {
1964 // old data
1965 const CHashPair &existItem = HashPairs[(unsigned)ui.IndexInArc];
1966 if (ui.NewProps)
1967 {
1968 WriteLine(hashFileString,
1969 options,
1970 ui.Path,
1971 ui.IsDir,
1972 existItem.Method, existItem.HashString
1973 );
1974 }
1975 else
1976 {
1977 hashFileString += existItem.FullLine;
1978 Add_LF(hashFileString, options);
1979 }
1980 }
1981 if (hashFileString.IsError())
1982 return E_OUTOFMEMORY;
1983 }
1984
1985 RINOK(WriteStream(outStream, hashFileString, hashFileString.Len()))
1986
1987 return S_OK;
1988 COM_TRY_END
1989 }
1990
1991
1992
SetProperty(const wchar_t * nameSpec,const PROPVARIANT & value)1993 HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value)
1994 {
1995 UString name = nameSpec;
1996 name.MakeLower_Ascii();
1997 if (name.IsEmpty())
1998 return E_INVALIDARG;
1999
2000 if (name.IsEqualTo("m")) // "hm" hash method
2001 {
2002 // COneMethodInfo omi;
2003 // RINOK(omi.ParseMethodFromPROPVARIANT(L"", value));
2004 // _methods.Add(omi.MethodName); // change it. use omi.PropsString
2005 if (value.vt != VT_BSTR)
2006 return E_INVALIDARG;
2007 UString s (value.bstrVal);
2008 _methods.Add(s);
2009 return S_OK;
2010 }
2011
2012 if (name.IsEqualTo("flags"))
2013 {
2014 if (value.vt != VT_BSTR)
2015 return E_INVALIDARG;
2016 if (!_options.ParseString(value.bstrVal))
2017 return E_INVALIDARG;
2018 return S_OK;
2019 }
2020
2021 if (name.IsEqualTo("backslash"))
2022 return PROPVARIANT_to_bool(value, _supportWindowsBackslash);
2023
2024 if (name.IsPrefixedBy_Ascii_NoCase("crc"))
2025 {
2026 name.Delete(0, 3);
2027 _crcSize = 4;
2028 _crcSize_WasSet = true;
2029 return ParsePropToUInt32(name, value, _crcSize);
2030 }
2031
2032 // common properties
2033 if (name.IsPrefixedBy_Ascii_NoCase("mt")
2034 || name.IsPrefixedBy_Ascii_NoCase("memuse"))
2035 return S_OK;
2036
2037 return E_INVALIDARG;
2038 }
2039
2040
Z7_COM7F_IMF(CHandler::SetProperties (const wchar_t * const * names,const PROPVARIANT * values,UInt32 numProps))2041 Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps))
2042 {
2043 COM_TRY_BEGIN
2044
2045 InitProps();
2046
2047 for (UInt32 i = 0; i < numProps; i++)
2048 {
2049 RINOK(SetProperty(names[i], values[i]))
2050 }
2051 return S_OK;
2052 COM_TRY_END
2053 }
2054
CHandler()2055 CHandler::CHandler()
2056 {
2057 ClearVars();
2058 InitProps();
2059 }
2060
2061 }
2062
2063
2064
CreateHashHandler_In()2065 static IInArchive *CreateHashHandler_In() { return new NHash::CHandler; }
CreateHashHandler_Out()2066 static IOutArchive *CreateHashHandler_Out() { return new NHash::CHandler; }
2067
Codecs_AddHashArcHandler(CCodecs * codecs)2068 void Codecs_AddHashArcHandler(CCodecs *codecs)
2069 {
2070 {
2071 CArcInfoEx item;
2072
2073 item.Name = "Hash";
2074 item.CreateInArchive = CreateHashHandler_In;
2075 item.CreateOutArchive = CreateHashHandler_Out;
2076 item.IsArcFunc = NULL;
2077 item.Flags =
2078 NArcInfoFlags::kKeepName
2079 | NArcInfoFlags::kStartOpen
2080 | NArcInfoFlags::kByExtOnlyOpen
2081 // | NArcInfoFlags::kPureStartOpen
2082 | NArcInfoFlags::kHashHandler
2083 ;
2084
2085 // ubuntu uses "SHA256SUMS" file
2086 item.AddExts(UString (
2087 "sha256"
2088 " sha512"
2089 " sha384"
2090 " sha224"
2091 // " sha512-224"
2092 // " sha512-256"
2093 // " sha3-224"
2094 " sha3-256"
2095 // " sha3-384"
2096 // " sha3-512"
2097 // " shake128"
2098 // " shake256"
2099 " sha1"
2100 " sha"
2101 " md5"
2102 " blake2sp"
2103 " xxh64"
2104 " crc32 crc64"
2105 " asc"
2106 " cksum"
2107 // " b2sum"
2108 ),
2109 UString());
2110
2111 item.UpdateEnabled = (item.CreateOutArchive != NULL);
2112 item.SignatureOffset = 0;
2113 // item.Version = MY_VER_MIX;
2114 item.NewInterface = true;
2115
2116 item.Signatures.AddNew().CopyFrom(NULL, 0);
2117
2118 codecs->Formats.Add(item);
2119 }
2120 }
2121