xref: /aosp_15_r20/external/lzma/CPP/7zip/UI/Common/HashCalc.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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