1 // 7zExtract.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/7zCrc.h"
6 
7 #include "../../../Common/ComTry.h"
8 
9 #include "../../Common/ProgressUtils.h"
10 
11 #include "7zDecode.h"
12 #include "7zHandler.h"
13 
14 // EXTERN_g_ExternalCodecs
15 
16 namespace NArchive {
17 namespace N7z {
18 
19 class CFolderOutStream:
20   public ISequentialOutStream,
21   public CMyUnknownImp
22 {
23   CMyComPtr<ISequentialOutStream> _stream;
24 public:
25   bool TestMode;
26   bool CheckCrc;
27 private:
28   bool _fileIsOpen;
29   bool _calcCrc;
30   UInt32 _crc;
31   UInt64 _rem;
32 
33   const UInt32 *_indexes;
34   unsigned _numFiles;
35   unsigned _fileIndex;
36 
37   HRESULT OpenFile(bool isCorrupted = false);
38   HRESULT CloseFile_and_SetResult(Int32 res);
39   HRESULT CloseFile();
40   HRESULT ProcessEmptyFiles();
41 
42 public:
43   MY_UNKNOWN_IMP1(ISequentialOutStream)
44 
45   const CDbEx *_db;
46   CMyComPtr<IArchiveExtractCallback> ExtractCallback;
47 
48   bool ExtraWriteWasCut;
49 
CFolderOutStream()50   CFolderOutStream():
51       TestMode(false),
52       CheckCrc(true)
53       {}
54 
55   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
56 
57   HRESULT Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles);
58   HRESULT FlushCorrupted(Int32 callbackOperationResult);
59 
WasWritingFinished() const60   bool WasWritingFinished() const { return _numFiles == 0; }
61 };
62 
63 
Init(unsigned startIndex,const UInt32 * indexes,unsigned numFiles)64 HRESULT CFolderOutStream::Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles)
65 {
66   _fileIndex = startIndex;
67   _indexes = indexes;
68   _numFiles = numFiles;
69 
70   _fileIsOpen = false;
71   ExtraWriteWasCut = false;
72 
73   return ProcessEmptyFiles();
74 }
75 
OpenFile(bool isCorrupted)76 HRESULT CFolderOutStream::OpenFile(bool isCorrupted)
77 {
78   const CFileItem &fi = _db->Files[_fileIndex];
79   UInt32 nextFileIndex = (_indexes ? *_indexes : _fileIndex);
80   Int32 askMode = (_fileIndex == nextFileIndex) ?
81         (TestMode ?
82         NExtract::NAskMode::kTest :
83         NExtract::NAskMode::kExtract) :
84       NExtract::NAskMode::kSkip;
85 
86   if (isCorrupted
87       && askMode == NExtract::NAskMode::kExtract
88       && !_db->IsItemAnti(_fileIndex)
89       && !fi.IsDir)
90     askMode = NExtract::NAskMode::kTest;
91 
92   CMyComPtr<ISequentialOutStream> realOutStream;
93   RINOK(ExtractCallback->GetStream(_fileIndex, &realOutStream, askMode));
94 
95   _stream = realOutStream;
96   _crc = CRC_INIT_VAL;
97   _calcCrc = (CheckCrc && fi.CrcDefined && !fi.IsDir);
98 
99   _fileIsOpen = true;
100   _rem = fi.Size;
101 
102   if (askMode == NExtract::NAskMode::kExtract
103       && !realOutStream
104       && !_db->IsItemAnti(_fileIndex)
105       && !fi.IsDir)
106     askMode = NExtract::NAskMode::kSkip;
107   return ExtractCallback->PrepareOperation(askMode);
108 }
109 
CloseFile_and_SetResult(Int32 res)110 HRESULT CFolderOutStream::CloseFile_and_SetResult(Int32 res)
111 {
112   _stream.Release();
113   _fileIsOpen = false;
114 
115   if (!_indexes)
116     _numFiles--;
117   else if (*_indexes == _fileIndex)
118   {
119     _indexes++;
120     _numFiles--;
121   }
122 
123   _fileIndex++;
124   return ExtractCallback->SetOperationResult(res);
125 }
126 
CloseFile()127 HRESULT CFolderOutStream::CloseFile()
128 {
129   const CFileItem &fi = _db->Files[_fileIndex];
130   return CloseFile_and_SetResult((!_calcCrc || fi.Crc == CRC_GET_DIGEST(_crc)) ?
131       NExtract::NOperationResult::kOK :
132       NExtract::NOperationResult::kCRCError);
133 }
134 
ProcessEmptyFiles()135 HRESULT CFolderOutStream::ProcessEmptyFiles()
136 {
137   while (_numFiles != 0 && _db->Files[_fileIndex].Size == 0)
138   {
139     RINOK(OpenFile());
140     RINOK(CloseFile());
141   }
142   return S_OK;
143 }
144 
Write(const void * data,UInt32 size,UInt32 * processedSize)145 STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
146 {
147   if (processedSize)
148     *processedSize = 0;
149 
150   while (size != 0)
151   {
152     if (_fileIsOpen)
153     {
154       UInt32 cur = (size < _rem ? size : (UInt32)_rem);
155       if (_calcCrc)
156       {
157         const UInt32 k_Step = (UInt32)1 << 20;
158         if (cur > k_Step)
159           cur = k_Step;
160       }
161       HRESULT result = S_OK;
162       if (_stream)
163         result = _stream->Write(data, cur, &cur);
164       if (_calcCrc)
165         _crc = CrcUpdate(_crc, data, cur);
166       if (processedSize)
167         *processedSize += cur;
168       data = (const Byte *)data + cur;
169       size -= cur;
170       _rem -= cur;
171       if (_rem == 0)
172       {
173         RINOK(CloseFile());
174         RINOK(ProcessEmptyFiles());
175       }
176       RINOK(result);
177       if (cur == 0)
178         break;
179       continue;
180     }
181 
182     RINOK(ProcessEmptyFiles());
183     if (_numFiles == 0)
184     {
185       // we support partial extracting
186       /*
187       if (processedSize)
188         *processedSize += size;
189       break;
190       */
191       ExtraWriteWasCut = true;
192       // return S_FALSE;
193       return k_My_HRESULT_WritingWasCut;
194     }
195     RINOK(OpenFile());
196   }
197 
198   return S_OK;
199 }
200 
FlushCorrupted(Int32 callbackOperationResult)201 HRESULT CFolderOutStream::FlushCorrupted(Int32 callbackOperationResult)
202 {
203   while (_numFiles != 0)
204   {
205     if (_fileIsOpen)
206     {
207       RINOK(CloseFile_and_SetResult(callbackOperationResult));
208     }
209     else
210     {
211       RINOK(OpenFile(true));
212     }
213   }
214   return S_OK;
215 }
216 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testModeSpec,IArchiveExtractCallback * extractCallbackSpec)217 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
218     Int32 testModeSpec, IArchiveExtractCallback *extractCallbackSpec)
219 {
220   COM_TRY_BEGIN
221 
222   CMyComPtr<IArchiveExtractCallback> extractCallback = extractCallbackSpec;
223 
224   UInt64 importantTotalUnpacked = 0;
225 
226   // numItems = (UInt32)(Int32)-1;
227 
228   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
229   if (allFilesMode)
230     numItems = _db.Files.Size();
231 
232   if (numItems == 0)
233     return S_OK;
234 
235   {
236     CNum prevFolder = kNumNoIndex;
237     UInt32 nextFile = 0;
238 
239     UInt32 i;
240 
241     for (i = 0; i < numItems; i++)
242     {
243       UInt32 fileIndex = allFilesMode ? i : indices[i];
244       CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex];
245       if (folderIndex == kNumNoIndex)
246         continue;
247       if (folderIndex != prevFolder || fileIndex < nextFile)
248         nextFile = _db.FolderStartFileIndex[folderIndex];
249       for (CNum index = nextFile; index <= fileIndex; index++)
250         importantTotalUnpacked += _db.Files[index].Size;
251       nextFile = fileIndex + 1;
252       prevFolder = folderIndex;
253     }
254   }
255 
256   RINOK(extractCallback->SetTotal(importantTotalUnpacked));
257 
258   CLocalProgress *lps = new CLocalProgress;
259   CMyComPtr<ICompressProgressInfo> progress = lps;
260   lps->Init(extractCallback, false);
261 
262   CDecoder decoder(
263     #if !defined(USE_MIXER_MT)
264       false
265     #elif !defined(USE_MIXER_ST)
266       true
267     #elif !defined(__7Z_SET_PROPERTIES)
268       #ifdef _7ZIP_ST
269         false
270       #else
271         true
272       #endif
273     #else
274       _useMultiThreadMixer
275     #endif
276     );
277 
278   UInt64 curPacked, curUnpacked;
279 
280   CMyComPtr<IArchiveExtractCallbackMessage> callbackMessage;
281   extractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage);
282 
283   CFolderOutStream *folderOutStream = new CFolderOutStream;
284   CMyComPtr<ISequentialOutStream> outStream(folderOutStream);
285 
286   folderOutStream->_db = &_db;
287   folderOutStream->ExtractCallback = extractCallback;
288   folderOutStream->TestMode = (testModeSpec != 0);
289   folderOutStream->CheckCrc = (_crcSize != 0);
290 
291   for (UInt32 i = 0;; lps->OutSize += curUnpacked, lps->InSize += curPacked)
292   {
293     RINOK(lps->SetCur());
294 
295     if (i >= numItems)
296       break;
297 
298     curUnpacked = 0;
299     curPacked = 0;
300 
301     UInt32 fileIndex = allFilesMode ? i : indices[i];
302     CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex];
303 
304     UInt32 numSolidFiles = 1;
305 
306     if (folderIndex != kNumNoIndex)
307     {
308       curPacked = _db.GetFolderFullPackSize(folderIndex);
309       UInt32 nextFile = fileIndex + 1;
310       fileIndex = _db.FolderStartFileIndex[folderIndex];
311       UInt32 k;
312 
313       for (k = i + 1; k < numItems; k++)
314       {
315         UInt32 fileIndex2 = allFilesMode ? k : indices[k];
316         if (_db.FileIndexToFolderIndexMap[fileIndex2] != folderIndex
317             || fileIndex2 < nextFile)
318           break;
319         nextFile = fileIndex2 + 1;
320       }
321 
322       numSolidFiles = k - i;
323 
324       for (k = fileIndex; k < nextFile; k++)
325         curUnpacked += _db.Files[k].Size;
326     }
327 
328     {
329       HRESULT result = folderOutStream->Init(fileIndex,
330           allFilesMode ? NULL : indices + i,
331           numSolidFiles);
332 
333       i += numSolidFiles;
334 
335       RINOK(result);
336     }
337 
338     // to test solid block with zero unpacked size we disable that code
339     if (folderOutStream->WasWritingFinished())
340       continue;
341 
342     #ifndef _NO_CRYPTO
343     CMyComPtr<ICryptoGetTextPassword> getTextPassword;
344     if (extractCallback)
345       extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword);
346     #endif
347 
348     try
349     {
350       #ifndef _NO_CRYPTO
351         bool isEncrypted = false;
352         bool passwordIsDefined = false;
353         UString password;
354       #endif
355 
356 
357       bool dataAfterEnd_Error = false;
358 
359       HRESULT result = decoder.Decode(
360           EXTERNAL_CODECS_VARS
361           _inStream,
362           _db.ArcInfo.DataStartPosition,
363           _db, folderIndex,
364           &curUnpacked,
365 
366           outStream,
367           progress,
368           NULL // *inStreamMainRes
369           , dataAfterEnd_Error
370 
371           _7Z_DECODER_CRYPRO_VARS
372           #if !defined(_7ZIP_ST)
373             , true, _numThreads, _memUsage
374           #endif
375           );
376 
377       if (result == S_FALSE || result == E_NOTIMPL || dataAfterEnd_Error)
378       {
379         bool wasFinished = folderOutStream->WasWritingFinished();
380 
381         int resOp = NExtract::NOperationResult::kDataError;
382 
383         if (result != S_FALSE)
384         {
385           if (result == E_NOTIMPL)
386             resOp = NExtract::NOperationResult::kUnsupportedMethod;
387           else if (wasFinished && dataAfterEnd_Error)
388             resOp = NExtract::NOperationResult::kDataAfterEnd;
389         }
390 
391         RINOK(folderOutStream->FlushCorrupted(resOp));
392 
393         if (wasFinished)
394         {
395           // we don't show error, if it's after required files
396           if (/* !folderOutStream->ExtraWriteWasCut && */ callbackMessage)
397           {
398             RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp));
399           }
400         }
401         continue;
402       }
403 
404       if (result != S_OK)
405         return result;
406 
407       RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
408       continue;
409     }
410     catch(...)
411     {
412       RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
413       // continue;
414       return E_FAIL;
415     }
416   }
417 
418   return S_OK;
419 
420   COM_TRY_END
421 }
422 
423 }}
424