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