• 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 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 += ' ';
334     _s += s;
335   }
336 
337   /*
338   if (showSize)
339   {
340     _s += ' ';
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 < 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 != 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 
GetParent(UInt32,UInt32 * parent,UInt32 * parentType)1036 STDMETHODIMP CHandler::GetParent(UInt32 /* index */ , UInt32 *parent, UInt32 *parentType)
1037 {
1038   *parentType = NParentType::kDir;
1039   *parent = (UInt32)(Int32)-1;
1040   return S_OK;
1041 }
1042 
GetNumRawProps(UInt32 * numProps)1043 STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps)
1044 {
1045   *numProps = ARRAY_SIZE(kRawProps);
1046   return S_OK;
1047 }
1048 
GetRawPropInfo(UInt32 index,BSTR * name,PROPID * propID)1049 STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)
1050 {
1051   *propID = kRawProps[index];
1052   *name = 0;
1053   return S_OK;
1054 }
1055 
GetRawProp(UInt32 index,PROPID propID,const void ** data,UInt32 * dataSize,UInt32 * propType)1056 STDMETHODIMP 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 
GetNumberOfItems(UInt32 * numItems)1080 STDMETHODIMP 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 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)1092 STDMETHODIMP 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 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)1155 STDMETHODIMP 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(stream->Seek(0, STREAM_SEEK_END, &len));
1210   if (len == 0 || len >= ((UInt64)1 << 31))
1211     return S_FALSE;
1212   RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
1213   buf.Alloc((size_t)len);
1214   UInt64 pos = 0;
1215   // return ReadStream_FALSE(stream, buf, (size_t)len);
1216   for (;;)
1217   {
1218     const UInt32 kBlockSize = ((UInt32)1 << 24);
1219     const UInt32 curSize = (len < kBlockSize) ? (UInt32)len : kBlockSize;
1220     UInt32 processedSizeLoc;
1221     RINOK(stream->Read((Byte *)buf + pos, curSize, &processedSizeLoc));
1222     if (processedSizeLoc == 0)
1223       return E_FAIL;
1224     len -= processedSizeLoc;
1225     pos += processedSizeLoc;
1226     if (len == 0)
1227       return S_OK;
1228     if (openCallback)
1229     {
1230       const UInt64 files = 0;
1231       RINOK(openCallback->SetCompleted(&files, &pos));
1232     }
1233   }
1234 }
1235 
1236 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback * openCallback)1237 STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback)
1238 {
1239   COM_TRY_BEGIN
1240   {
1241     Close();
1242 
1243     CByteBuffer buf;
1244     RINOK(ReadStream_to_Buf(stream, buf, openCallback))
1245 
1246     CObjectVector<CHashPair> &pairs = HashPairs;
1247 
1248     bool zeroMode = false;
1249     bool cr_lf_Mode = false;
1250     {
1251       for (size_t i = 0; i < buf.Size(); i++)
1252         if (buf[i] == 0)
1253         {
1254           zeroMode = true;
1255           break;
1256         }
1257     }
1258     _is_ZeroMode = zeroMode;
1259     if (!zeroMode)
1260       cr_lf_Mode = Is_CR_LF_Data(buf, buf.Size());
1261 
1262     if (openCallback)
1263     {
1264       CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
1265       openCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
1266       NCOM::CPropVariant prop;
1267       if (openVolumeCallback)
1268       {
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 
Close()1379 STDMETHODIMP 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 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 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)1429 STDMETHODIMP 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 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   CMyComPtr<IArchiveUpdateCallbackFile> updateCallbackFile;
1477   extractCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&updateCallbackFile);
1478   if (!updateCallbackFile)
1479     return E_NOTIMPL;
1480   {
1481     CMyComPtr<IArchiveGetDiskProperty> GetDiskProperty;
1482     extractCallback->QueryInterface(IID_IArchiveGetDiskProperty, (void **)&GetDiskProperty);
1483     if (GetDiskProperty)
1484     {
1485       UInt64 totalSize = 0;
1486       UInt32 i;
1487       for (i = 0; i < numItems; i++)
1488       {
1489         const UInt32 index = allFilesMode ? i : indices[i];
1490         const CHashPair &hp = HashPairs[index];
1491         if (hp.IsDir())
1492           continue;
1493         {
1494           NCOM::CPropVariant prop;
1495           RINOK(GetDiskProperty->GetDiskProperty(index, kpidSize, &prop));
1496           if (prop.vt != VT_UI8)
1497             continue;
1498           totalSize += prop.uhVal.QuadPart;
1499         }
1500       }
1501       RINOK(extractCallback->SetTotal(totalSize));
1502       // RINOK(Hash_SetTotalUnpacked->Hash_SetTotalUnpacked(indices, numItems));
1503     }
1504   }
1505 
1506   const UInt32 kBufSize = 1 << 15;
1507   CHashMidBuf buf;
1508   if (!buf.Alloc(kBufSize))
1509     return E_OUTOFMEMORY;
1510 
1511   CLocalProgress *lps = new CLocalProgress;
1512   CMyComPtr<ICompressProgressInfo> progress = lps;
1513   lps->Init(extractCallback, false);
1514   lps->InSize = lps->OutSize = 0;
1515 
1516   UInt32 i;
1517   for (i = 0; i < numItems; i++)
1518   {
1519     RINOK(lps->SetCur());
1520     const UInt32 index = allFilesMode ? i : indices[i];
1521 
1522     CHashPair &hp = HashPairs[index];
1523 
1524     UString path;
1525     hp.Get_UString_Path(path);
1526 
1527     CMyComPtr<ISequentialInStream> inStream;
1528     const bool isDir = hp.IsDir();
1529     if (!isDir)
1530     {
1531       RINOK(updateCallbackFile->GetStream2(index, &inStream, NUpdateNotifyOp::kHashRead));
1532       if (!inStream)
1533       {
1534         continue; // we have shown error in GetStream2()
1535       }
1536       // askMode = NArchive::NExtract::NAskMode::kSkip;
1537     }
1538 
1539     Int32 askMode = testMode ?
1540         NArchive::NExtract::NAskMode::kTest :
1541         NArchive::NExtract::NAskMode::kExtract;
1542 
1543     CMyComPtr<ISequentialOutStream> realOutStream;
1544     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
1545 
1546     /* PrepareOperation() can expect kExtract to set
1547        Attrib and security of output file */
1548     askMode = NArchive::NExtract::NAskMode::kReadExternal;
1549 
1550     extractCallback->PrepareOperation(askMode);
1551 
1552     const bool isAltStream = false;
1553 
1554     UInt64 fileSize = 0;
1555 
1556     CHashBundle hb_Loc;
1557 
1558     CHashBundle *hb_Use = &hb_Glob;
1559 
1560     HRESULT res_SetMethods = S_OK;
1561 
1562     UStringVector methods_loc;
1563 
1564     if (!hp.Method.IsEmpty())
1565     {
1566       hb_Use = &hb_Loc;
1567       CMethodId id;
1568       if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS hp.Method, id))
1569       {
1570         methods_loc.Add(UString(hp.Method));
1571         RINOK(hb_Loc.SetMethods(
1572             EXTERNAL_CODECS_LOC_VARS
1573             methods_loc));
1574       }
1575       else
1576         res_SetMethods = E_NOTIMPL;
1577     }
1578     else if (methods.IsEmpty())
1579     {
1580       AddDefaultMethod(methods_loc, (unsigned)hp.Hash.Size());
1581       if (!methods_loc.IsEmpty())
1582       {
1583         hb_Use = &hb_Loc;
1584         RINOK(hb_Loc.SetMethods(
1585             EXTERNAL_CODECS_LOC_VARS
1586             methods_loc));
1587       }
1588     }
1589 
1590     const bool isSupportedMode = hp.IsSupportedMode();
1591     hb_Use->InitForNewFile();
1592 
1593     if (inStream)
1594     {
1595       for (UInt32 step = 0;; step++)
1596       {
1597         if ((step & 0xFF) == 0)
1598         {
1599           RINOK(lps->SetRatioInfo(NULL, &fileSize));
1600         }
1601         UInt32 size;
1602         RINOK(inStream->Read(buf, kBufSize, &size));
1603         if (size == 0)
1604           break;
1605         hb_Use->Update(buf, size);
1606         if (realOutStream)
1607         {
1608           RINOK(WriteStream(realOutStream, buf, size));
1609         }
1610         fileSize += size;
1611       }
1612 
1613       hp.Size_from_Disk = fileSize;
1614       hp.Size_from_Disk_Defined = true;
1615     }
1616 
1617     realOutStream.Release();
1618     inStream.Release();
1619 
1620     lps->InSize += hp.Hash.Size();
1621     lps->OutSize += fileSize;
1622 
1623     hb_Use->Final(isDir, isAltStream, path);
1624 
1625     Int32 opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod;
1626     if (isSupportedMode
1627         && res_SetMethods != E_NOTIMPL
1628         && hb_Use->Hashers.Size() > 0
1629         )
1630     {
1631       const CHasherState &hs = hb_Use->Hashers[0];
1632       if (hs.DigestSize == hp.Hash.Size())
1633       {
1634         opRes = NArchive::NExtract::NOperationResult::kCRCError;
1635         if (CheckDigests(hp.Hash, hs.Digests[0], hs.DigestSize))
1636           if (!hp.Size_from_Arc_Defined || hp.Size_from_Arc == fileSize)
1637             opRes = NArchive::NExtract::NOperationResult::kOK;
1638       }
1639     }
1640 
1641     RINOK(extractCallback->SetOperationResult(opRes));
1642   }
1643 
1644   return lps->SetCur();
1645 
1646   COM_TRY_END
1647 }
1648 
1649 
1650 // ---------- UPDATE ----------
1651 
1652 struct CUpdateItem
1653 {
1654   int IndexInArc;
1655   unsigned IndexInClient;
1656   UInt64 Size;
1657   bool NewData;
1658   bool NewProps;
1659   bool IsDir;
1660   UString Path;
1661 
CUpdateItemNHash::CUpdateItem1662   CUpdateItem(): Size(0), IsDir(false) {}
1663 };
1664 
1665 
GetPropString(IArchiveUpdateCallback * callback,UInt32 index,PROPID propId,UString & res,bool convertSlash)1666 static HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId,
1667     UString &res,
1668     bool convertSlash)
1669 {
1670   NCOM::CPropVariant prop;
1671   RINOK(callback->GetProperty(index, propId, &prop));
1672   if (prop.vt == VT_BSTR)
1673   {
1674     res = prop.bstrVal;
1675     if (convertSlash)
1676       NArchive::NItemName::ReplaceSlashes_OsToUnix(res);
1677   }
1678   else if (prop.vt != VT_EMPTY)
1679     return E_INVALIDARG;
1680   return S_OK;
1681 }
1682 
1683 
GetFileTimeType(UInt32 * type)1684 STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type)
1685 {
1686   *type = NFileTimeType::kUnix;
1687   return S_OK;
1688 }
1689 
1690 
UpdateItems(ISequentialOutStream * outStream,UInt32 numItems,IArchiveUpdateCallback * callback)1691 STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
1692     IArchiveUpdateCallback *callback)
1693 {
1694   COM_TRY_BEGIN
1695 
1696   if (_isArc && !CanUpdate())
1697     return E_NOTIMPL;
1698 
1699   /*
1700   CMyComPtr<IArchiveUpdateCallbackArcProp> reportArcProp;
1701   callback->QueryInterface(IID_IArchiveUpdateCallbackArcProp, (void **)&reportArcProp);
1702   */
1703 
1704   CObjectVector<CUpdateItem> updateItems;
1705 
1706   UInt64 complexity = 0;
1707 
1708   UInt32 i;
1709   for (i = 0; i < numItems; i++)
1710   {
1711     CUpdateItem ui;
1712     Int32 newData;
1713     Int32 newProps;
1714     UInt32 indexInArc;
1715 
1716     if (!callback)
1717       return E_FAIL;
1718 
1719     RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc));
1720 
1721     ui.NewProps = IntToBool(newProps);
1722     ui.NewData = IntToBool(newData);
1723     ui.IndexInArc = (int)indexInArc;
1724     ui.IndexInClient = i;
1725     if (IntToBool(newProps))
1726     {
1727       {
1728         NCOM::CPropVariant prop;
1729         RINOK(callback->GetProperty(i, kpidIsDir, &prop));
1730         if (prop.vt == VT_EMPTY)
1731           ui.IsDir = false;
1732         else if (prop.vt != VT_BOOL)
1733           return E_INVALIDARG;
1734         else
1735           ui.IsDir = (prop.boolVal != VARIANT_FALSE);
1736       }
1737 
1738       RINOK(GetPropString(callback, i, kpidPath, ui.Path,
1739           true)); // convertSlash
1740       /*
1741       if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/')
1742         ui.Name += '/';
1743       */
1744     }
1745 
1746     if (IntToBool(newData))
1747     {
1748       NCOM::CPropVariant prop;
1749       RINOK(callback->GetProperty(i, kpidSize, &prop));
1750       if (prop.vt == VT_UI8)
1751       {
1752         ui.Size = prop.uhVal.QuadPart;
1753         complexity += ui.Size;
1754       }
1755       else if (prop.vt == VT_EMPTY)
1756         ui.Size = (UInt64)(Int64)-1;
1757       else
1758         return E_INVALIDARG;
1759     }
1760 
1761     updateItems.Add(ui);
1762   }
1763 
1764   if (complexity != 0)
1765   {
1766     RINOK(callback->SetTotal(complexity));
1767   }
1768 
1769   #ifdef EXTERNAL_CODECS
1770   const CExternalCodecs *__externalCodecs = g_ExternalCodecs_Ptr;
1771   #endif
1772 
1773   CHashBundle hb;
1774   UStringVector methods;
1775   if (!_methods.IsEmpty())
1776   {
1777     FOR_VECTOR(k, _methods)
1778     {
1779       methods.Add(_methods[k]);
1780     }
1781   }
1782   else if (_crcSize_WasSet)
1783   {
1784     AddDefaultMethod(methods, _crcSize);
1785   }
1786   else
1787   {
1788     CMyComPtr<IArchiveGetRootProps> getRootProps;
1789     callback->QueryInterface(IID_IArchiveGetRootProps, (void **)&getRootProps);
1790 
1791     NCOM::CPropVariant prop;
1792     if (getRootProps)
1793     {
1794       RINOK(getRootProps->GetRootProp(kpidArcFileName, &prop));
1795       if (prop.vt == VT_BSTR)
1796       {
1797         const UString method = GetMethod_from_FileName(prop.bstrVal);
1798         if (!method.IsEmpty())
1799           methods.Add(method);
1800       }
1801     }
1802   }
1803 
1804   RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS methods));
1805 
1806   CLocalProgress *lps = new CLocalProgress;
1807   CMyComPtr<ICompressProgressInfo> progress = lps;
1808   lps->Init(callback, true);
1809 
1810   const UInt32 kBufSize = 1 << 15;
1811   CHashMidBuf buf;
1812   if (!buf.Alloc(kBufSize))
1813     return E_OUTOFMEMORY;
1814 
1815   CDynLimBuf hashFileString((size_t)1 << 31);
1816 
1817   CHashOptionsLocal options = _options;
1818 
1819   if (_isArc)
1820   {
1821     if (!options.HashMode_Zero.Def && _is_ZeroMode)
1822       options.HashMode_Zero.Val = true;
1823     if (!options.HashMode_Tag.Def && _are_there_Tags)
1824       options.HashMode_Tag.Val = true;
1825     if (!options.HashMode_Dirs.Def && _are_there_Dirs)
1826       options.HashMode_Dirs.Val = true;
1827   }
1828   if (options.HashMode_OnlyHash.Val && updateItems.Size() != 1)
1829     options.HashMode_OnlyHash.Val = false;
1830 
1831   lps->OutSize = 0;
1832   complexity = 0;
1833 
1834   for (i = 0; i < updateItems.Size(); i++)
1835   {
1836     lps->InSize = complexity;
1837     RINOK(lps->SetCur());
1838 
1839     const CUpdateItem &ui = updateItems[i];
1840 
1841     /*
1842     CHashPair item;
1843     if (!ui.NewProps)
1844       item = HashPairs[(unsigned)ui.IndexInArc];
1845     */
1846 
1847     if (ui.NewData)
1848     {
1849       UInt64 currentComplexity = ui.Size;
1850       UInt64 fileSize = 0;
1851 
1852       CMyComPtr<ISequentialInStream> fileInStream;
1853       bool needWrite = true;
1854       {
1855         HRESULT res = callback->GetStream(ui.IndexInClient, &fileInStream);
1856 
1857         if (res == S_FALSE)
1858           needWrite = false;
1859         else
1860         {
1861           RINOK(res);
1862 
1863           if (fileInStream)
1864           {
1865             CMyComPtr<IStreamGetSize> streamGetSize;
1866             fileInStream->QueryInterface(IID_IStreamGetSize, (void **)&streamGetSize);
1867             if (streamGetSize)
1868             {
1869               UInt64 size;
1870               if (streamGetSize->GetSize(&size) == S_OK)
1871                 currentComplexity = size;
1872             }
1873             /*
1874             CMyComPtr<IStreamGetProps> getProps;
1875             fileInStream->QueryInterface(IID_IStreamGetProps, (void **)&getProps);
1876             if (getProps)
1877             {
1878               FILETIME mTime;
1879               UInt64 size2;
1880               if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK)
1881               {
1882                 currentComplexity = size2;
1883                 // item.MTime = NWindows::NTime::FileTimeToUnixTime64(mTime);;
1884               }
1885             }
1886             */
1887           }
1888           else
1889           {
1890             currentComplexity = 0;
1891           }
1892         }
1893       }
1894 
1895       hb.InitForNewFile();
1896       const bool isDir = ui.IsDir;
1897 
1898       if (needWrite && fileInStream && !isDir)
1899       {
1900         for (UInt32 step = 0;; step++)
1901         {
1902           if ((step & 0xFF) == 0)
1903           {
1904             RINOK(lps->SetRatioInfo(&fileSize, NULL));
1905             // RINOK(callback->SetCompleted(&completeValue));
1906           }
1907           UInt32 size;
1908           RINOK(fileInStream->Read(buf, kBufSize, &size));
1909           if (size == 0)
1910             break;
1911           hb.Update(buf, size);
1912           fileSize += size;
1913         }
1914         currentComplexity = fileSize;
1915       }
1916 
1917       fileInStream.Release();
1918       const bool isAltStream = false;
1919       hb.Final(isDir, isAltStream, ui.Path);
1920 
1921       if (options.HashMode_Dirs.Val || !isDir)
1922       {
1923         if (!hb.Hashers.IsEmpty())
1924           lps->OutSize += hb.Hashers[0].DigestSize;
1925         WriteLine(hashFileString,
1926             options,
1927             ui.Path,
1928             isDir,
1929             hb);
1930         if (hashFileString.IsError())
1931           return E_OUTOFMEMORY;
1932       }
1933 
1934       complexity += currentComplexity;
1935 
1936       /*
1937       if (reportArcProp)
1938       {
1939         PROPVARIANT prop;
1940         prop.vt = VT_EMPTY;
1941         prop.wReserved1 = 0;
1942 
1943         NCOM::PropVarEm_Set_UInt64(&prop, fileSize);
1944         RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidSize, &prop));
1945 
1946         for (unsigned k = 0; k < hb.Hashers.Size(); k++)
1947         {
1948           const CHasherState &hs = hb.Hashers[k];
1949 
1950           if (hs.DigestSize == 4 && hs.Name.IsEqualTo_Ascii_NoCase("crc32"))
1951           {
1952             NCOM::PropVarEm_Set_UInt32(&prop, GetUi32(hs.Digests[k_HashCalc_Index_Current]));
1953             RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidCRC, &prop));
1954           }
1955           else
1956           {
1957             RINOK(reportArcProp->ReportRawProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient,
1958               kpidChecksum, hs.Digests[k_HashCalc_Index_Current],
1959               hs.DigestSize, NPropDataType::kRaw));
1960           }
1961           RINOK(reportArcProp->ReportFinished(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, NArchive::NUpdate::NOperationResult::kOK));
1962         }
1963       }
1964       */
1965       RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
1966     }
1967     else
1968     {
1969       // old data
1970       const CHashPair &existItem = HashPairs[(unsigned)ui.IndexInArc];
1971       if (ui.NewProps)
1972       {
1973         WriteLine(hashFileString,
1974             options,
1975             ui.Path,
1976             ui.IsDir,
1977             existItem.Method, existItem.HashString
1978             );
1979       }
1980       else
1981       {
1982         hashFileString += existItem.FullLine;
1983         Add_LF(hashFileString, options);
1984       }
1985     }
1986     if (hashFileString.IsError())
1987       return E_OUTOFMEMORY;
1988   }
1989 
1990   RINOK(WriteStream(outStream, hashFileString, hashFileString.Len()));
1991 
1992   return S_OK;
1993   COM_TRY_END
1994 }
1995 
1996 
1997 
SetProperty(const wchar_t * nameSpec,const PROPVARIANT & value)1998 HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value)
1999 {
2000   UString name = nameSpec;
2001   name.MakeLower_Ascii();
2002   if (name.IsEmpty())
2003     return E_INVALIDARG;
2004 
2005   if (name.IsEqualTo("m")) // "hm" hash method
2006   {
2007     // COneMethodInfo omi;
2008     // RINOK(omi.ParseMethodFromPROPVARIANT(L"", value));
2009     // _methods.Add(omi.MethodName); // change it. use omi.PropsString
2010     if (value.vt != VT_BSTR)
2011       return E_INVALIDARG;
2012     UString s (value.bstrVal);
2013     _methods.Add(s);
2014     return S_OK;
2015   }
2016 
2017   if (name.IsEqualTo("flags"))
2018   {
2019     if (value.vt != VT_BSTR)
2020       return E_INVALIDARG;
2021     if (!_options.ParseString(value.bstrVal))
2022       return E_INVALIDARG;
2023     return S_OK;
2024   }
2025 
2026   if (name.IsPrefixedBy_Ascii_NoCase("crc"))
2027   {
2028     name.Delete(0, 3);
2029     _crcSize = 4;
2030     _crcSize_WasSet = true;
2031     return ParsePropToUInt32(name, value, _crcSize);
2032   }
2033 
2034   // common properties
2035   if (name.IsPrefixedBy_Ascii_NoCase("mt")
2036       || name.IsPrefixedBy_Ascii_NoCase("memuse"))
2037     return S_OK;
2038 
2039   return E_INVALIDARG;
2040 }
2041 
2042 
SetProperties(const wchar_t * const * names,const PROPVARIANT * values,UInt32 numProps)2043 STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)
2044 {
2045   COM_TRY_BEGIN
2046 
2047   InitProps();
2048 
2049   for (UInt32 i = 0; i < numProps; i++)
2050   {
2051     RINOK(SetProperty(names[i], values[i]));
2052   }
2053   return S_OK;
2054   COM_TRY_END
2055 }
2056 
CHandler()2057 CHandler::CHandler()
2058 {
2059   ClearVars();
2060   InitProps();
2061 }
2062 
2063 }
2064 
2065 
2066 
CreateHashHandler_In()2067 static IInArchive  *CreateHashHandler_In()  { return new NHash::CHandler; }
CreateHashHandler_Out()2068 static IOutArchive *CreateHashHandler_Out() { return new NHash::CHandler; }
2069 
Codecs_AddHashArcHandler(CCodecs * codecs)2070 void Codecs_AddHashArcHandler(CCodecs *codecs)
2071 {
2072   {
2073     CArcInfoEx item;
2074 
2075     item.Name = "Hash";
2076     item.CreateInArchive = CreateHashHandler_In;
2077     item.CreateOutArchive = CreateHashHandler_Out;
2078     item.IsArcFunc = NULL;
2079     item.Flags =
2080         NArcInfoFlags::kKeepName
2081       | NArcInfoFlags::kStartOpen
2082       | NArcInfoFlags::kByExtOnlyOpen
2083       // | NArcInfoFlags::kPureStartOpen
2084       | NArcInfoFlags::kHashHandler
2085       ;
2086 
2087     // ubuntu uses "SHA256SUMS" file
2088     item.AddExts(UString (
2089         "sha256 sha512 sha224 sha384 sha1 sha md5"
2090         // "b2sum"
2091         " crc32 crc64"
2092         " asc"
2093         " cksum"
2094         ),
2095         UString());
2096 
2097     item.UpdateEnabled = (item.CreateOutArchive != NULL);
2098     item.SignatureOffset = 0;
2099     // item.Version = MY_VER_MIX;
2100     item.NewInterface = true;
2101 
2102     item.Signatures.AddNew().CopyFrom(NULL, 0);
2103 
2104     codecs->Formats.Add(item);
2105   }
2106 }
2107