// 7zExtract.cpp #include "StdAfx.h" #include "../../../../C/7zCrc.h" #include "../../../Common/ComTry.h" #include "../../Common/ProgressUtils.h" #include "7zDecode.h" #include "7zHandler.h" // EXTERN_g_ExternalCodecs namespace NArchive { namespace N7z { class CFolderOutStream: public ISequentialOutStream, public CMyUnknownImp { CMyComPtr _stream; public: bool TestMode; bool CheckCrc; private: bool _fileIsOpen; bool _calcCrc; UInt32 _crc; UInt64 _rem; const UInt32 *_indexes; unsigned _numFiles; unsigned _fileIndex; HRESULT OpenFile(bool isCorrupted = false); HRESULT CloseFile_and_SetResult(Int32 res); HRESULT CloseFile(); HRESULT ProcessEmptyFiles(); public: MY_UNKNOWN_IMP1(ISequentialOutStream) const CDbEx *_db; CMyComPtr ExtractCallback; bool ExtraWriteWasCut; CFolderOutStream(): TestMode(false), CheckCrc(true) {} STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); HRESULT Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles); HRESULT FlushCorrupted(Int32 callbackOperationResult); bool WasWritingFinished() const { return _numFiles == 0; } }; HRESULT CFolderOutStream::Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles) { _fileIndex = startIndex; _indexes = indexes; _numFiles = numFiles; _fileIsOpen = false; ExtraWriteWasCut = false; return ProcessEmptyFiles(); } HRESULT CFolderOutStream::OpenFile(bool isCorrupted) { const CFileItem &fi = _db->Files[_fileIndex]; UInt32 nextFileIndex = (_indexes ? *_indexes : _fileIndex); Int32 askMode = (_fileIndex == nextFileIndex) ? (TestMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract) : NExtract::NAskMode::kSkip; if (isCorrupted && askMode == NExtract::NAskMode::kExtract && !_db->IsItemAnti(_fileIndex) && !fi.IsDir) askMode = NExtract::NAskMode::kTest; CMyComPtr realOutStream; RINOK(ExtractCallback->GetStream(_fileIndex, &realOutStream, askMode)); _stream = realOutStream; _crc = CRC_INIT_VAL; _calcCrc = (CheckCrc && fi.CrcDefined && !fi.IsDir); _fileIsOpen = true; _rem = fi.Size; if (askMode == NExtract::NAskMode::kExtract && !realOutStream && !_db->IsItemAnti(_fileIndex) && !fi.IsDir) askMode = NExtract::NAskMode::kSkip; return ExtractCallback->PrepareOperation(askMode); } HRESULT CFolderOutStream::CloseFile_and_SetResult(Int32 res) { _stream.Release(); _fileIsOpen = false; if (!_indexes) _numFiles--; else if (*_indexes == _fileIndex) { _indexes++; _numFiles--; } _fileIndex++; return ExtractCallback->SetOperationResult(res); } HRESULT CFolderOutStream::CloseFile() { const CFileItem &fi = _db->Files[_fileIndex]; return CloseFile_and_SetResult((!_calcCrc || fi.Crc == CRC_GET_DIGEST(_crc)) ? NExtract::NOperationResult::kOK : NExtract::NOperationResult::kCRCError); } HRESULT CFolderOutStream::ProcessEmptyFiles() { while (_numFiles != 0 && _db->Files[_fileIndex].Size == 0) { RINOK(OpenFile()); RINOK(CloseFile()); } return S_OK; } STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; while (size != 0) { if (_fileIsOpen) { UInt32 cur = (size < _rem ? size : (UInt32)_rem); HRESULT result = S_OK; if (_stream) result = _stream->Write(data, cur, &cur); if (_calcCrc) _crc = CrcUpdate(_crc, data, cur); if (processedSize) *processedSize += cur; data = (const Byte *)data + cur; size -= cur; _rem -= cur; if (_rem == 0) { RINOK(CloseFile()); RINOK(ProcessEmptyFiles()); } RINOK(result); if (cur == 0) break; continue; } RINOK(ProcessEmptyFiles()); if (_numFiles == 0) { // we support partial extracting /* if (processedSize) *processedSize += size; break; */ ExtraWriteWasCut = true; // return S_FALSE; return k_My_HRESULT_WritingWasCut; } RINOK(OpenFile()); } return S_OK; } HRESULT CFolderOutStream::FlushCorrupted(Int32 callbackOperationResult) { while (_numFiles != 0) { if (_fileIsOpen) { RINOK(CloseFile_and_SetResult(callbackOperationResult)); } else { RINOK(OpenFile(true)); } } return S_OK; } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testModeSpec, IArchiveExtractCallback *extractCallbackSpec) { COM_TRY_BEGIN CMyComPtr extractCallback = extractCallbackSpec; UInt64 importantTotalUnpacked = 0; // numItems = (UInt32)(Int32)-1; bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _db.Files.Size(); if (numItems == 0) return S_OK; { CNum prevFolder = kNumNoIndex; UInt32 nextFile = 0; UInt32 i; for (i = 0; i < numItems; i++) { UInt32 fileIndex = allFilesMode ? i : indices[i]; CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; if (folderIndex == kNumNoIndex) continue; if (folderIndex != prevFolder || fileIndex < nextFile) nextFile = _db.FolderStartFileIndex[folderIndex]; for (CNum index = nextFile; index <= fileIndex; index++) importantTotalUnpacked += _db.Files[index].Size; nextFile = fileIndex + 1; prevFolder = folderIndex; } } RINOK(extractCallback->SetTotal(importantTotalUnpacked)); CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CDecoder decoder( #if !defined(USE_MIXER_MT) false #elif !defined(USE_MIXER_ST) true #elif !defined(__7Z_SET_PROPERTIES) #ifdef _7ZIP_ST false #else true #endif #else _useMultiThreadMixer #endif ); UInt64 curPacked, curUnpacked; CMyComPtr callbackMessage; extractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage); CFolderOutStream *folderOutStream = new CFolderOutStream; CMyComPtr outStream(folderOutStream); folderOutStream->_db = &_db; folderOutStream->ExtractCallback = extractCallback; folderOutStream->TestMode = (testModeSpec != 0); folderOutStream->CheckCrc = (_crcSize != 0); for (UInt32 i = 0;; lps->OutSize += curUnpacked, lps->InSize += curPacked) { RINOK(lps->SetCur()); if (i >= numItems) break; curUnpacked = 0; curPacked = 0; UInt32 fileIndex = allFilesMode ? i : indices[i]; CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; UInt32 numSolidFiles = 1; if (folderIndex != kNumNoIndex) { curPacked = _db.GetFolderFullPackSize(folderIndex); UInt32 nextFile = fileIndex + 1; fileIndex = _db.FolderStartFileIndex[folderIndex]; UInt32 k; for (k = i + 1; k < numItems; k++) { UInt32 fileIndex2 = allFilesMode ? k : indices[k]; if (_db.FileIndexToFolderIndexMap[fileIndex2] != folderIndex || fileIndex2 < nextFile) break; nextFile = fileIndex2 + 1; } numSolidFiles = k - i; for (k = fileIndex; k < nextFile; k++) curUnpacked += _db.Files[k].Size; } { HRESULT result = folderOutStream->Init(fileIndex, allFilesMode ? NULL : indices + i, numSolidFiles); i += numSolidFiles; RINOK(result); } // to test solid block with zero unpacked size we disable that code if (folderOutStream->WasWritingFinished()) continue; #ifndef _NO_CRYPTO CMyComPtr getTextPassword; if (extractCallback) extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword); #endif try { #ifndef _NO_CRYPTO bool isEncrypted = false; bool passwordIsDefined = false; UString password; #endif HRESULT result = decoder.Decode( EXTERNAL_CODECS_VARS _inStream, _db.ArcInfo.DataStartPosition, _db, folderIndex, &curUnpacked, outStream, progress, NULL // *inStreamMainRes _7Z_DECODER_CRYPRO_VARS #if !defined(_7ZIP_ST) && !defined(_SFX) , true, _numThreads #endif ); if (result == S_FALSE || result == E_NOTIMPL) { bool wasFinished = folderOutStream->WasWritingFinished(); int resOp = (result == S_FALSE ? NExtract::NOperationResult::kDataError : NExtract::NOperationResult::kUnsupportedMethod); RINOK(folderOutStream->FlushCorrupted(resOp)); if (wasFinished) { // we don't show error, if it's after required files if (/* !folderOutStream->ExtraWriteWasCut && */ callbackMessage) { RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp)); } } continue; } if (result != S_OK) return result; RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)); continue; } catch(...) { RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)); // continue; return E_FAIL; } } return S_OK; COM_TRY_END } }}