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