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