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