xref: /third_party/lzma/CPP/7zip/UI/Common/HashCalc.cpp (revision 370b324c)
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
23using namespace NWindows;
24
25#ifdef Z7_EXTERNAL_CODECS
26extern const CExternalCodecs *g_ExternalCodecs_Ptr;
27#endif
28
29class CHashMidBuf
30{
31  void *_data;
32public:
33  CHashMidBuf(): _data(NULL) {}
34  operator void *() { return _data; }
35  bool Alloc(size_t size)
36  {
37    if (_data)
38      return false;
39    _data = ::MidAlloc(size);
40    return _data != NULL;
41  }
42  ~CHashMidBuf() { ::MidFree(_data); }
43};
44
45static const char * const k_DefaultHashMethod = "CRC32";
46
47HRESULT 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
119void 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
130void 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
137void CHashBundle::SetSize(UInt64 size)
138{
139  CurSize = size;
140}
141
142static 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
179void CHasherState::AddDigest(unsigned groupIndex, const Byte *data)
180{
181  NumSums[groupIndex]++;
182  AddDigests(Digests[groupIndex], data, DigestSize);
183}
184
185void 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
244static 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
262static 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
297static void SetSpacesAndNul(char *s, unsigned num)
298{
299  for (unsigned i = 0; i < num; i++)
300    s[i] = ' ';
301  s[num] = 0;
302}
303
304static const unsigned kHashColumnWidth_Min = 4 * 2;
305
306static unsigned GetColumnWidth(unsigned digestSize)
307{
308  const unsigned width = digestSize * 2;
309  return width < kHashColumnWidth_Min ? kHashColumnWidth_Min: width;
310}
311
312
313static void AddHashResultLine(
314    AString &_s,
315    // bool showHash,
316    // UInt64 fileSize, bool showSize,
317    const CObjectVector<CHasherState> &hashers
318    // unsigned digestIndex, = k_HashCalc_Index_Current
319    )
320{
321  FOR_VECTOR (i, hashers)
322  {
323    const CHasherState &h = hashers[i];
324    char s[k_HashCalc_DigestSize_Max * 2 + 64];
325    s[0] = 0;
326    // if (showHash)
327      HashHexToString(s, h.Digests[k_HashCalc_Index_Current], h.DigestSize);
328    const unsigned pos = (unsigned)strlen(s);
329    const int numSpaces = (int)GetColumnWidth(h.DigestSize) - (int)pos;
330    if (numSpaces > 0)
331      SetSpacesAndNul(s + pos, (unsigned)numSpaces);
332    if (i != 0)
333      _s.Add_Space();
334    _s += s;
335  }
336
337  /*
338  if (showSize)
339  {
340    _s.Add_Space();
341    static const unsigned kSizeField_Len = 13; // same as in HashCon.cpp
342    char s[kSizeField_Len + 32];
343    char *p = s;
344    SetSpacesAndNul(s, kSizeField_Len);
345    p = s + kSizeField_Len;
346    ConvertUInt64ToString(fileSize, p);
347    int numSpaces = (int)kSizeField_Len - (int)strlen(p);
348    if (numSpaces > 0)
349      p -= (unsigned)numSpaces;
350    _s += p;
351  }
352  */
353}
354
355
356static void Add_LF(CDynLimBuf &hashFileString, const CHashOptionsLocal &options)
357{
358  hashFileString += (char)(options.HashMode_Zero.Val ? 0 : '\n');
359}
360
361
362
363
364static 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
435static 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
451HRESULT 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
648static inline char GetHex_Upper(unsigned v)
649{
650  return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
651}
652
653static inline char GetHex_Lower(unsigned v)
654{
655  return (char)((v < 10) ? ('0' + v) : ('a' + (v - 10)));
656}
657
658void 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
696void 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
721namespace NHash {
722
723static 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
750bool 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
762bool 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
789static const char *SkipWhite(const char *s)
790{
791  while (IsWhite(*s))
792    s++;
793  return s;
794}
795
796static 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
812static UString GetMethod_from_FileName(const UString &name)
813{
814  AString s;
815  ConvertUnicodeToUTF8(name, s);
816  const int dotPos = s.ReverseFind_Dot();
817  const char *src = s.Ptr();
818  bool isExtension = false;
819  if (dotPos >= 0)
820  {
821    isExtension = true;
822    src = s.Ptr(dotPos + 1);
823  }
824  const char *m = "";
825  unsigned i;
826  for (i = 0; i < Z7_ARRAY_SIZE(k_CsumMethodNames); i++)
827  {
828    m = k_CsumMethodNames[i];
829    if (isExtension)
830    {
831      if (StringsAreEqual_Ascii(src, m))
832        break;
833    }
834    else if (IsString1PrefixedByString2_NoCase_Ascii(src, m))
835      if (StringsAreEqual_Ascii(src + strlen(m), "sums"))
836        break;
837  }
838  UString res;
839  if (i != Z7_ARRAY_SIZE(k_CsumMethodNames))
840    res = m;
841  return res;
842}
843
844
845bool 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
948static 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
992static 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
1016static const Byte kArcProps[] =
1017{
1018  // kpidComment,
1019  kpidCharacts
1020};
1021
1022static const Byte kProps[] =
1023{
1024  kpidPath,
1025  kpidSize,
1026  kpidPackSize,
1027  kpidMethod
1028};
1029
1030static const Byte kRawProps[] =
1031{
1032  kpidChecksum
1033};
1034
1035
1036Z7_COM7F_IMF(CHandler::GetParent(UInt32 /* index */ , UInt32 *parent, UInt32 *parentType))
1037{
1038  *parentType = NParentType::kDir;
1039  *parent = (UInt32)(Int32)-1;
1040  return S_OK;
1041}
1042
1043Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
1044{
1045  *numProps = Z7_ARRAY_SIZE(kRawProps);
1046  return S_OK;
1047}
1048
1049Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID))
1050{
1051  *propID = kRawProps[index];
1052  *name = NULL;
1053  return S_OK;
1054}
1055
1056Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1057{
1058  *data = NULL;
1059  *dataSize = 0;
1060  *propType = 0;
1061
1062  if (propID == kpidChecksum)
1063  {
1064    const CHashPair &hp = HashPairs[index];
1065    if (hp.Hash.Size() > 0)
1066    {
1067      *data = hp.Hash;
1068      *dataSize = (UInt32)hp.Hash.Size();
1069      *propType = NPropDataType::kRaw;
1070    }
1071    return S_OK;
1072  }
1073
1074  return S_OK;
1075}
1076
1077IMP_IInArchive_Props
1078IMP_IInArchive_ArcProps
1079
1080Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
1081{
1082  *numItems = HashPairs.Size();
1083  return S_OK;
1084}
1085
1086static void Add_OptSpace_String(UString &dest, const char *src)
1087{
1088  dest.Add_Space_if_NotEmpty();
1089  dest += src;
1090}
1091
1092Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1093{
1094  NWindows::NCOM::CPropVariant prop;
1095  switch (propID)
1096  {
1097    case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
1098    /*
1099    case kpidErrorFlags:
1100    {
1101      UInt32 v = 0;
1102      if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
1103      // if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
1104      if (v != 0)
1105        prop = v;
1106      break;
1107    }
1108    */
1109    case kpidCharacts:
1110    {
1111      UString s;
1112      if (_hashSize_Defined)
1113      {
1114        s.Add_Space_if_NotEmpty();
1115        s.Add_UInt32(_hashSize * 8);
1116        s += "-bit";
1117      }
1118      if (!_nameExtenstion.IsEmpty())
1119      {
1120        s.Add_Space_if_NotEmpty();
1121        s += _nameExtenstion;
1122      }
1123      if (_is_PgpMethod)
1124      {
1125        Add_OptSpace_String(s, "PGP");
1126        if (!_pgpMethod.IsEmpty())
1127        {
1128          s += ":";
1129          s += _pgpMethod;
1130        }
1131      }
1132      if (_is_ZeroMode)
1133        Add_OptSpace_String(s, "ZERO");
1134      if (_are_there_Tags)
1135        Add_OptSpace_String(s, "TAG");
1136      if (_are_there_Dirs)
1137        Add_OptSpace_String(s, "DIRS");
1138      prop = s;
1139      break;
1140    }
1141
1142    case kpidReadOnly:
1143    {
1144      if (_isArc)
1145        if (!CanUpdate())
1146          prop = true;
1147      break;
1148    }
1149  }
1150  prop.Detach(value);
1151  return S_OK;
1152}
1153
1154
1155Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1156{
1157  // COM_TRY_BEGIN
1158  NWindows::NCOM::CPropVariant prop;
1159  CHashPair &hp = HashPairs[index];
1160  switch (propID)
1161  {
1162    case kpidIsDir:
1163    {
1164      prop = hp.IsDir();
1165      break;
1166    }
1167    case kpidPath:
1168    {
1169      UString path;
1170      hp.Get_UString_Path(path);
1171
1172      NArchive::NItemName::ReplaceToOsSlashes_Remove_TailSlash(path,
1173          true); // useBackslashReplacement
1174
1175      prop = path;
1176      break;
1177    }
1178    case kpidSize:
1179    {
1180      // client needs processed size of last file
1181      if (hp.Size_from_Disk_Defined)
1182        prop = (UInt64)hp.Size_from_Disk;
1183      else if (hp.Size_from_Arc_Defined)
1184        prop = (UInt64)hp.Size_from_Arc;
1185      break;
1186    }
1187    case kpidPackSize:
1188    {
1189      prop = (UInt64)hp.Hash.Size();
1190      break;
1191    }
1192    case kpidMethod:
1193    {
1194      if (!hp.Method.IsEmpty())
1195        prop = hp.Method;
1196      break;
1197    }
1198  }
1199  prop.Detach(value);
1200  return S_OK;
1201  // COM_TRY_END
1202}
1203
1204
1205static HRESULT ReadStream_to_Buf(IInStream *stream, CByteBuffer &buf, IArchiveOpenCallback *openCallback)
1206{
1207  buf.Free();
1208  UInt64 len;
1209  RINOK(InStream_AtBegin_GetSize(stream, len))
1210  if (len == 0 || len >= ((UInt64)1 << 31))
1211    return S_FALSE;
1212  buf.Alloc((size_t)len);
1213  UInt64 pos = 0;
1214  // return ReadStream_FALSE(stream, buf, (size_t)len);
1215  for (;;)
1216  {
1217    const UInt32 kBlockSize = ((UInt32)1 << 24);
1218    const UInt32 curSize = (len < kBlockSize) ? (UInt32)len : kBlockSize;
1219    UInt32 processedSizeLoc;
1220    RINOK(stream->Read((Byte *)buf + pos, curSize, &processedSizeLoc))
1221    if (processedSizeLoc == 0)
1222      return E_FAIL;
1223    len -= processedSizeLoc;
1224    pos += processedSizeLoc;
1225    if (len == 0)
1226      return S_OK;
1227    if (openCallback)
1228    {
1229      const UInt64 files = 0;
1230      RINOK(openCallback->SetCompleted(&files, &pos))
1231    }
1232  }
1233}
1234
1235
1236Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback))
1237{
1238  COM_TRY_BEGIN
1239  {
1240    Close();
1241
1242    CByteBuffer buf;
1243    RINOK(ReadStream_to_Buf(stream, buf, openCallback))
1244
1245    CObjectVector<CHashPair> &pairs = HashPairs;
1246
1247    bool zeroMode = false;
1248    bool cr_lf_Mode = false;
1249    {
1250      for (size_t i = 0; i < buf.Size(); i++)
1251        if (buf[i] == 0)
1252        {
1253          zeroMode = true;
1254          break;
1255        }
1256    }
1257    _is_ZeroMode = zeroMode;
1258    if (!zeroMode)
1259      cr_lf_Mode = Is_CR_LF_Data(buf, buf.Size());
1260
1261    if (openCallback)
1262    {
1263      Z7_DECL_CMyComPtr_QI_FROM(
1264          IArchiveOpenVolumeCallback,
1265          openVolumeCallback, openCallback)
1266      if (openVolumeCallback)
1267      {
1268        NCOM::CPropVariant prop;
1269        RINOK(openVolumeCallback->GetProperty(kpidName, &prop))
1270        if (prop.vt == VT_BSTR)
1271          _nameExtenstion = GetMethod_from_FileName(prop.bstrVal);
1272      }
1273    }
1274
1275    bool cksumMode = false;
1276    if (_nameExtenstion.IsEqualTo_Ascii_NoCase("cksum"))
1277      cksumMode = true;
1278    _is_CksumMode = cksumMode;
1279
1280    size_t pos = 0;
1281    AString s;
1282    bool minusMode = false;
1283    unsigned numLines = 0;
1284
1285    while (pos < buf.Size())
1286    {
1287      if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s))
1288        return S_FALSE;
1289      numLines++;
1290      if (s.IsEmpty())
1291        continue;
1292
1293      if (s.IsPrefixedBy_Ascii_NoCase("; "))
1294      {
1295        if (numLines != 1)
1296          return S_FALSE;
1297        // comment line of FileVerifier++
1298        continue;
1299      }
1300
1301      if (s.IsPrefixedBy_Ascii_NoCase("-----"))
1302      {
1303        if (minusMode)
1304          break; // end of pgp mode
1305        minusMode = true;
1306        if (s.IsPrefixedBy_Ascii_NoCase("-----BEGIN PGP SIGNED MESSAGE"))
1307        {
1308          if (_is_PgpMethod)
1309            return S_FALSE;
1310          if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s))
1311            return S_FALSE;
1312          const char *kStart = "Hash: ";
1313          if (!s.IsPrefixedBy_Ascii_NoCase(kStart))
1314            return S_FALSE;
1315          _pgpMethod = s.Ptr((unsigned)strlen(kStart));
1316          _is_PgpMethod = true;
1317        }
1318        continue;
1319      }
1320
1321      CHashPair pair;
1322      pair.FullLine = s;
1323      if (cksumMode)
1324      {
1325        if (!pair.ParseCksum(s))
1326          return S_FALSE;
1327      }
1328      else if (!pair.Parse(s))
1329        return S_FALSE;
1330      pairs.Add(pair);
1331    }
1332
1333    {
1334      unsigned hashSize = 0;
1335      bool hashSize_Dismatch = false;
1336      for (unsigned i = 0; i < HashPairs.Size(); i++)
1337      {
1338        const CHashPair &hp = HashPairs[i];
1339        if (i == 0)
1340          hashSize = (unsigned)hp.Hash.Size();
1341        else
1342          if (hashSize != hp.Hash.Size())
1343            hashSize_Dismatch = true;
1344
1345        if (hp.IsBSD)
1346          _are_there_Tags = true;
1347        if (!_are_there_Dirs && hp.IsDir())
1348          _are_there_Dirs = true;
1349      }
1350      if (!hashSize_Dismatch && hashSize != 0)
1351      {
1352        _hashSize = hashSize;
1353        _hashSize_Defined = true;
1354      }
1355    }
1356
1357    _phySize = buf.Size();
1358    _isArc = true;
1359    return S_OK;
1360  }
1361  COM_TRY_END
1362}
1363
1364
1365void 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
1379Z7_COM7F_IMF(CHandler::Close())
1380{
1381  ClearVars();
1382  _nameExtenstion.Empty();
1383  _pgpMethod.Empty();
1384  HashPairs.Clear();
1385  return S_OK;
1386}
1387
1388
1389static 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
1409static void AddDefaultMethod(UStringVector &methods, unsigned size)
1410{
1411  const char *m = NULL;
1412       if (size == 32) m = "sha256";
1413  else if (size == 20) m = "sha1";
1414  else if (size == 16) m = "md5";
1415  else if (size ==  8) m = "crc64";
1416  else if (size ==  4) m = "crc32";
1417  else
1418    return;
1419  #ifdef Z7_EXTERNAL_CODECS
1420  const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1421  #endif
1422  CMethodId id;
1423  if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS
1424      AString(m), id))
1425    methods.Add(UString(m));
1426}
1427
1428
1429Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1430    Int32 testMode, IArchiveExtractCallback *extractCallback))
1431{
1432  COM_TRY_BEGIN
1433
1434  /*
1435  if (testMode == 0)
1436    return E_NOTIMPL;
1437  */
1438
1439  const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1440  if (allFilesMode)
1441    numItems = HashPairs.Size();
1442  if (numItems == 0)
1443    return S_OK;
1444
1445  #ifdef Z7_EXTERNAL_CODECS
1446  const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1447  #endif
1448
1449  CHashBundle hb_Glob;
1450  // UStringVector methods = options.Methods;
1451  UStringVector methods;
1452
1453  if (methods.IsEmpty() && !_nameExtenstion.IsEmpty())
1454  {
1455    AString utf;
1456    ConvertUnicodeToUTF8(_nameExtenstion, utf);
1457    CMethodId id;
1458    if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS utf, id))
1459      methods.Add(_nameExtenstion);
1460  }
1461
1462  if (methods.IsEmpty() && !_pgpMethod.IsEmpty())
1463  {
1464    CMethodId id;
1465    if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS _pgpMethod, id))
1466      methods.Add(UString(_pgpMethod));
1467  }
1468
1469  if (methods.IsEmpty() && _pgpMethod.IsEmpty() && _hashSize_Defined)
1470    AddDefaultMethod(methods, _hashSize);
1471
1472  RINOK(hb_Glob.SetMethods(
1473      EXTERNAL_CODECS_LOC_VARS
1474      methods))
1475
1476  Z7_DECL_CMyComPtr_QI_FROM(
1477      IArchiveUpdateCallbackFile,
1478      updateCallbackFile, extractCallback)
1479  if (!updateCallbackFile)
1480    return E_NOTIMPL;
1481  {
1482    Z7_DECL_CMyComPtr_QI_FROM(
1483        IArchiveGetDiskProperty,
1484        GetDiskProperty, extractCallback)
1485    if (GetDiskProperty)
1486    {
1487      UInt64 totalSize = 0;
1488      UInt32 i;
1489      for (i = 0; i < numItems; i++)
1490      {
1491        const UInt32 index = allFilesMode ? i : indices[i];
1492        const CHashPair &hp = HashPairs[index];
1493        if (hp.IsDir())
1494          continue;
1495        {
1496          NCOM::CPropVariant prop;
1497          RINOK(GetDiskProperty->GetDiskProperty(index, kpidSize, &prop))
1498          if (prop.vt != VT_UI8)
1499            continue;
1500          totalSize += prop.uhVal.QuadPart;
1501        }
1502      }
1503      RINOK(extractCallback->SetTotal(totalSize))
1504      // RINOK(Hash_SetTotalUnpacked->Hash_SetTotalUnpacked(indices, numItems));
1505    }
1506  }
1507
1508  const UInt32 kBufSize = 1 << 15;
1509  CHashMidBuf buf;
1510  if (!buf.Alloc(kBufSize))
1511    return E_OUTOFMEMORY;
1512
1513  CLocalProgress *lps = new CLocalProgress;
1514  CMyComPtr<ICompressProgressInfo> progress = lps;
1515  lps->Init(extractCallback, false);
1516  lps->InSize = lps->OutSize = 0;
1517
1518  UInt32 i;
1519  for (i = 0; i < numItems; i++)
1520  {
1521    RINOK(lps->SetCur())
1522    const UInt32 index = allFilesMode ? i : indices[i];
1523
1524    CHashPair &hp = HashPairs[index];
1525
1526    UString path;
1527    hp.Get_UString_Path(path);
1528
1529    CMyComPtr<ISequentialInStream> inStream;
1530    const bool isDir = hp.IsDir();
1531    if (!isDir)
1532    {
1533      RINOK(updateCallbackFile->GetStream2(index, &inStream, NUpdateNotifyOp::kHashRead))
1534      if (!inStream)
1535      {
1536        continue; // we have shown error in GetStream2()
1537      }
1538      // askMode = NArchive::NExtract::NAskMode::kSkip;
1539    }
1540
1541    Int32 askMode = testMode ?
1542        NArchive::NExtract::NAskMode::kTest :
1543        NArchive::NExtract::NAskMode::kExtract;
1544
1545    CMyComPtr<ISequentialOutStream> realOutStream;
1546    RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
1547
1548    /* PrepareOperation() can expect kExtract to set
1549       Attrib and security of output file */
1550    askMode = NArchive::NExtract::NAskMode::kReadExternal;
1551
1552    extractCallback->PrepareOperation(askMode);
1553
1554    const bool isAltStream = false;
1555
1556    UInt64 fileSize = 0;
1557
1558    CHashBundle hb_Loc;
1559
1560    CHashBundle *hb_Use = &hb_Glob;
1561
1562    HRESULT res_SetMethods = S_OK;
1563
1564    UStringVector methods_loc;
1565
1566    if (!hp.Method.IsEmpty())
1567    {
1568      hb_Use = &hb_Loc;
1569      CMethodId id;
1570      if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS hp.Method, id))
1571      {
1572        methods_loc.Add(UString(hp.Method));
1573        RINOK(hb_Loc.SetMethods(
1574            EXTERNAL_CODECS_LOC_VARS
1575            methods_loc))
1576      }
1577      else
1578        res_SetMethods = E_NOTIMPL;
1579    }
1580    else if (methods.IsEmpty())
1581    {
1582      AddDefaultMethod(methods_loc, (unsigned)hp.Hash.Size());
1583      if (!methods_loc.IsEmpty())
1584      {
1585        hb_Use = &hb_Loc;
1586        RINOK(hb_Loc.SetMethods(
1587            EXTERNAL_CODECS_LOC_VARS
1588            methods_loc))
1589      }
1590    }
1591
1592    const bool isSupportedMode = hp.IsSupportedMode();
1593    hb_Use->InitForNewFile();
1594
1595    if (inStream)
1596    {
1597      for (UInt32 step = 0;; step++)
1598      {
1599        if ((step & 0xFF) == 0)
1600        {
1601          RINOK(progress->SetRatioInfo(NULL, &fileSize))
1602        }
1603        UInt32 size;
1604        RINOK(inStream->Read(buf, kBufSize, &size))
1605        if (size == 0)
1606          break;
1607        hb_Use->Update(buf, size);
1608        if (realOutStream)
1609        {
1610          RINOK(WriteStream(realOutStream, buf, size))
1611        }
1612        fileSize += size;
1613      }
1614
1615      hp.Size_from_Disk = fileSize;
1616      hp.Size_from_Disk_Defined = true;
1617    }
1618
1619    realOutStream.Release();
1620    inStream.Release();
1621
1622    lps->InSize += hp.Hash.Size();
1623    lps->OutSize += fileSize;
1624
1625    hb_Use->Final(isDir, isAltStream, path);
1626
1627    Int32 opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod;
1628    if (isSupportedMode
1629        && res_SetMethods != E_NOTIMPL
1630        && hb_Use->Hashers.Size() > 0
1631        )
1632    {
1633      const CHasherState &hs = hb_Use->Hashers[0];
1634      if (hs.DigestSize == hp.Hash.Size())
1635      {
1636        opRes = NArchive::NExtract::NOperationResult::kCRCError;
1637        if (CheckDigests(hp.Hash, hs.Digests[0], hs.DigestSize))
1638          if (!hp.Size_from_Arc_Defined || hp.Size_from_Arc == fileSize)
1639            opRes = NArchive::NExtract::NOperationResult::kOK;
1640      }
1641    }
1642
1643    RINOK(extractCallback->SetOperationResult(opRes))
1644  }
1645
1646  return lps->SetCur();
1647
1648  COM_TRY_END
1649}
1650
1651
1652// ---------- UPDATE ----------
1653
1654struct CUpdateItem
1655{
1656  int IndexInArc;
1657  unsigned IndexInClient;
1658  UInt64 Size;
1659  bool NewData;
1660  bool NewProps;
1661  bool IsDir;
1662  UString Path;
1663
1664  CUpdateItem(): Size(0), IsDir(false) {}
1665};
1666
1667
1668static HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId,
1669    UString &res,
1670    bool convertSlash)
1671{
1672  NCOM::CPropVariant prop;
1673  RINOK(callback->GetProperty(index, propId, &prop))
1674  if (prop.vt == VT_BSTR)
1675  {
1676    res = prop.bstrVal;
1677    if (convertSlash)
1678      NArchive::NItemName::ReplaceSlashes_OsToUnix(res);
1679  }
1680  else if (prop.vt != VT_EMPTY)
1681    return E_INVALIDARG;
1682  return S_OK;
1683}
1684
1685
1686Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type))
1687{
1688  *type = NFileTimeType::kUnix;
1689  return S_OK;
1690}
1691
1692
1693Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
1694    IArchiveUpdateCallback *callback))
1695{
1696  COM_TRY_BEGIN
1697
1698  if (_isArc && !CanUpdate())
1699    return E_NOTIMPL;
1700
1701  /*
1702  Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp,
1703      reportArcProp, callback)
1704  */
1705
1706  CObjectVector<CUpdateItem> updateItems;
1707
1708  UInt64 complexity = 0;
1709
1710  UInt32 i;
1711  for (i = 0; i < numItems; i++)
1712  {
1713    CUpdateItem ui;
1714    Int32 newData;
1715    Int32 newProps;
1716    UInt32 indexInArc;
1717
1718    if (!callback)
1719      return E_FAIL;
1720
1721    RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc))
1722
1723    ui.NewProps = IntToBool(newProps);
1724    ui.NewData = IntToBool(newData);
1725    ui.IndexInArc = (int)indexInArc;
1726    ui.IndexInClient = i;
1727    if (IntToBool(newProps))
1728    {
1729      {
1730        NCOM::CPropVariant prop;
1731        RINOK(callback->GetProperty(i, kpidIsDir, &prop))
1732        if (prop.vt == VT_EMPTY)
1733          ui.IsDir = false;
1734        else if (prop.vt != VT_BOOL)
1735          return E_INVALIDARG;
1736        else
1737          ui.IsDir = (prop.boolVal != VARIANT_FALSE);
1738      }
1739
1740      RINOK(GetPropString(callback, i, kpidPath, ui.Path,
1741          true)) // convertSlash
1742      /*
1743      if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/')
1744        ui.Name += '/';
1745      */
1746    }
1747
1748    if (IntToBool(newData))
1749    {
1750      NCOM::CPropVariant prop;
1751      RINOK(callback->GetProperty(i, kpidSize, &prop))
1752      if (prop.vt == VT_UI8)
1753      {
1754        ui.Size = prop.uhVal.QuadPart;
1755        complexity += ui.Size;
1756      }
1757      else if (prop.vt == VT_EMPTY)
1758        ui.Size = (UInt64)(Int64)-1;
1759      else
1760        return E_INVALIDARG;
1761    }
1762
1763    updateItems.Add(ui);
1764  }
1765
1766  if (complexity != 0)
1767  {
1768    RINOK(callback->SetTotal(complexity))
1769  }
1770
1771  #ifdef Z7_EXTERNAL_CODECS
1772  const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr;
1773  #endif
1774
1775  CHashBundle hb;
1776  UStringVector methods;
1777  if (!_methods.IsEmpty())
1778  {
1779    FOR_VECTOR(k, _methods)
1780    {
1781      methods.Add(_methods[k]);
1782    }
1783  }
1784  else if (_crcSize_WasSet)
1785  {
1786    AddDefaultMethod(methods, _crcSize);
1787  }
1788  else
1789  {
1790    Z7_DECL_CMyComPtr_QI_FROM(
1791        IArchiveGetRootProps,
1792        getRootProps, callback)
1793    if (getRootProps)
1794    {
1795      NCOM::CPropVariant prop;
1796      RINOK(getRootProps->GetRootProp(kpidArcFileName, &prop))
1797      if (prop.vt == VT_BSTR)
1798      {
1799        const UString method = GetMethod_from_FileName(prop.bstrVal);
1800        if (!method.IsEmpty())
1801          methods.Add(method);
1802      }
1803    }
1804  }
1805
1806  RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS methods))
1807
1808  CLocalProgress *lps = new CLocalProgress;
1809  CMyComPtr<ICompressProgressInfo> progress = lps;
1810  lps->Init(callback, true);
1811
1812  const UInt32 kBufSize = 1 << 15;
1813  CHashMidBuf buf;
1814  if (!buf.Alloc(kBufSize))
1815    return E_OUTOFMEMORY;
1816
1817  CDynLimBuf hashFileString((size_t)1 << 31);
1818
1819  CHashOptionsLocal options = _options;
1820
1821  if (_isArc)
1822  {
1823    if (!options.HashMode_Zero.Def && _is_ZeroMode)
1824      options.HashMode_Zero.Val = true;
1825    if (!options.HashMode_Tag.Def && _are_there_Tags)
1826      options.HashMode_Tag.Val = true;
1827    if (!options.HashMode_Dirs.Def && _are_there_Dirs)
1828      options.HashMode_Dirs.Val = true;
1829  }
1830  if (options.HashMode_OnlyHash.Val && updateItems.Size() != 1)
1831    options.HashMode_OnlyHash.Val = false;
1832
1833  lps->OutSize = 0;
1834  complexity = 0;
1835
1836  for (i = 0; i < updateItems.Size(); i++)
1837  {
1838    lps->InSize = complexity;
1839    RINOK(lps->SetCur())
1840
1841    const CUpdateItem &ui = updateItems[i];
1842
1843    /*
1844    CHashPair item;
1845    if (!ui.NewProps)
1846      item = HashPairs[(unsigned)ui.IndexInArc];
1847    */
1848
1849    if (ui.NewData)
1850    {
1851      UInt64 currentComplexity = ui.Size;
1852      UInt64 fileSize = 0;
1853
1854      CMyComPtr<ISequentialInStream> fileInStream;
1855      bool needWrite = true;
1856      {
1857        HRESULT res = callback->GetStream(ui.IndexInClient, &fileInStream);
1858
1859        if (res == S_FALSE)
1860          needWrite = false;
1861        else
1862        {
1863          RINOK(res)
1864
1865          if (fileInStream)
1866          {
1867            Z7_DECL_CMyComPtr_QI_FROM(
1868                IStreamGetSize,
1869                streamGetSize, fileInStream)
1870            if (streamGetSize)
1871            {
1872              UInt64 size;
1873              if (streamGetSize->GetSize(&size) == S_OK)
1874                currentComplexity = size;
1875            }
1876            /*
1877            Z7_DECL_CMyComPtr_QI_FROM(
1878                IStreamGetProps,
1879                getProps, fileInStream)
1880            if (getProps)
1881            {
1882              FILETIME mTime;
1883              UInt64 size2;
1884              if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK)
1885              {
1886                currentComplexity = size2;
1887                // item.MTime = NWindows::NTime::FileTimeToUnixTime64(mTime);;
1888              }
1889            }
1890            */
1891          }
1892          else
1893          {
1894            currentComplexity = 0;
1895          }
1896        }
1897      }
1898
1899      hb.InitForNewFile();
1900      const bool isDir = ui.IsDir;
1901
1902      if (needWrite && fileInStream && !isDir)
1903      {
1904        for (UInt32 step = 0;; step++)
1905        {
1906          if ((step & 0xFF) == 0)
1907          {
1908            RINOK(progress->SetRatioInfo(&fileSize, NULL))
1909            // RINOK(callback->SetCompleted(&completeValue));
1910          }
1911          UInt32 size;
1912          RINOK(fileInStream->Read(buf, kBufSize, &size))
1913          if (size == 0)
1914            break;
1915          hb.Update(buf, size);
1916          fileSize += size;
1917        }
1918        currentComplexity = fileSize;
1919      }
1920
1921      fileInStream.Release();
1922      const bool isAltStream = false;
1923      hb.Final(isDir, isAltStream, ui.Path);
1924
1925      if (options.HashMode_Dirs.Val || !isDir)
1926      {
1927        if (!hb.Hashers.IsEmpty())
1928          lps->OutSize += hb.Hashers[0].DigestSize;
1929        WriteLine(hashFileString,
1930            options,
1931            ui.Path,
1932            isDir,
1933            hb);
1934        if (hashFileString.IsError())
1935          return E_OUTOFMEMORY;
1936      }
1937
1938      complexity += currentComplexity;
1939
1940      /*
1941      if (reportArcProp)
1942      {
1943        PROPVARIANT prop;
1944        prop.vt = VT_EMPTY;
1945        prop.wReserved1 = 0;
1946
1947        NCOM::PropVarEm_Set_UInt64(&prop, fileSize);
1948        RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidSize, &prop));
1949
1950        for (unsigned k = 0; k < hb.Hashers.Size(); k++)
1951        {
1952          const CHasherState &hs = hb.Hashers[k];
1953
1954          if (hs.DigestSize == 4 && hs.Name.IsEqualTo_Ascii_NoCase("crc32"))
1955          {
1956            NCOM::PropVarEm_Set_UInt32(&prop, GetUi32(hs.Digests[k_HashCalc_Index_Current]));
1957            RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidCRC, &prop));
1958          }
1959          else
1960          {
1961            RINOK(reportArcProp->ReportRawProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient,
1962              kpidChecksum, hs.Digests[k_HashCalc_Index_Current],
1963              hs.DigestSize, NPropDataType::kRaw));
1964          }
1965          RINOK(reportArcProp->ReportFinished(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, NArchive::NUpdate::NOperationResult::kOK));
1966        }
1967      }
1968      */
1969      RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK))
1970    }
1971    else
1972    {
1973      // old data
1974      const CHashPair &existItem = HashPairs[(unsigned)ui.IndexInArc];
1975      if (ui.NewProps)
1976      {
1977        WriteLine(hashFileString,
1978            options,
1979            ui.Path,
1980            ui.IsDir,
1981            existItem.Method, existItem.HashString
1982            );
1983      }
1984      else
1985      {
1986        hashFileString += existItem.FullLine;
1987        Add_LF(hashFileString, options);
1988      }
1989    }
1990    if (hashFileString.IsError())
1991      return E_OUTOFMEMORY;
1992  }
1993
1994  RINOK(WriteStream(outStream, hashFileString, hashFileString.Len()))
1995
1996  return S_OK;
1997  COM_TRY_END
1998}
1999
2000
2001
2002HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value)
2003{
2004  UString name = nameSpec;
2005  name.MakeLower_Ascii();
2006  if (name.IsEmpty())
2007    return E_INVALIDARG;
2008
2009  if (name.IsEqualTo("m")) // "hm" hash method
2010  {
2011    // COneMethodInfo omi;
2012    // RINOK(omi.ParseMethodFromPROPVARIANT(L"", value));
2013    // _methods.Add(omi.MethodName); // change it. use omi.PropsString
2014    if (value.vt != VT_BSTR)
2015      return E_INVALIDARG;
2016    UString s (value.bstrVal);
2017    _methods.Add(s);
2018    return S_OK;
2019  }
2020
2021  if (name.IsEqualTo("flags"))
2022  {
2023    if (value.vt != VT_BSTR)
2024      return E_INVALIDARG;
2025    if (!_options.ParseString(value.bstrVal))
2026      return E_INVALIDARG;
2027    return S_OK;
2028  }
2029
2030  if (name.IsPrefixedBy_Ascii_NoCase("crc"))
2031  {
2032    name.Delete(0, 3);
2033    _crcSize = 4;
2034    _crcSize_WasSet = true;
2035    return ParsePropToUInt32(name, value, _crcSize);
2036  }
2037
2038  // common properties
2039  if (name.IsPrefixedBy_Ascii_NoCase("mt")
2040      || name.IsPrefixedBy_Ascii_NoCase("memuse"))
2041    return S_OK;
2042
2043  return E_INVALIDARG;
2044}
2045
2046
2047Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps))
2048{
2049  COM_TRY_BEGIN
2050
2051  InitProps();
2052
2053  for (UInt32 i = 0; i < numProps; i++)
2054  {
2055    RINOK(SetProperty(names[i], values[i]))
2056  }
2057  return S_OK;
2058  COM_TRY_END
2059}
2060
2061CHandler::CHandler()
2062{
2063  ClearVars();
2064  InitProps();
2065}
2066
2067}
2068
2069
2070
2071static IInArchive  *CreateHashHandler_In()  { return new NHash::CHandler; }
2072static IOutArchive *CreateHashHandler_Out() { return new NHash::CHandler; }
2073
2074void Codecs_AddHashArcHandler(CCodecs *codecs)
2075{
2076  {
2077    CArcInfoEx item;
2078
2079    item.Name = "Hash";
2080    item.CreateInArchive = CreateHashHandler_In;
2081    item.CreateOutArchive = CreateHashHandler_Out;
2082    item.IsArcFunc = NULL;
2083    item.Flags =
2084        NArcInfoFlags::kKeepName
2085      | NArcInfoFlags::kStartOpen
2086      | NArcInfoFlags::kByExtOnlyOpen
2087      // | NArcInfoFlags::kPureStartOpen
2088      | NArcInfoFlags::kHashHandler
2089      ;
2090
2091    // ubuntu uses "SHA256SUMS" file
2092    item.AddExts(UString (
2093        "sha256 sha512 sha224 sha384 sha1 sha md5"
2094        // "b2sum"
2095        " crc32 crc64"
2096        " asc"
2097        " cksum"
2098        ),
2099        UString());
2100
2101    item.UpdateEnabled = (item.CreateOutArchive != NULL);
2102    item.SignatureOffset = 0;
2103    // item.Version = MY_VER_MIX;
2104    item.NewInterface = true;
2105
2106    item.Signatures.AddNew().CopyFrom(NULL, 0);
2107
2108    codecs->Formats.Add(item);
2109  }
2110}
2111