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