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       HRESULT result = S_OK;
156       if (_stream)
157         result = _stream->Write(data, cur, &cur);
158       if (_calcCrc)
159         _crc = CrcUpdate(_crc, data, cur);
160       if (processedSize)
161         *processedSize += cur;
162       data = (const Byte *)data + cur;
163       size -= cur;
164       _rem -= cur;
165       if (_rem == 0)
166       {
167         RINOK(CloseFile());
168         RINOK(ProcessEmptyFiles());
169       }
170       RINOK(result);
171       if (cur == 0)
172         break;
173       continue;
174     }
175 
176     RINOK(ProcessEmptyFiles());
177     if (_numFiles == 0)
178     {
179       // we support partial extracting
180       /*
181       if (processedSize)
182         *processedSize += size;
183       break;
184       */
185       ExtraWriteWasCut = true;
186       // return S_FALSE;
187       return k_My_HRESULT_WritingWasCut;
188     }
189     RINOK(OpenFile());
190   }
191 
192   return S_OK;
193 }
194 
FlushCorrupted(Int32 callbackOperationResult)195 HRESULT CFolderOutStream::FlushCorrupted(Int32 callbackOperationResult)
196 {
197   while (_numFiles != 0)
198   {
199     if (_fileIsOpen)
200     {
201       RINOK(CloseFile_and_SetResult(callbackOperationResult));
202     }
203     else
204     {
205       RINOK(OpenFile(true));
206     }
207   }
208   return S_OK;
209 }
210 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testModeSpec,IArchiveExtractCallback * extractCallbackSpec)211 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
212     Int32 testModeSpec, IArchiveExtractCallback *extractCallbackSpec)
213 {
214   COM_TRY_BEGIN
215 
216   CMyComPtr<IArchiveExtractCallback> extractCallback = extractCallbackSpec;
217 
218   UInt64 importantTotalUnpacked = 0;
219 
220   // numItems = (UInt32)(Int32)-1;
221 
222   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
223   if (allFilesMode)
224     numItems = _db.Files.Size();
225 
226   if (numItems == 0)
227     return S_OK;
228 
229   {
230     CNum prevFolder = kNumNoIndex;
231     UInt32 nextFile = 0;
232 
233     UInt32 i;
234 
235     for (i = 0; i < numItems; i++)
236     {
237       UInt32 fileIndex = allFilesMode ? i : indices[i];
238       CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex];
239       if (folderIndex == kNumNoIndex)
240         continue;
241       if (folderIndex != prevFolder || fileIndex < nextFile)
242         nextFile = _db.FolderStartFileIndex[folderIndex];
243       for (CNum index = nextFile; index <= fileIndex; index++)
244         importantTotalUnpacked += _db.Files[index].Size;
245       nextFile = fileIndex + 1;
246       prevFolder = folderIndex;
247     }
248   }
249 
250   RINOK(extractCallback->SetTotal(importantTotalUnpacked));
251 
252   CLocalProgress *lps = new CLocalProgress;
253   CMyComPtr<ICompressProgressInfo> progress = lps;
254   lps->Init(extractCallback, false);
255 
256   CDecoder decoder(
257     #if !defined(USE_MIXER_MT)
258       false
259     #elif !defined(USE_MIXER_ST)
260       true
261     #elif !defined(__7Z_SET_PROPERTIES)
262       #ifdef _7ZIP_ST
263         false
264       #else
265         true
266       #endif
267     #else
268       _useMultiThreadMixer
269     #endif
270     );
271 
272   UInt64 curPacked, curUnpacked;
273 
274   CMyComPtr<IArchiveExtractCallbackMessage> callbackMessage;
275   extractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage);
276 
277   CFolderOutStream *folderOutStream = new CFolderOutStream;
278   CMyComPtr<ISequentialOutStream> outStream(folderOutStream);
279 
280   folderOutStream->_db = &_db;
281   folderOutStream->ExtractCallback = extractCallback;
282   folderOutStream->TestMode = (testModeSpec != 0);
283   folderOutStream->CheckCrc = (_crcSize != 0);
284 
285   for (UInt32 i = 0;; lps->OutSize += curUnpacked, lps->InSize += curPacked)
286   {
287     RINOK(lps->SetCur());
288 
289     if (i >= numItems)
290       break;
291 
292     curUnpacked = 0;
293     curPacked = 0;
294 
295     UInt32 fileIndex = allFilesMode ? i : indices[i];
296     CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex];
297 
298     UInt32 numSolidFiles = 1;
299 
300     if (folderIndex != kNumNoIndex)
301     {
302       curPacked = _db.GetFolderFullPackSize(folderIndex);
303       UInt32 nextFile = fileIndex + 1;
304       fileIndex = _db.FolderStartFileIndex[folderIndex];
305       UInt32 k;
306 
307       for (k = i + 1; k < numItems; k++)
308       {
309         UInt32 fileIndex2 = allFilesMode ? k : indices[k];
310         if (_db.FileIndexToFolderIndexMap[fileIndex2] != folderIndex
311             || fileIndex2 < nextFile)
312           break;
313         nextFile = fileIndex2 + 1;
314       }
315 
316       numSolidFiles = k - i;
317 
318       for (k = fileIndex; k < nextFile; k++)
319         curUnpacked += _db.Files[k].Size;
320     }
321 
322     {
323       HRESULT result = folderOutStream->Init(fileIndex,
324           allFilesMode ? NULL : indices + i,
325           numSolidFiles);
326 
327       i += numSolidFiles;
328 
329       RINOK(result);
330     }
331 
332     // to test solid block with zero unpacked size we disable that code
333     if (folderOutStream->WasWritingFinished())
334       continue;
335 
336     #ifndef _NO_CRYPTO
337     CMyComPtr<ICryptoGetTextPassword> getTextPassword;
338     if (extractCallback)
339       extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword);
340     #endif
341 
342     try
343     {
344       #ifndef _NO_CRYPTO
345         bool isEncrypted = false;
346         bool passwordIsDefined = false;
347         UString password;
348       #endif
349 
350 
351       bool dataAfterEnd_Error = false;
352 
353       HRESULT result = decoder.Decode(
354           EXTERNAL_CODECS_VARS
355           _inStream,
356           _db.ArcInfo.DataStartPosition,
357           _db, folderIndex,
358           &curUnpacked,
359 
360           outStream,
361           progress,
362           NULL // *inStreamMainRes
363           , dataAfterEnd_Error
364 
365           _7Z_DECODER_CRYPRO_VARS
366           #if !defined(_7ZIP_ST) && !defined(_SFX)
367             , true, _numThreads
368           #endif
369           );
370 
371       if (result == S_FALSE || result == E_NOTIMPL || dataAfterEnd_Error)
372       {
373         bool wasFinished = folderOutStream->WasWritingFinished();
374 
375         int resOp = NExtract::NOperationResult::kDataError;
376 
377         if (result != S_FALSE)
378         {
379           if (result == E_NOTIMPL)
380             resOp = NExtract::NOperationResult::kUnsupportedMethod;
381           else if (wasFinished && dataAfterEnd_Error)
382             resOp = NExtract::NOperationResult::kDataAfterEnd;
383         }
384 
385         RINOK(folderOutStream->FlushCorrupted(resOp));
386 
387         if (wasFinished)
388         {
389           // we don't show error, if it's after required files
390           if (/* !folderOutStream->ExtraWriteWasCut && */ callbackMessage)
391           {
392             RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp));
393           }
394         }
395         continue;
396       }
397 
398       if (result != S_OK)
399         return result;
400 
401       RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
402       continue;
403     }
404     catch(...)
405     {
406       RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
407       // continue;
408       return E_FAIL;
409     }
410   }
411 
412   return S_OK;
413 
414   COM_TRY_END
415 }
416 
417 }}
418