• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // LzmaHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/IntToString.h"
9 
10 #include "../../Windows/PropVariant.h"
11 
12 #include "../Common/FilterCoder.h"
13 #include "../Common/ProgressUtils.h"
14 #include "../Common/RegisterArc.h"
15 #include "../Common/StreamUtils.h"
16 
17 #include "../Compress/BcjCoder.h"
18 #include "../Compress/LzmaDecoder.h"
19 
20 #include "Common/DummyOutStream.h"
21 
22 using namespace NWindows;
23 
24 namespace NArchive {
25 namespace NLzma {
26 
CheckDicSize(const Byte * p)27 static bool CheckDicSize(const Byte *p)
28 {
29   UInt32 dicSize = GetUi32(p);
30   if (dicSize == 1)
31     return true;
32   for (unsigned i = 0; i <= 30; i++)
33     if (dicSize == ((UInt32)2 << i) || dicSize == ((UInt32)3 << i))
34       return true;
35   return (dicSize == 0xFFFFFFFF);
36 }
37 
38 static const Byte kProps[] =
39 {
40   kpidSize,
41   kpidPackSize,
42   kpidMethod
43 };
44 
45 static const Byte kArcProps[] =
46 {
47   kpidNumStreams,
48   kpidMethod
49 };
50 
51 struct CHeader
52 {
53   UInt64 Size;
54   Byte FilterID;
55   Byte LzmaProps[5];
56 
GetPropNArchive::NLzma::CHeader57   Byte GetProp() const { return LzmaProps[0]; }
GetDicSizeNArchive::NLzma::CHeader58   UInt32 GetDicSize() const { return GetUi32(LzmaProps + 1); }
HasSizeNArchive::NLzma::CHeader59   bool HasSize() const { return (Size != (UInt64)(Int64)-1); }
60   bool Parse(const Byte *buf, bool isThereFilter);
61 };
62 
Parse(const Byte * buf,bool isThereFilter)63 bool CHeader::Parse(const Byte *buf, bool isThereFilter)
64 {
65   FilterID = 0;
66   if (isThereFilter)
67     FilterID = buf[0];
68   const Byte *sig = buf + (isThereFilter ? 1 : 0);
69   for (int i = 0; i < 5; i++)
70     LzmaProps[i] = sig[i];
71   Size = GetUi64(sig + 5);
72   return
73     LzmaProps[0] < 5 * 5 * 9 &&
74     FilterID < 2 &&
75     (!HasSize() || Size < ((UInt64)1 << 56))
76     && CheckDicSize(LzmaProps + 1);
77 }
78 
79 class CDecoder
80 {
81   CMyComPtr<ISequentialOutStream> _bcjStream;
82   CFilterCoder *_filterCoder;
83   CMyComPtr<ICompressCoder> _lzmaDecoder;
84 public:
85   NCompress::NLzma::CDecoder *_lzmaDecoderSpec;
86 
87   ~CDecoder();
88   HRESULT Create(bool filtered, ISequentialInStream *inStream);
89 
90   HRESULT Code(const CHeader &header, ISequentialOutStream *outStream, ICompressProgressInfo *progress);
91 
GetInputProcessedSize() const92   UInt64 GetInputProcessedSize() const { return _lzmaDecoderSpec->GetInputProcessedSize(); }
93 
ReleaseInStream()94   void ReleaseInStream() { if (_lzmaDecoder) _lzmaDecoderSpec->ReleaseInStream(); }
95 
ReadInput(Byte * data,UInt32 size,UInt32 * processedSize)96   HRESULT ReadInput(Byte *data, UInt32 size, UInt32 *processedSize)
97     { return _lzmaDecoderSpec->ReadFromInputStream(data, size, processedSize); }
98 };
99 
Create(bool filteredMode,ISequentialInStream * inStream)100 HRESULT CDecoder::Create(bool filteredMode, ISequentialInStream *inStream)
101 {
102   if (!_lzmaDecoder)
103   {
104     _lzmaDecoderSpec = new NCompress::NLzma::CDecoder;
105     _lzmaDecoderSpec->FinishStream = true;
106     _lzmaDecoder = _lzmaDecoderSpec;
107   }
108 
109   if (filteredMode)
110   {
111     if (!_bcjStream)
112     {
113       _filterCoder = new CFilterCoder(false);
114       CMyComPtr<ICompressCoder> coder = _filterCoder;
115       _filterCoder->Filter = new NCompress::NBcj::CCoder(false);
116       _bcjStream = _filterCoder;
117     }
118   }
119 
120   return _lzmaDecoderSpec->SetInStream(inStream);
121 }
122 
~CDecoder()123 CDecoder::~CDecoder()
124 {
125   ReleaseInStream();
126 }
127 
Code(const CHeader & header,ISequentialOutStream * outStream,ICompressProgressInfo * progress)128 HRESULT CDecoder::Code(const CHeader &header, ISequentialOutStream *outStream,
129     ICompressProgressInfo *progress)
130 {
131   if (header.FilterID > 1)
132     return E_NOTIMPL;
133 
134   RINOK(_lzmaDecoderSpec->SetDecoderProperties2(header.LzmaProps, 5));
135 
136   bool filteredMode = (header.FilterID == 1);
137 
138   if (filteredMode)
139   {
140     RINOK(_filterCoder->SetOutStream(outStream));
141     outStream = _bcjStream;
142     RINOK(_filterCoder->SetOutStreamSize(NULL));
143   }
144 
145   const UInt64 *Size = header.HasSize() ? &header.Size : NULL;
146   HRESULT res = _lzmaDecoderSpec->CodeResume(outStream, Size, progress);
147 
148   if (filteredMode)
149   {
150     {
151       HRESULT res2 = _filterCoder->OutStreamFinish();
152       if (res == S_OK)
153         res = res2;
154     }
155     HRESULT res2 = _filterCoder->ReleaseOutStream();
156     if (res == S_OK)
157       res = res2;
158   }
159 
160   RINOK(res);
161 
162   if (header.HasSize())
163     if (_lzmaDecoderSpec->GetOutputProcessedSize() != header.Size)
164       return S_FALSE;
165 
166   return S_OK;
167 }
168 
169 
170 class CHandler:
171   public IInArchive,
172   public IArchiveOpenSeq,
173   public CMyUnknownImp
174 {
175   CHeader _header;
176   bool _lzma86;
177   CMyComPtr<IInStream> _stream;
178   CMyComPtr<ISequentialInStream> _seqStream;
179 
180   bool _isArc;
181   bool _needSeekToStart;
182   bool _dataAfterEnd;
183   bool _needMoreInput;
184 
185   bool _packSize_Defined;
186   bool _unpackSize_Defined;
187   bool _numStreams_Defined;
188 
189   bool _unsupported;
190   bool _dataError;
191 
192   UInt64 _packSize;
193   UInt64 _unpackSize;
194   UInt64 _numStreams;
195 
196   void GetMethod(NCOM::CPropVariant &prop);
197 
198 public:
199   MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq)
200 
201   INTERFACE_IInArchive(;)
202   STDMETHOD(OpenSeq)(ISequentialInStream *stream);
203 
CHandler(bool lzma86)204   CHandler(bool lzma86) { _lzma86 = lzma86; }
205 
GetHeaderSize() const206   unsigned GetHeaderSize() const { return 5 + 8 + (_lzma86 ? 1 : 0); }
207 
208 };
209 
210 IMP_IInArchive_Props
211 IMP_IInArchive_ArcProps
212 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)213 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
214 {
215   NCOM::CPropVariant prop;
216   switch (propID)
217   {
218     case kpidPhySize: if (_packSize_Defined) prop = _packSize; break;
219     case kpidNumStreams: if (_numStreams_Defined) prop = _numStreams; break;
220     case kpidUnpackSize: if (_unpackSize_Defined) prop = _unpackSize; break;
221     case kpidMethod: GetMethod(prop); break;
222     case kpidErrorFlags:
223     {
224       UInt32 v = 0;
225       if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;;
226       if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
227       if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd;
228       if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod;
229       if (_dataError) v |= kpv_ErrorFlags_DataError;
230       prop = v;
231       break;
232     }
233   }
234   prop.Detach(value);
235   return S_OK;
236 }
237 
GetNumberOfItems(UInt32 * numItems)238 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
239 {
240   *numItems = 1;
241   return S_OK;
242 }
243 
244 
DictSizeToString(UInt32 val,char * s)245 static void DictSizeToString(UInt32 val, char *s)
246 {
247   for (unsigned i = 0; i <= 31; i++)
248     if (((UInt32)1 << i) == val)
249     {
250       ::ConvertUInt32ToString(i, s);
251       return;
252     }
253   char c = 'b';
254        if ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; }
255   else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; }
256   ::ConvertUInt32ToString(val, s);
257   s += MyStringLen(s);
258   *s++ = c;
259   *s = 0;
260 }
261 
AddProp32(char * s,const char * name,UInt32 v)262 static char *AddProp32(char *s, const char *name, UInt32 v)
263 {
264   *s++ = ':';
265   s = MyStpCpy(s, name);
266   ::ConvertUInt32ToString(v, s);
267   return s + MyStringLen(s);
268 }
269 
GetMethod(NCOM::CPropVariant & prop)270 void CHandler::GetMethod(NCOM::CPropVariant &prop)
271 {
272   if (!_stream)
273     return;
274 
275   char sz[64];
276   char *s = sz;
277   if (_header.FilterID != 0)
278     s = MyStpCpy(s, "BCJ ");
279   s = MyStpCpy(s, "LZMA:");
280   DictSizeToString(_header.GetDicSize(), s);
281   s += strlen(s);
282 
283   UInt32 d = _header.GetProp();
284   // if (d != 0x5D)
285   {
286     UInt32 lc = d % 9;
287     d /= 9;
288     UInt32 pb = d / 5;
289     UInt32 lp = d % 5;
290     if (lc != 3) s = AddProp32(s, "lc", lc);
291     if (lp != 0) s = AddProp32(s, "lp", lp);
292     if (pb != 2) s = AddProp32(s, "pb", pb);
293   }
294   prop = sz;
295 }
296 
297 
GetProperty(UInt32,PROPID propID,PROPVARIANT * value)298 STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
299 {
300   NCOM::CPropVariant prop;
301   switch (propID)
302   {
303     case kpidSize: if (_stream && _header.HasSize()) prop = _header.Size; break;
304     case kpidPackSize: if (_packSize_Defined) prop = _packSize; break;
305     case kpidMethod: GetMethod(prop); break;
306   }
307   prop.Detach(value);
308   return S_OK;
309 }
310 
IsArc_Lzma(const Byte * p,size_t size)311 API_FUNC_static_IsArc IsArc_Lzma(const Byte *p, size_t size)
312 {
313   const UInt32 kHeaderSize = 1 + 4 + 8;
314   if (size < kHeaderSize)
315     return k_IsArc_Res_NEED_MORE;
316   if (p[0] >= 5 * 5 * 9)
317     return k_IsArc_Res_NO;
318   UInt64 unpackSize = GetUi64(p + 1 + 4);
319   if (unpackSize != (UInt64)(Int64)-1)
320   {
321     if (size >= ((UInt64)1 << 56))
322       return k_IsArc_Res_NO;
323   }
324   if (unpackSize != 0)
325   {
326     if (size < kHeaderSize + 2)
327       return k_IsArc_Res_NEED_MORE;
328     if (p[kHeaderSize] != 0)
329       return k_IsArc_Res_NO;
330     if (unpackSize != (UInt64)(Int64)-1)
331     {
332       if ((p[kHeaderSize + 1] & 0x80) != 0)
333         return k_IsArc_Res_NO;
334     }
335   }
336   if (!CheckDicSize(p + 1))
337     // return k_IsArc_Res_YES_LOW_PROB;
338     return k_IsArc_Res_NO;
339   return k_IsArc_Res_YES;
340 }
341 }
342 
IsArc_Lzma86(const Byte * p,size_t size)343 API_FUNC_static_IsArc IsArc_Lzma86(const Byte *p, size_t size)
344 {
345   if (size < 1)
346     return k_IsArc_Res_NEED_MORE;
347   Byte filterID = p[0];
348   if (filterID != 0 && filterID != 1)
349     return k_IsArc_Res_NO;
350   return IsArc_Lzma(p + 1, size - 1);
351 }
352 }
353 
354 
355 
Open(IInStream * inStream,const UInt64 *,IArchiveOpenCallback *)356 STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *)
357 {
358   Close();
359 
360   const unsigned headerSize = GetHeaderSize();
361   const UInt32 kBufSize = 1 << 7;
362   Byte buf[kBufSize];
363   size_t processedSize = kBufSize;
364   RINOK(ReadStream(inStream, buf, &processedSize));
365   if (processedSize < headerSize + 2)
366     return S_FALSE;
367   if (!_header.Parse(buf, _lzma86))
368     return S_FALSE;
369   const Byte *start = buf + headerSize;
370   if (start[0] != 0 /* || (start[1] & 0x80) != 0 */ ) // empty stream with EOS is not 0x80
371     return S_FALSE;
372 
373   RINOK(inStream->Seek(0, STREAM_SEEK_END, &_packSize));
374 
375   SizeT srcLen = processedSize - headerSize;
376 
377   if (srcLen > 10
378       && _header.Size == 0
379       // && _header.FilterID == 0
380       && _header.LzmaProps[0] == 0
381       )
382     return S_FALSE;
383 
384   CDecoder state;
385   const UInt32 outLimit = 1 << 11;
386   Byte outBuf[outLimit];
387 
388   SizeT outSize = outLimit;
389   if (outSize > _header.Size)
390     outSize = (SizeT)_header.Size;
391   SizeT destLen = outSize;
392   ELzmaStatus status;
393 
394   SRes res = LzmaDecode(outBuf, &destLen, start, &srcLen,
395       _header.LzmaProps, 5, LZMA_FINISH_ANY,
396       &status, &g_Alloc);
397 
398   if (res != SZ_OK)
399     if (res != SZ_ERROR_INPUT_EOF)
400       return S_FALSE;
401 
402   _isArc = true;
403   _stream = inStream;
404   _seqStream = inStream;
405   _needSeekToStart = true;
406   return S_OK;
407 }
408 
OpenSeq(ISequentialInStream * stream)409 STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream)
410 {
411   Close();
412   _isArc = true;
413   _seqStream = stream;
414   return S_OK;
415 }
416 
Close()417 STDMETHODIMP CHandler::Close()
418 {
419   _isArc = false;
420   _packSize_Defined = false;
421   _unpackSize_Defined = false;
422   _numStreams_Defined = false;
423 
424   _dataAfterEnd = false;
425   _needMoreInput = false;
426   _unsupported = false;
427   _dataError = false;
428 
429   _packSize = 0;
430 
431   _needSeekToStart = false;
432 
433   _stream.Release();
434   _seqStream.Release();
435    return S_OK;
436 }
437 
438 class CCompressProgressInfoImp:
439   public ICompressProgressInfo,
440   public CMyUnknownImp
441 {
442   CMyComPtr<IArchiveOpenCallback> Callback;
443 public:
444   UInt64 Offset;
445 
446   MY_UNKNOWN_IMP1(ICompressProgressInfo)
447   STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
Init(IArchiveOpenCallback * callback)448   void Init(IArchiveOpenCallback *callback) { Callback = callback; }
449 };
450 
SetRatioInfo(const UInt64 * inSize,const UInt64 *)451 STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)
452 {
453   if (Callback)
454   {
455     const UInt64 files = 0;
456     const UInt64 val = Offset + *inSize;
457     return Callback->SetCompleted(&files, &val);
458   }
459   return S_OK;
460 }
461 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)462 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
463     Int32 testMode, IArchiveExtractCallback *extractCallback)
464 {
465   COM_TRY_BEGIN
466 
467   if (numItems == 0)
468     return S_OK;
469   if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0))
470     return E_INVALIDARG;
471 
472   if (_packSize_Defined)
473     extractCallback->SetTotal(_packSize);
474 
475 
476   CMyComPtr<ISequentialOutStream> realOutStream;
477   Int32 askMode = testMode ?
478       NExtract::NAskMode::kTest :
479       NExtract::NAskMode::kExtract;
480   RINOK(extractCallback->GetStream(0, &realOutStream, askMode));
481   if (!testMode && !realOutStream)
482     return S_OK;
483 
484   extractCallback->PrepareOperation(askMode);
485 
486   CDummyOutStream *outStreamSpec = new CDummyOutStream;
487   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
488   outStreamSpec->SetStream(realOutStream);
489   outStreamSpec->Init();
490   realOutStream.Release();
491 
492   CLocalProgress *lps = new CLocalProgress;
493   CMyComPtr<ICompressProgressInfo> progress = lps;
494   lps->Init(extractCallback, true);
495 
496   if (_needSeekToStart)
497   {
498     if (!_stream)
499       return E_FAIL;
500     RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL));
501   }
502   else
503     _needSeekToStart = true;
504 
505   CDecoder decoder;
506   HRESULT result = decoder.Create(_lzma86, _seqStream);
507   RINOK(result);
508 
509   bool firstItem = true;
510 
511   UInt64 packSize = 0;
512   UInt64 unpackSize = 0;
513   UInt64 numStreams = 0;
514 
515   bool dataAfterEnd = false;
516 
517   for (;;)
518   {
519     lps->InSize = packSize;
520     lps->OutSize = unpackSize;
521     RINOK(lps->SetCur());
522 
523     const UInt32 kBufSize = 1 + 5 + 8;
524     Byte buf[kBufSize];
525     const UInt32 headerSize = GetHeaderSize();
526     UInt32 processed;
527     RINOK(decoder.ReadInput(buf, headerSize, &processed));
528     if (processed != headerSize)
529     {
530       if (processed != 0)
531         dataAfterEnd = true;
532       break;
533     }
534 
535     CHeader st;
536     if (!st.Parse(buf, _lzma86))
537     {
538       dataAfterEnd = true;
539       break;
540     }
541     numStreams++;
542     firstItem = false;
543 
544     result = decoder.Code(st, outStream, progress);
545 
546     packSize = decoder.GetInputProcessedSize();
547     unpackSize = outStreamSpec->GetSize();
548 
549     if (result == E_NOTIMPL)
550     {
551       _unsupported = true;
552       result = S_FALSE;
553       break;
554     }
555     if (result == S_FALSE)
556       break;
557     RINOK(result);
558   }
559 
560   if (firstItem)
561   {
562     _isArc = false;
563     result = S_FALSE;
564   }
565   else if (result == S_OK || result == S_FALSE)
566   {
567     if (dataAfterEnd)
568       _dataAfterEnd = true;
569     else if (decoder._lzmaDecoderSpec->NeedsMoreInput())
570       _needMoreInput = true;
571 
572     _packSize = packSize;
573     _unpackSize = unpackSize;
574     _numStreams = numStreams;
575 
576     _packSize_Defined = true;
577     _unpackSize_Defined = true;
578     _numStreams_Defined = true;
579   }
580 
581   Int32 opResult = NExtract::NOperationResult::kOK;
582 
583   if (!_isArc)
584     opResult = NExtract::NOperationResult::kIsNotArc;
585   else if (_needMoreInput)
586     opResult = NExtract::NOperationResult::kUnexpectedEnd;
587   else if (_unsupported)
588     opResult = NExtract::NOperationResult::kUnsupportedMethod;
589   else if (_dataAfterEnd)
590     opResult = NExtract::NOperationResult::kDataAfterEnd;
591   else if (result == S_FALSE)
592     opResult = NExtract::NOperationResult::kDataError;
593   else if (result == S_OK)
594     opResult = NExtract::NOperationResult::kOK;
595   else
596     return result;
597 
598   outStream.Release();
599   return extractCallback->SetOperationResult(opResult);
600 
601   COM_TRY_END
602 }
603 
604 namespace NLzmaAr {
605 
606 // 2, { 0x5D, 0x00 },
607 
608 REGISTER_ARC_I_CLS_NO_SIG(
609   CHandler(false),
610   "lzma", "lzma", 0, 0xA,
611   0,
612   NArcInfoFlags::kStartOpen |
613   NArcInfoFlags::kKeepName,
614   IsArc_Lzma)
615 
616 }
617 
618 namespace NLzma86Ar {
619 
620 REGISTER_ARC_I_CLS_NO_SIG(
621   CHandler(true),
622   "lzma86", "lzma86", 0, 0xB,
623   0,
624   NArcInfoFlags::kKeepName,
625   IsArc_Lzma86)
626 
627 }
628 
629 }}
630