xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Zip/ZipAddCommon.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // ZipAddCommon.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/7zCrc.h"
6 #include "../../../../C/Alloc.h"
7 
8 #include "../../../Windows/PropVariant.h"
9 
10 #include "../../ICoder.h"
11 #include "../../IPassword.h"
12 #include "../../MyVersion.h"
13 
14 #include "../../Common/CreateCoder.h"
15 #include "../../Common/StreamObjects.h"
16 #include "../../Common/StreamUtils.h"
17 
18 #include "../../Compress/LzmaEncoder.h"
19 #include "../../Compress/PpmdZip.h"
20 #include "../../Compress/XzEncoder.h"
21 
22 #include "../Common/InStreamWithCRC.h"
23 
24 #include "ZipAddCommon.h"
25 #include "ZipHeader.h"
26 
27 namespace NArchive {
28 namespace NZip {
29 
30 using namespace NFileHeader;
31 
32 
33 static const unsigned kLzmaPropsSize = 5;
34 static const unsigned kLzmaHeaderSize = 4 + kLzmaPropsSize;
35 
36 Z7_CLASS_IMP_NOQIB_3(
37   CLzmaEncoder
38   , ICompressCoder
39   , ICompressSetCoderProperties
40   , ICompressSetCoderPropertiesOpt
41 )
42 public:
43   CMyComPtr2<ICompressCoder, NCompress::NLzma::CEncoder> Encoder;
44   Byte Header[kLzmaHeaderSize];
45 };
46 
47 Z7_COM7F_IMF(CLzmaEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps))
48 {
49   Encoder.Create_if_Empty();
50   CMyComPtr2_Create<ISequentialOutStream, CBufPtrSeqOutStream> outStream;
51   outStream->Init(Header + 4, kLzmaPropsSize);
52   RINOK(Encoder->SetCoderProperties(propIDs, props, numProps))
53   RINOK(Encoder->WriteCoderProperties(outStream))
54   if (outStream->GetPos() != kLzmaPropsSize)
55     return E_FAIL;
56   Header[0] = MY_VER_MAJOR;
57   Header[1] = MY_VER_MINOR;
58   Header[2] = kLzmaPropsSize;
59   Header[3] = 0;
60   return S_OK;
61 }
62 
63 Z7_COM7F_IMF(CLzmaEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps))
64 {
65   return Encoder->SetCoderPropertiesOpt(propIDs, props, numProps);
66 }
67 
68 Z7_COM7F_IMF(CLzmaEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
69     const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress))
70 {
71   RINOK(WriteStream(outStream, Header, kLzmaHeaderSize))
72   return Encoder.Interface()->Code(inStream, outStream, inSize, outSize, progress);
73 }
74 
75 
76 CAddCommon::CAddCommon():
77     _isLzmaEos(false),
78     _buf(NULL)
79     {}
80 
81 void CAddCommon::SetOptions(const CCompressionMethodMode &options)
82 {
83   _options = options;
84 }
85 
86 CAddCommon::~CAddCommon()
87 {
88   MidFree(_buf);
89 }
90 
91 static const UInt32 kBufSize = ((UInt32)1 << 16);
92 
93 HRESULT CAddCommon::CalcStreamCRC(ISequentialInStream *inStream, UInt32 &resultCRC)
94 {
95   if (!_buf)
96   {
97     _buf = (Byte *)MidAlloc(kBufSize);
98     if (!_buf)
99       return E_OUTOFMEMORY;
100   }
101 
102   UInt32 crc = CRC_INIT_VAL;
103   for (;;)
104   {
105     UInt32 processed;
106     RINOK(inStream->Read(_buf, kBufSize, &processed))
107     if (processed == 0)
108     {
109       resultCRC = CRC_GET_DIGEST(crc);
110       return S_OK;
111     }
112     crc = CrcUpdate(crc, _buf, (size_t)processed);
113   }
114 }
115 
116 
117 HRESULT CAddCommon::Set_Pre_CompressionResult(bool inSeqMode, bool outSeqMode, UInt64 unpackSize,
118     CCompressingResult &opRes) const
119 {
120   // We use Zip64, if unPackSize size is larger than 0xF8000000 to support
121   // cases when compressed size can be about 3% larger than uncompressed size
122 
123   const UInt32 kUnpackZip64Limit = 0xF8000000;
124 
125   opRes.UnpackSize = unpackSize;
126   opRes.PackSize = (UInt64)1 << 60; // we use big value to force Zip64 mode.
127 
128   if (unpackSize < kUnpackZip64Limit)
129     opRes.PackSize = (UInt32)0xFFFFFFFF - 1; // it will not use Zip64 for that size
130 
131   if (opRes.PackSize < unpackSize)
132     opRes.PackSize = unpackSize;
133 
134   const Byte method = _options.MethodSequence[0];
135 
136   if (method == NCompressionMethod::kStore && !_options.Password_Defined)
137     opRes.PackSize = unpackSize;
138 
139   opRes.CRC = 0;
140 
141   opRes.LzmaEos = false;
142 
143   opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default;
144   opRes.DescriptorMode = outSeqMode;
145 
146   if (_options.Password_Defined)
147   {
148     opRes.ExtractVersion = NCompressionMethod::kExtractVersion_ZipCrypto;
149     if (_options.IsAesMode)
150       opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Aes;
151     else
152     {
153       if (inSeqMode)
154         opRes.DescriptorMode = true;
155     }
156   }
157 
158   opRes.Method = method;
159   Byte ver = 0;
160 
161   switch (method)
162   {
163     case NCompressionMethod::kStore: break;
164     case NCompressionMethod::kDeflate: ver = NCompressionMethod::kExtractVersion_Deflate; break;
165     case NCompressionMethod::kDeflate64: ver = NCompressionMethod::kExtractVersion_Deflate64; break;
166     case NCompressionMethod::kXz   : ver = NCompressionMethod::kExtractVersion_Xz; break;
167     case NCompressionMethod::kPPMd : ver = NCompressionMethod::kExtractVersion_PPMd; break;
168     case NCompressionMethod::kBZip2: ver = NCompressionMethod::kExtractVersion_BZip2; break;
169     case NCompressionMethod::kLZMA :
170     {
171       ver = NCompressionMethod::kExtractVersion_LZMA;
172       const COneMethodInfo *oneMethodMain = &_options._methods[0];
173       opRes.LzmaEos = oneMethodMain->Get_Lzma_Eos();
174       break;
175     }
176     default: break;
177   }
178   if (opRes.ExtractVersion < ver)
179       opRes.ExtractVersion = ver;
180 
181   return S_OK;
182 }
183 
184 
185 HRESULT CAddCommon::Compress(
186     DECL_EXTERNAL_CODECS_LOC_VARS
187     ISequentialInStream *inStream, IOutStream *outStream,
188     bool inSeqMode, bool outSeqMode,
189     UInt32 fileTime,
190     UInt64 expectedDataSize, bool expectedDataSize_IsConfirmed,
191     ICompressProgressInfo *progress, CCompressingResult &opRes)
192 {
193   // opRes.LzmaEos = false;
194 
195   if (!inStream)
196   {
197     // We can create empty stream here. But it was already implemented in caller code in 9.33+
198     return E_INVALIDARG;
199   }
200 
201   CMyComPtr2_Create<ISequentialInStream, CSequentialInStreamWithCRC> inCrcStream;
202 
203   CMyComPtr<IInStream> inStream2;
204   if (!inSeqMode)
205   {
206     inStream->QueryInterface(IID_IInStream, (void **)&inStream2);
207     if (!inStream2)
208     {
209       // inSeqMode = true;
210       // inSeqMode must be correct before
211       return E_FAIL;
212     }
213   }
214 
215   inCrcStream->SetStream(inStream);
216   inCrcStream->SetFullSize(expectedDataSize_IsConfirmed ? expectedDataSize : (UInt64)(Int64)-1);
217   // inCrcStream->Init();
218 
219   unsigned numTestMethods = _options.MethodSequence.Size();
220   // numTestMethods != 0
221 
222   bool descriptorMode = outSeqMode;
223 
224   // ZipCrypto without descriptor requires additional reading pass for
225   // inStream to calculate CRC for password check field.
226   // The descriptor allows to use ZipCrypto check field without CRC (InfoZip's modification).
227 
228   if (!outSeqMode)
229     if (inSeqMode && _options.Password_Defined && !_options.IsAesMode)
230       descriptorMode = true;
231   opRes.DescriptorMode = descriptorMode;
232 
233   if (numTestMethods > 1)
234     if (inSeqMode || outSeqMode || !inStream2)
235       numTestMethods = 1;
236 
237   UInt32 crc = 0;
238   bool crc_IsCalculated = false;
239 
240   CFilterCoder::C_OutStream_Releaser outStreamReleaser;
241   // opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default;
242 
243   for (unsigned i = 0; i < numTestMethods; i++)
244   {
245     inCrcStream->Init();
246 
247     if (i != 0)
248     {
249       // if (inStream2)
250       {
251         RINOK(InStream_SeekToBegin(inStream2))
252       }
253       RINOK(outStream->Seek(0, STREAM_SEEK_SET, NULL))
254       RINOK(outStream->SetSize(0))
255     }
256 
257     opRes.LzmaEos = false;
258     opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default;
259 
260     const Byte method = _options.MethodSequence[i];
261     if (method == NCompressionMethod::kStore && descriptorMode)
262     {
263       // we still can create descriptor_mode archives with "Store" method, but they are not good for 100%
264       return E_NOTIMPL;
265     }
266 
267     bool needCode = true;
268 
269     if (_options.Password_Defined)
270     {
271       opRes.ExtractVersion = NCompressionMethod::kExtractVersion_ZipCrypto;
272 
273       if (!_cryptoStream.IsDefined())
274         _cryptoStream.SetFromCls(new CFilterCoder(true));
275 
276       if (_options.IsAesMode)
277       {
278         opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Aes;
279         if (!_cryptoStream->Filter)
280         {
281           _cryptoStream->Filter = _filterAesSpec = new NCrypto::NWzAes::CEncoder;
282           _filterAesSpec->SetKeyMode(_options.AesKeyMode);
283           RINOK(_filterAesSpec->CryptoSetPassword((const Byte *)(const char *)_options.Password, _options.Password.Len()))
284         }
285         RINOK(_filterAesSpec->WriteHeader(outStream))
286       }
287       else
288       {
289         if (!_cryptoStream->Filter)
290         {
291           _cryptoStream->Filter = _filterSpec = new NCrypto::NZip::CEncoder;
292           _filterSpec->CryptoSetPassword((const Byte *)(const char *)_options.Password, _options.Password.Len());
293         }
294 
295         UInt32 check;
296 
297         if (descriptorMode)
298         {
299           // it's Info-ZIP modification for stream_mode descriptor_mode (bit 3 of the general purpose bit flag is set)
300           check = (fileTime & 0xFFFF);
301         }
302         else
303         {
304           if (!crc_IsCalculated)
305           {
306             RINOK(CalcStreamCRC(inStream, crc))
307             crc_IsCalculated = true;
308             RINOK(InStream_SeekToBegin(inStream2))
309             inCrcStream->Init();
310           }
311           check = (crc >> 16);
312         }
313 
314         RINOK(_filterSpec->WriteHeader_Check16(outStream, (UInt16)check))
315       }
316 
317       if (method == NCompressionMethod::kStore)
318       {
319         needCode = false;
320         RINOK(_cryptoStream->Code(inCrcStream, outStream, NULL, NULL, progress))
321       }
322       else
323       {
324         RINOK(_cryptoStream->SetOutStream(outStream))
325         RINOK(_cryptoStream->InitEncoder())
326         outStreamReleaser.FilterCoder = _cryptoStream.ClsPtr();
327       }
328     }
329 
330     if (needCode)
331     {
332       switch (method)
333       {
334       case NCompressionMethod::kStore:
335       {
336         _copyCoder.Create_if_Empty();
337         CMyComPtr<ISequentialOutStream> outStreamNew;
338         if (_options.Password_Defined)
339           outStreamNew = _cryptoStream;
340         else
341           outStreamNew = outStream;
342         RINOK(_copyCoder.Interface()->Code(inCrcStream, outStreamNew, NULL, NULL, progress))
343         break;
344       }
345 
346       default:
347       {
348         if (!_compressEncoder)
349         {
350           CLzmaEncoder *_lzmaEncoder = NULL;
351           if (method == NCompressionMethod::kLZMA)
352           {
353             _compressExtractVersion = NCompressionMethod::kExtractVersion_LZMA;
354             _lzmaEncoder = new CLzmaEncoder();
355             _compressEncoder = _lzmaEncoder;
356           }
357           else if (method == NCompressionMethod::kXz)
358           {
359             _compressExtractVersion = NCompressionMethod::kExtractVersion_Xz;
360             NCompress::NXz::CEncoder *encoder = new NCompress::NXz::CEncoder();
361             _compressEncoder = encoder;
362           }
363           else if (method == NCompressionMethod::kPPMd)
364           {
365             _compressExtractVersion = NCompressionMethod::kExtractVersion_PPMd;
366             NCompress::NPpmdZip::CEncoder *encoder = new NCompress::NPpmdZip::CEncoder();
367             _compressEncoder = encoder;
368           }
369           else
370           {
371           CMethodId methodId;
372           switch (method)
373           {
374             case NCompressionMethod::kBZip2:
375               methodId = kMethodId_BZip2;
376               _compressExtractVersion = NCompressionMethod::kExtractVersion_BZip2;
377               break;
378             default:
379               _compressExtractVersion = ((method == NCompressionMethod::kDeflate64) ?
380                   NCompressionMethod::kExtractVersion_Deflate64 :
381                   NCompressionMethod::kExtractVersion_Deflate);
382               methodId = kMethodId_ZipBase + method;
383               break;
384           }
385           RINOK(CreateCoder_Id(
386               EXTERNAL_CODECS_LOC_VARS
387               methodId, true, _compressEncoder))
388           if (!_compressEncoder)
389             return E_NOTIMPL;
390 
391           if (method == NCompressionMethod::kDeflate ||
392               method == NCompressionMethod::kDeflate64)
393           {
394           }
395           else if (method == NCompressionMethod::kBZip2)
396           {
397           }
398           }
399           {
400             CMyComPtr<ICompressSetCoderProperties> setCoderProps;
401             _compressEncoder.QueryInterface(IID_ICompressSetCoderProperties, &setCoderProps);
402             if (setCoderProps)
403             {
404               if (!_options._methods.IsEmpty())
405               {
406                 COneMethodInfo *oneMethodMain = &_options._methods[0];
407 
408                 RINOK(oneMethodMain->SetCoderProps(setCoderProps,
409                     _options.DataSizeReduce_Defined ? &_options.DataSizeReduce : NULL))
410               }
411             }
412           }
413           if (method == NCompressionMethod::kLZMA)
414             _isLzmaEos = _lzmaEncoder->Encoder->IsWriteEndMark();
415         }
416 
417         if (method == NCompressionMethod::kLZMA)
418           opRes.LzmaEos = _isLzmaEos;
419 
420         CMyComPtr<ISequentialOutStream> outStreamNew;
421         if (_options.Password_Defined)
422           outStreamNew = _cryptoStream;
423         else
424           outStreamNew = outStream;
425         if (_compressExtractVersion > opRes.ExtractVersion)
426           opRes.ExtractVersion = _compressExtractVersion;
427 
428         {
429           CMyComPtr<ICompressSetCoderPropertiesOpt> optProps;
430           _compressEncoder->QueryInterface(IID_ICompressSetCoderPropertiesOpt, (void **)&optProps);
431           if (optProps)
432           {
433             const PROPID propID = NCoderPropID::kExpectedDataSize;
434             NWindows::NCOM::CPropVariant prop = (UInt64)expectedDataSize;
435             RINOK(optProps->SetCoderPropertiesOpt(&propID, &prop, 1))
436           }
437         }
438 
439         try {
440         RINOK(_compressEncoder->Code(inCrcStream, outStreamNew, NULL, NULL, progress))
441         } catch (...) { return E_FAIL; }
442         break;
443       }
444       } // switch end
445 
446       if (_options.Password_Defined)
447       {
448         RINOK(_cryptoStream->OutStreamFinish())
449       }
450     }
451 
452     if (_options.Password_Defined)
453     {
454       if (_options.IsAesMode)
455       {
456         RINOK(_filterAesSpec->WriteFooter(outStream))
457       }
458     }
459 
460     RINOK(outStream->Seek(0, STREAM_SEEK_CUR, &opRes.PackSize))
461 
462     {
463       opRes.CRC = inCrcStream->GetCRC();
464       opRes.UnpackSize = inCrcStream->GetSize();
465       opRes.Method = method;
466     }
467 
468     if (!inCrcStream->WasFinished())
469       return E_FAIL;
470 
471     if (_options.Password_Defined)
472     {
473       if (opRes.PackSize < opRes.UnpackSize +
474           (_options.IsAesMode ? _filterAesSpec->GetAddPackSize() : NCrypto::NZip::kHeaderSize))
475         break;
476     }
477     else if (opRes.PackSize < opRes.UnpackSize)
478       break;
479   }
480 
481   return S_OK;
482 }
483 
484 }}
485