Home | History | Annotate | Download | only in 7z
      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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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       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