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