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