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
16namespace NArchive {
17namespace N7z {
18
19class CFolderOutStream:
20  public ISequentialOutStream,
21  public CMyUnknownImp
22{
23  CMyComPtr<ISequentialOutStream> _stream;
24public:
25  bool TestMode;
26  bool CheckCrc;
27private:
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
42public:
43  MY_UNKNOWN_IMP1(ISequentialOutStream)
44
45  const CDbEx *_db;
46  CMyComPtr<IArchiveExtractCallback> ExtractCallback;
47
48  bool ExtraWriteWasCut;
49
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
60  bool WasWritingFinished() const { return _numFiles == 0; }
61};
62
63
64HRESULT 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
76HRESULT 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
110HRESULT 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
127HRESULT 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
135HRESULT 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
145STDMETHODIMP 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
195HRESULT 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
211STDMETHODIMP 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      HRESULT result = decoder.Decode(
352          EXTERNAL_CODECS_VARS
353          _inStream,
354          _db.ArcInfo.DataStartPosition,
355          _db, folderIndex,
356          &curUnpacked,
357
358          outStream,
359          progress,
360          NULL // *inStreamMainRes
361
362          _7Z_DECODER_CRYPRO_VARS
363          #if !defined(_7ZIP_ST) && !defined(_SFX)
364            , true, _numThreads
365          #endif
366          );
367
368      if (result == S_FALSE || result == E_NOTIMPL)
369      {
370        bool wasFinished = folderOutStream->WasWritingFinished();
371
372        int resOp = (result == S_FALSE ?
373            NExtract::NOperationResult::kDataError :
374            NExtract::NOperationResult::kUnsupportedMethod);
375
376        RINOK(folderOutStream->FlushCorrupted(resOp));
377
378        if (wasFinished)
379        {
380          // we don't show error, if it's after required files
381          if (/* !folderOutStream->ExtraWriteWasCut && */ callbackMessage)
382          {
383            RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp));
384          }
385        }
386        continue;
387      }
388
389      if (result != S_OK)
390        return result;
391
392      RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
393      continue;
394    }
395    catch(...)
396    {
397      RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError));
398      // continue;
399      return E_FAIL;
400    }
401  }
402
403  return S_OK;
404
405  COM_TRY_END
406}
407
408}}
409