xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/LzhHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // LzhHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/AutoPtr.h"
8 #include "../../Common/ComTry.h"
9 #include "../../Common/MyBuffer.h"
10 #include "../../Common/StringConvert.h"
11 
12 #include "../../Windows/PropVariant.h"
13 #include "../../Windows/PropVariantUtils.h"
14 #include "../../Windows/TimeUtils.h"
15 
16 #include "../ICoder.h"
17 
18 #include "../Common/LimitedStreams.h"
19 #include "../Common/ProgressUtils.h"
20 #include "../Common/RegisterArc.h"
21 #include "../Common/StreamUtils.h"
22 
23 #include "../Compress/CopyCoder.h"
24 #include "../Compress/LzhDecoder.h"
25 
26 #include "IArchive.h"
27 
28 #include "Common/ItemNameUtils.h"
29 
30 using namespace NWindows;
31 using namespace NTime;
32 
33 #define Get16(p) GetUi16(p)
34 #define Get32(p) GetUi32(p)
35 
36 
37 // CRC-16 (-IBM, -ANSI). The poly is 0x8005 (x^16 + x^15 + x^2 + 1)
38 
39 static const UInt16 kCrc16Poly = 0xA001;
40 
41 MY_ALIGN(64)
42 static UInt16 g_LzhCrc16Table[256];
43 
44 #define CRC16_UPDATE_BYTE(crc, b) (g_LzhCrc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
45 
46 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size);
LzhCrc16Update(UInt32 crc,const void * data,size_t size)47 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size)
48 {
49   const Byte *p = (const Byte *)data;
50   const Byte *pEnd = p + size;
51   for (; p != pEnd; p++)
52     crc = CRC16_UPDATE_BYTE(crc, *p);
53   return crc;
54 }
55 
56 static struct CLzhCrc16TableInit
57 {
CLzhCrc16TableInitCLzhCrc16TableInit58   CLzhCrc16TableInit()
59   {
60     for (UInt32 i = 0; i < 256; i++)
61     {
62       UInt32 r = i;
63       for (unsigned j = 0; j < 8; j++)
64         r = (r >> 1) ^ (kCrc16Poly & ((UInt32)0 - (r & 1)));
65       g_LzhCrc16Table[i] = (UInt16)r;
66     }
67   }
68 } g_LzhCrc16TableInit;
69 
70 
71 namespace NArchive {
72 namespace NLzh{
73 
74 const unsigned kMethodIdSize = 5;
75 
76 const Byte kExtIdFileName = 0x01;
77 const Byte kExtIdDirName  = 0x02;
78 const Byte kExtIdUnixTime = 0x54;
79 
80 struct CExtension
81 {
82   Byte Type;
83   CByteBuffer Data;
84 
GetStringNArchive::NLzh::CExtension85   AString GetString() const
86   {
87     AString s;
88     s.SetFrom_CalcLen((const char *)(const Byte *)Data, (unsigned)Data.Size());
89     return s;
90   }
91 };
92 
93 const UInt32 kBasicPartSize = 22;
94 
IsArc_Lzh(const Byte * p,size_t size)95 API_FUNC_static_IsArc IsArc_Lzh(const Byte *p, size_t size)
96 {
97   if (size < 2 + kBasicPartSize)
98     return k_IsArc_Res_NEED_MORE;
99   if (p[2] != '-' || p[3] != 'l'  || p[4] != 'h' || p[6] != '-')
100     return k_IsArc_Res_NO;
101   Byte n = p[5];
102   if (n != 'd')
103     if (n < '0' || n > '7')
104       return k_IsArc_Res_NO;
105   return k_IsArc_Res_YES;
106 }
107 }
108 
109 struct CItem
110 {
111   AString Name;
112   Byte Method[kMethodIdSize];
113   Byte Attributes;
114   Byte Level;
115   Byte OsId;
116   UInt32 PackSize;
117   UInt32 Size;
118   UInt32 ModifiedTime;
119   UInt16 CRC;
120   CObjectVector<CExtension> Extensions;
121 
IsValidMethodNArchive::CItem122   bool IsValidMethod() const  { return (Method[0] == '-' && Method[1] == 'l' && Method[4] == '-'); }
IsLhMethodNArchive::CItem123   bool IsLhMethod() const  {return (IsValidMethod() && Method[2] == 'h'); }
IsDirNArchive::CItem124   bool IsDir() const {return (IsLhMethod() && Method[3] == 'd'); }
125 
IsCopyMethodNArchive::CItem126   bool IsCopyMethod() const
127   {
128     return (IsLhMethod() && Method[3] == '0') ||
129       (IsValidMethod() && Method[2] == 'z' && Method[3] == '4');
130   }
131 
IsLh1GroupMethodNArchive::CItem132   bool IsLh1GroupMethod() const
133   {
134     if (!IsLhMethod())
135       return false;
136     switch (Method[3])
137     {
138       case '1':
139         return true;
140     }
141     return false;
142   }
143 
IsLh4GroupMethodNArchive::CItem144   bool IsLh4GroupMethod() const
145   {
146     if (!IsLhMethod())
147       return false;
148     switch (Method[3])
149     {
150       case '4':
151       case '5':
152       case '6':
153       case '7':
154         return true;
155     }
156     return false;
157   }
158 
GetNumDictBitsNArchive::CItem159   unsigned GetNumDictBits() const
160   {
161     if (!IsLhMethod())
162       return 0;
163     switch (Method[3])
164     {
165       case '1': return 12;
166       case '2': return 13;
167       case '3': return 13;
168       case '4': return 12;
169       case '5': return 13;
170       case '6': return 15;
171       case '7': return 16;
172     }
173     return 0;
174   }
175 
FindExtNArchive::CItem176   int FindExt(Byte type) const
177   {
178     FOR_VECTOR (i, Extensions)
179       if (Extensions[i].Type == type)
180         return (int)i;
181     return -1;
182   }
183 
GetUnixTimeNArchive::CItem184   bool GetUnixTime(UInt32 &value) const
185   {
186     value = 0;
187     int index = FindExt(kExtIdUnixTime);
188     if (index < 0
189         || Extensions[index].Data.Size() < 4)
190     {
191       if (Level == 2)
192       {
193         value = ModifiedTime;
194         return true;
195       }
196       return false;
197     }
198     const Byte *data = (const Byte *)(Extensions[index].Data);
199     value = GetUi32(data);
200     return true;
201   }
202 
GetDirNameNArchive::CItem203   AString GetDirName() const
204   {
205     int index = FindExt(kExtIdDirName);
206     if (index < 0)
207       return AString();
208     return Extensions[index].GetString();
209   }
210 
GetFileNameNArchive::CItem211   AString GetFileName() const
212   {
213     int index = FindExt(kExtIdFileName);
214     if (index < 0)
215       return Name;
216     return Extensions[index].GetString();
217   }
218 
GetNameNArchive::CItem219   AString GetName() const
220   {
221     AString s (GetDirName());
222     const char kDirSeparator = '\\';
223     // check kDirSeparator in Linux
224     s.Replace((char)(Byte)0xFF, kDirSeparator);
225     if (!s.IsEmpty() && s.Back() != kDirSeparator)
226       s += kDirSeparator;
227     s += GetFileName();
228     return s;
229   }
230 };
231 
ReadUInt16(const Byte * p,UInt16 & v)232 static const Byte *ReadUInt16(const Byte *p, UInt16 &v)
233 {
234   v = Get16(p);
235   return p + 2;
236 }
237 
ReadString(const Byte * p,size_t size,AString & s)238 static const Byte *ReadString(const Byte *p, size_t size, AString &s)
239 {
240   s.Empty();
241   for (size_t i = 0; i < size; i++)
242   {
243     const Byte c = p[i];
244     if (c == 0)
245       break;
246     s += (char)c;
247   }
248   return p + size;
249 }
250 
CalcSum(const Byte * data,size_t size)251 static Byte CalcSum(const Byte *data, size_t size)
252 {
253   Byte sum = 0;
254   for (size_t i = 0; i < size; i++)
255     sum = (Byte)(sum + data[i]);
256   return sum;
257 }
258 
GetNextItem(ISequentialInStream * stream,bool & filled,CItem & item)259 static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &item)
260 {
261   filled = false;
262 
263   size_t processedSize = 2;
264   Byte startHeader[2];
265   RINOK(ReadStream(stream, startHeader, &processedSize))
266   if (processedSize == 0)
267     return S_OK;
268   if (processedSize == 1)
269     return (startHeader[0] == 0) ? S_OK: S_FALSE;
270   if (startHeader[0] == 0 && startHeader[1] == 0)
271     return S_OK;
272 
273   Byte header[256];
274   processedSize = kBasicPartSize;
275   RINOK(ReadStream(stream, header, &processedSize))
276   if (processedSize != kBasicPartSize)
277     return (startHeader[0] == 0) ? S_OK: S_FALSE;
278 
279   const Byte *p = header;
280   memcpy(item.Method, p, kMethodIdSize);
281   if (!item.IsValidMethod())
282     return S_OK;
283   p += kMethodIdSize;
284   item.PackSize = Get32(p);
285   item.Size = Get32(p + 4);
286   item.ModifiedTime = Get32(p + 8);
287   item.Attributes = p[12];
288   item.Level = p[13];
289   p += 14;
290   if (item.Level > 2)
291     return S_FALSE;
292   UInt32 headerSize;
293   if (item.Level < 2)
294   {
295     headerSize = startHeader[0];
296     if (headerSize < kBasicPartSize)
297       return S_FALSE;
298     RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, headerSize - kBasicPartSize))
299     if (startHeader[1] != CalcSum(header, headerSize))
300       return S_FALSE;
301     const size_t nameLength = *p++;
302     if ((size_t)(p - header) + nameLength + 2 > headerSize)
303       return S_FALSE;
304     p = ReadString(p, nameLength, item.Name);
305   }
306   else
307     headerSize = startHeader[0] | ((UInt32)startHeader[1] << 8);
308   p = ReadUInt16(p, item.CRC);
309   if (item.Level != 0)
310   {
311     if (item.Level == 2)
312     {
313       RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, 2))
314     }
315     if ((size_t)(p - header) + 3 > headerSize)
316       return S_FALSE;
317     item.OsId = *p++;
318     UInt16 nextSize;
319     p = ReadUInt16(p, nextSize);
320     while (nextSize != 0)
321     {
322       if (nextSize < 3)
323         return S_FALSE;
324       if (item.Level == 1)
325       {
326         if (item.PackSize < nextSize)
327           return S_FALSE;
328         item.PackSize -= nextSize;
329       }
330       if (item.Extensions.Size() >= (1 << 8))
331         return S_FALSE;
332       CExtension ext;
333       RINOK(ReadStream_FALSE(stream, &ext.Type, 1))
334       nextSize = (UInt16)(nextSize - 3);
335       ext.Data.Alloc(nextSize);
336       RINOK(ReadStream_FALSE(stream, (Byte *)ext.Data, nextSize))
337       item.Extensions.Add(ext);
338       Byte hdr2[2];
339       RINOK(ReadStream_FALSE(stream, hdr2, 2))
340       ReadUInt16(hdr2, nextSize);
341     }
342   }
343   filled = true;
344   return S_OK;
345 }
346 
347 
348 static const CUInt32PCharPair g_OsPairs[] =
349 {
350   {   0, "MS-DOS" },
351   { 'M', "MS-DOS" },
352   { '2', "OS/2" },
353   { '9', "OS9" },
354   { 'K', "OS/68K" },
355   { '3', "OS/386" },
356   { 'H', "HUMAN" },
357   { 'U', "UNIX" },
358   { 'C', "CP/M" },
359   { 'F', "FLEX" },
360   { 'm', "Mac" },
361   { 'R', "Runser" },
362   { 'T', "TownsOS" },
363   { 'X', "XOSK" },
364   { 'w', "Windows 95" },
365   { 'W', "Windows NT" },
366   { 'J', "Java VM" }
367 };
368 
369 
370 static const Byte kProps[] =
371 {
372   kpidPath,
373   kpidIsDir,
374   kpidSize,
375   kpidPackSize,
376   kpidMTime,
377   // kpidAttrib,
378   kpidCRC,
379   kpidMethod,
380   kpidHostOS
381 };
382 
383 
384 Z7_CLASS_IMP_NOQIB_1(
385   COutStreamWithCRC
386   , ISequentialOutStream
387 )
388   UInt32 _crc;
389   CMyComPtr<ISequentialOutStream> _stream;
390 public:
391   void Init(ISequentialOutStream *stream)
392   {
393     _stream = stream;
394     _crc = 0;
395   }
396   void ReleaseStream() { _stream.Release(); }
397   UInt32 GetCRC() const { return _crc; }
398 };
399 
400 Z7_COM7F_IMF(COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize))
401 {
402   HRESULT res = S_OK;
403   if (_stream)
404     res = _stream->Write(data, size, &size);
405   _crc = LzhCrc16Update(_crc, data, size);
406   if (processedSize)
407     *processedSize = size;
408   return res;
409 }
410 
411 
412 struct CItemEx: public CItem
413 {
414   UInt64 DataPosition;
415 };
416 
417 
418 Z7_CLASS_IMP_CHandler_IInArchive_0
419 
420   CObjectVector<CItemEx> _items;
421   CMyComPtr<IInStream> _stream;
422   UInt64 _phySize;
423   UInt32 _errorFlags;
424   bool _isArc;
425 public:
426   CHandler();
427 };
428 
429 IMP_IInArchive_Props
430 IMP_IInArchive_ArcProps_NO_Table
431 
432 CHandler::CHandler() {}
433 
434 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
435 {
436   *numItems = _items.Size();
437   return S_OK;
438 }
439 
440 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
441 {
442   NCOM::CPropVariant prop;
443   switch (propID)
444   {
445     case kpidPhySize: prop = _phySize; break;
446 
447     case kpidErrorFlags:
448       UInt32 v = _errorFlags;
449       if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
450       prop = v;
451       break;
452   }
453   prop.Detach(value);
454   return S_OK;
455 }
456 
457 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
458 {
459   COM_TRY_BEGIN
460   NCOM::CPropVariant prop;
461   const CItemEx &item = _items[index];
462   switch (propID)
463   {
464     case kpidPath:
465     {
466       UString s = NItemName::WinPathToOsPath(MultiByteToUnicodeString(item.GetName(), CP_OEMCP));
467       if (!s.IsEmpty())
468       {
469         if (s.Back() == WCHAR_PATH_SEPARATOR)
470           s.DeleteBack();
471         prop = s;
472       }
473       break;
474     }
475     case kpidIsDir:  prop = item.IsDir(); break;
476     case kpidSize:   prop = (UInt64)item.Size; break;
477     case kpidPackSize:  prop = (UInt64)item.PackSize; break;
478     case kpidCRC:  prop = (UInt32)item.CRC; break;
479     case kpidHostOS:  PAIR_TO_PROP(g_OsPairs, item.OsId, prop); break;
480     case kpidMTime:
481     {
482       UInt32 unixTime;
483       if (item.GetUnixTime(unixTime))
484         PropVariant_SetFrom_UnixTime(prop, unixTime);
485       else
486         PropVariant_SetFrom_DosTime(prop, item.ModifiedTime);
487       break;
488     }
489     // case kpidAttrib:  prop = (UInt32)item.Attributes; break;
490     case kpidMethod:
491     {
492       char method2[kMethodIdSize + 1];
493       method2[kMethodIdSize] = 0;
494       memcpy(method2, item.Method, kMethodIdSize);
495       prop = method2;
496       break;
497     }
498   }
499   prop.Detach(value);
500   return S_OK;
501   COM_TRY_END
502 }
503 
504 Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
505     const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback))
506 {
507   COM_TRY_BEGIN
508   Close();
509   try
510   {
511     _items.Clear();
512 
513     UInt64 endPos;
514     bool needSetTotal = true;
515 
516     RINOK(InStream_AtBegin_GetSize(stream, endPos))
517 
518     for (;;)
519     {
520       CItemEx item;
521       bool filled;
522       const HRESULT res = GetNextItem(stream, filled, item);
523       RINOK(InStream_GetPos(stream, item.DataPosition))
524       if (res == S_FALSE)
525       {
526         _errorFlags = kpv_ErrorFlags_HeadersError;
527         break;
528       }
529 
530       if (res != S_OK)
531         return S_FALSE;
532       _phySize = item.DataPosition;
533       if (!filled)
534         break;
535       _items.Add(item);
536 
537       _isArc = true;
538 
539       UInt64 newPostion;
540       RINOK(stream->Seek(item.PackSize, STREAM_SEEK_CUR, &newPostion))
541       if (newPostion > endPos)
542       {
543         _phySize = endPos;
544         _errorFlags = kpv_ErrorFlags_UnexpectedEnd;
545         break;
546       }
547       _phySize = newPostion;
548       if (callback)
549       {
550         if (needSetTotal)
551         {
552           RINOK(callback->SetTotal(NULL, &endPos))
553           needSetTotal = false;
554         }
555         if (_items.Size() % 100 == 0)
556         {
557           const UInt64 numFiles = _items.Size();
558           const UInt64 numBytes = item.DataPosition;
559           RINOK(callback->SetCompleted(&numFiles, &numBytes))
560         }
561       }
562     }
563     if (_items.IsEmpty())
564       return S_FALSE;
565 
566     _stream = stream;
567   }
568   catch(...)
569   {
570     return S_FALSE;
571   }
572   COM_TRY_END
573   return S_OK;
574 }
575 
576 Z7_COM7F_IMF(CHandler::Close())
577 {
578   _isArc = false;
579   _phySize = 0;
580   _errorFlags = 0;
581   _items.Clear();
582   _stream.Release();
583   return S_OK;
584 }
585 
586 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
587     Int32 testMode, IArchiveExtractCallback *extractCallback))
588 {
589   COM_TRY_BEGIN
590   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
591   if (allFilesMode)
592     numItems = _items.Size();
593   if (numItems == 0)
594     return S_OK;
595   UInt64 totalUnPacked = 0 /* , totalPacked = 0 */;
596   UInt32 i;
597   for (i = 0; i < numItems; i++)
598   {
599     const CItemEx &item = _items[allFilesMode ? i : indices[i]];
600     totalUnPacked += item.Size;
601     // totalPacked += item.PackSize;
602   }
603   RINOK(extractCallback->SetTotal(totalUnPacked))
604 
605   UInt32 cur_Unpacked, cur_Packed;
606 
607   CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
608   lps->Init(extractCallback, false);
609   CMyUniquePtr<NCompress::NLzh::NDecoder::CCoder> lzhDecoder;
610   CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
611   CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStream;
612   inStream->SetStream(_stream);
613 
614   for (i = 0;; i++,
615       lps->OutSize += cur_Unpacked,
616       lps->InSize += cur_Packed)
617   {
618     cur_Unpacked = 0;
619     cur_Packed = 0;
620     RINOK(lps->SetCur())
621     if (i >= numItems)
622       break;
623 
624     Int32 opRes;
625     {
626       CMyComPtr<ISequentialOutStream> realOutStream;
627       const Int32 askMode = testMode ?
628           NExtract::NAskMode::kTest :
629           NExtract::NAskMode::kExtract;
630       const UInt32 index = allFilesMode ? i : indices[i];
631       const CItemEx &item = _items[index];
632       RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
633 
634       if (item.IsDir())
635       {
636         // if (!testMode)
637         {
638           RINOK(extractCallback->PrepareOperation(askMode))
639           RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
640         }
641         continue;
642       }
643 
644       if (!testMode && !realOutStream)
645         continue;
646 
647       RINOK(extractCallback->PrepareOperation(askMode))
648       cur_Unpacked = item.Size;
649       cur_Packed = item.PackSize;
650 
651       CMyComPtr2_Create<ISequentialOutStream, COutStreamWithCRC> outStream;
652       outStream->Init(realOutStream);
653       realOutStream.Release();
654 
655       RINOK(InStream_SeekSet(_stream, item.DataPosition))
656 
657       inStream->Init(item.PackSize);
658 
659       HRESULT res = S_OK;
660       opRes = NExtract::NOperationResult::kOK;
661 
662       if (item.IsCopyMethod())
663       {
664         res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps);
665         if (res == S_OK && copyCoder->TotalSize != item.PackSize)
666           res = S_FALSE;
667       }
668       else if (item.IsLh4GroupMethod())
669       {
670         lzhDecoder.Create_if_Empty();
671         // lzhDecoder->FinishMode = true;
672         lzhDecoder->SetDictSize((UInt32)1 << item.GetNumDictBits());
673         res = lzhDecoder->Code(inStream, outStream, cur_Unpacked, lps);
674         if (res == S_OK && lzhDecoder->GetInputProcessedSize() != item.PackSize)
675           res = S_FALSE;
676       }
677       /*
678       else if (item.IsLh1GroupMethod())
679       {
680         if (!lzh1Decoder)
681         {
682           lzh1DecoderSpec = new NCompress::NLzh1::NDecoder::CCoder;
683           lzh1Decoder = lzh1DecoderSpec;
684         }
685         lzh1DecoderSpec->SetDictionary(item.GetNumDictBits());
686         res = lzh1Decoder->Code(inStream, outStream, NULL, &cur_Unpacked, progress);
687       }
688       */
689       else
690         opRes = NExtract::NOperationResult::kUnsupportedMethod;
691 
692       if (opRes == NExtract::NOperationResult::kOK)
693       {
694         if (res == S_FALSE)
695           opRes = NExtract::NOperationResult::kDataError;
696         else
697         {
698           RINOK(res)
699           if (outStream->GetCRC() != item.CRC)
700             opRes = NExtract::NOperationResult::kCRCError;
701         }
702       }
703     }
704     RINOK(extractCallback->SetOperationResult(opRes))
705   }
706   return S_OK;
707   COM_TRY_END
708 }
709 
710 static const Byte k_Signature[] = { '-', 'l', 'h' };
711 
712 REGISTER_ARC_I(
713   "Lzh", "lzh lha", NULL, 6,
714   k_Signature,
715   2,
716   0,
717   IsArc_Lzh)
718 
719 }}
720