1 // ArchiveExtractCallback.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "Common/ComTry.h"
6 #include "Common/Wildcard.h"
7 
8 #include "Windows/FileDir.h"
9 #include "Windows/FileFind.h"
10 #include "Windows/PropVariant.h"
11 #include "Windows/PropVariantConversions.h"
12 
13 #include "../../Common/FilePathAutoRename.h"
14 
15 #include "../Common/ExtractingFilePath.h"
16 
17 #include "ArchiveExtractCallback.h"
18 
19 using namespace NWindows;
20 
21 static const wchar_t *kCantAutoRename = L"ERROR: Can not create file with auto name";
22 static const wchar_t *kCantRenameFile = L"ERROR: Can not rename existing file ";
23 static const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";
24 
Init(const NWildcard::CCensorNode * wildcardCensor,const CArc * arc,IFolderArchiveExtractCallback * extractCallback2,bool stdOutMode,bool testMode,bool crcMode,const UString & directoryPath,const UStringVector & removePathParts,UInt64 packSize)25 void CArchiveExtractCallback::Init(
26     const NWildcard::CCensorNode *wildcardCensor,
27     const CArc *arc,
28     IFolderArchiveExtractCallback *extractCallback2,
29     bool stdOutMode, bool testMode, bool crcMode,
30     const UString &directoryPath,
31     const UStringVector &removePathParts,
32     UInt64 packSize)
33 {
34   _wildcardCensor = wildcardCensor;
35 
36   _stdOutMode = stdOutMode;
37   _testMode = testMode;
38   _crcMode = crcMode;
39   _unpTotal = 1;
40   _packTotal = packSize;
41 
42   _extractCallback2 = extractCallback2;
43   _compressProgress.Release();
44   _extractCallback2.QueryInterface(IID_ICompressProgressInfo, &_compressProgress);
45 
46   LocalProgressSpec->Init(extractCallback2, true);
47   LocalProgressSpec->SendProgress = false;
48 
49 
50   _removePathParts = removePathParts;
51   _arc = arc;
52   _directoryPath = directoryPath;
53   NFile::NName::NormalizeDirPathPrefix(_directoryPath);
54 }
55 
SetTotal(UInt64 size)56 STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 size)
57 {
58   COM_TRY_BEGIN
59   _unpTotal = size;
60   if (!_multiArchives && _extractCallback2)
61     return _extractCallback2->SetTotal(size);
62   return S_OK;
63   COM_TRY_END
64 }
65 
NormalizeVals(UInt64 & v1,UInt64 & v2)66 static void NormalizeVals(UInt64 &v1, UInt64 &v2)
67 {
68   const UInt64 kMax = (UInt64)1 << 31;
69   while (v1 > kMax)
70   {
71     v1 >>= 1;
72     v2 >>= 1;
73   }
74 }
75 
MyMultDiv64(UInt64 unpCur,UInt64 unpTotal,UInt64 packTotal)76 static UInt64 MyMultDiv64(UInt64 unpCur, UInt64 unpTotal, UInt64 packTotal)
77 {
78   NormalizeVals(packTotal, unpTotal);
79   NormalizeVals(unpCur, unpTotal);
80   if (unpTotal == 0)
81     unpTotal = 1;
82   return unpCur * packTotal / unpTotal;
83 }
84 
SetCompleted(const UInt64 * completeValue)85 STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)
86 {
87   COM_TRY_BEGIN
88   if (!_extractCallback2)
89     return S_OK;
90 
91   if (_multiArchives)
92   {
93     if (completeValue != NULL)
94     {
95       UInt64 packCur = LocalProgressSpec->InSize + MyMultDiv64(*completeValue, _unpTotal, _packTotal);
96       return _extractCallback2->SetCompleted(&packCur);
97     }
98   }
99   return _extractCallback2->SetCompleted(completeValue);
100   COM_TRY_END
101 }
102 
SetRatioInfo(const UInt64 * inSize,const UInt64 * outSize)103 STDMETHODIMP CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
104 {
105   COM_TRY_BEGIN
106   return _localProgress->SetRatioInfo(inSize, outSize);
107   COM_TRY_END
108 }
109 
CreateComplexDirectory(const UStringVector & dirPathParts,UString & fullPath)110 void CArchiveExtractCallback::CreateComplexDirectory(const UStringVector &dirPathParts, UString &fullPath)
111 {
112   fullPath = _directoryPath;
113   for (int i = 0; i < dirPathParts.Size(); i++)
114   {
115     if (i > 0)
116       fullPath += wchar_t(NFile::NName::kDirDelimiter);
117     fullPath += dirPathParts[i];
118     NFile::NDirectory::MyCreateDirectory(fullPath);
119   }
120 }
121 
GetTime(int index,PROPID propID,FILETIME & filetime,bool & filetimeIsDefined)122 HRESULT CArchiveExtractCallback::GetTime(int index, PROPID propID, FILETIME &filetime, bool &filetimeIsDefined)
123 {
124   filetimeIsDefined = false;
125   NCOM::CPropVariant prop;
126   RINOK(_arc->Archive->GetProperty(index, propID, &prop));
127   if (prop.vt == VT_FILETIME)
128   {
129     filetime = prop.filetime;
130     filetimeIsDefined = (filetime.dwHighDateTime != 0 || filetime.dwLowDateTime != 0);
131   }
132   else if (prop.vt != VT_EMPTY)
133     return E_FAIL;
134   return S_OK;
135 }
136 
GetUnpackSize()137 HRESULT CArchiveExtractCallback::GetUnpackSize()
138 {
139   NCOM::CPropVariant prop;
140   RINOK(_arc->Archive->GetProperty(_index, kpidSize, &prop));
141   _curSizeDefined = (prop.vt != VT_EMPTY);
142   if (_curSizeDefined)
143     _curSize = ConvertPropVariantToUInt64(prop);
144   return S_OK;
145 }
146 
GetStream(UInt32 index,ISequentialOutStream ** outStream,Int32 askExtractMode)147 STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)
148 {
149   COM_TRY_BEGIN
150   _crcStream.Release();
151   *outStream = 0;
152   _outFileStream.Release();
153 
154   _encrypted = false;
155   _isSplit = false;
156   _curSize = 0;
157   _curSizeDefined = false;
158   _index = index;
159 
160   UString fullPath;
161 
162   IInArchive *archive = _arc->Archive;
163   RINOK(_arc->GetItemPath(index, fullPath));
164   RINOK(IsArchiveItemFolder(archive, index, _fi.IsDir));
165 
166   _filePath = fullPath;
167 
168   {
169     NCOM::CPropVariant prop;
170     RINOK(archive->GetProperty(index, kpidPosition, &prop));
171     if (prop.vt != VT_EMPTY)
172     {
173       if (prop.vt != VT_UI8)
174         return E_FAIL;
175       _position = prop.uhVal.QuadPart;
176       _isSplit = true;
177     }
178   }
179 
180   RINOK(GetArchiveItemBoolProp(archive, index, kpidEncrypted, _encrypted));
181 
182   RINOK(GetUnpackSize());
183 
184   if (_wildcardCensor)
185   {
186     if (!_wildcardCensor->CheckPath(fullPath, !_fi.IsDir))
187       return S_OK;
188   }
189 
190   if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode)
191   {
192     if (_stdOutMode)
193     {
194       CMyComPtr<ISequentialOutStream> outStreamLoc = new CStdOutFileStream;
195       *outStream = outStreamLoc.Detach();
196       return S_OK;
197     }
198 
199     {
200       NCOM::CPropVariant prop;
201       RINOK(archive->GetProperty(index, kpidAttrib, &prop));
202       if (prop.vt == VT_UI4)
203       {
204         _fi.Attrib = prop.ulVal;
205         _fi.AttribDefined = true;
206       }
207       else if (prop.vt == VT_EMPTY)
208         _fi.AttribDefined = false;
209       else
210         return E_FAIL;
211     }
212 
213     RINOK(GetTime(index, kpidCTime, _fi.CTime, _fi.CTimeDefined));
214     RINOK(GetTime(index, kpidATime, _fi.ATime, _fi.ATimeDefined));
215     RINOK(GetTime(index, kpidMTime, _fi.MTime, _fi.MTimeDefined));
216 
217     bool isAnti = false;
218     RINOK(_arc->IsItemAnti(index, isAnti));
219 
220     UStringVector pathParts;
221     SplitPathToParts(fullPath, pathParts);
222 
223     if (pathParts.IsEmpty())
224       return E_FAIL;
225     int numRemovePathParts = 0;
226     switch(_pathMode)
227     {
228       case NExtract::NPathMode::kFullPathnames:
229         break;
230       case NExtract::NPathMode::kCurrentPathnames:
231       {
232         numRemovePathParts = _removePathParts.Size();
233         if (pathParts.Size() <= numRemovePathParts)
234           return E_FAIL;
235         for (int i = 0; i < numRemovePathParts; i++)
236           if (_removePathParts[i].CompareNoCase(pathParts[i]) != 0)
237             return E_FAIL;
238         break;
239       }
240       case NExtract::NPathMode::kNoPathnames:
241       {
242         numRemovePathParts = pathParts.Size() - 1;
243         break;
244       }
245     }
246     pathParts.Delete(0, numRemovePathParts);
247     MakeCorrectPath(pathParts);
248     UString processedPath = MakePathNameFromParts(pathParts);
249     if (!isAnti)
250     {
251       if (!_fi.IsDir)
252       {
253         if (!pathParts.IsEmpty())
254           pathParts.DeleteBack();
255       }
256 
257       if (!pathParts.IsEmpty())
258       {
259         UString fullPathNew;
260         CreateComplexDirectory(pathParts, fullPathNew);
261         if (_fi.IsDir)
262           NFile::NDirectory::SetDirTime(fullPathNew,
263             (WriteCTime && _fi.CTimeDefined) ? &_fi.CTime : NULL,
264             (WriteATime && _fi.ATimeDefined) ? &_fi.ATime : NULL,
265             (WriteMTime && _fi.MTimeDefined) ? &_fi.MTime : (_arc->MTimeDefined ? &_arc->MTime : NULL));
266       }
267     }
268 
269 
270     UString fullProcessedPath = _directoryPath + processedPath;
271 
272     if (_fi.IsDir)
273     {
274       _diskFilePath = fullProcessedPath;
275       if (isAnti)
276         NFile::NDirectory::MyRemoveDirectory(_diskFilePath);
277       return S_OK;
278     }
279 
280     if (!_isSplit)
281     {
282     NFile::NFind::CFileInfoW fileInfo;
283     if (fileInfo.Find(fullProcessedPath))
284     {
285       switch(_overwriteMode)
286       {
287         case NExtract::NOverwriteMode::kSkipExisting:
288           return S_OK;
289         case NExtract::NOverwriteMode::kAskBefore:
290         {
291           Int32 overwiteResult;
292           RINOK(_extractCallback2->AskOverwrite(
293               fullProcessedPath, &fileInfo.MTime, &fileInfo.Size, fullPath,
294               _fi.MTimeDefined ? &_fi.MTime : NULL,
295               _curSizeDefined ? &_curSize : NULL,
296               &overwiteResult))
297 
298           switch(overwiteResult)
299           {
300             case NOverwriteAnswer::kCancel:
301               return E_ABORT;
302             case NOverwriteAnswer::kNo:
303               return S_OK;
304             case NOverwriteAnswer::kNoToAll:
305               _overwriteMode = NExtract::NOverwriteMode::kSkipExisting;
306               return S_OK;
307             case NOverwriteAnswer::kYesToAll:
308               _overwriteMode = NExtract::NOverwriteMode::kWithoutPrompt;
309               break;
310             case NOverwriteAnswer::kYes:
311               break;
312             case NOverwriteAnswer::kAutoRename:
313               _overwriteMode = NExtract::NOverwriteMode::kAutoRename;
314               break;
315             default:
316               return E_FAIL;
317           }
318         }
319       }
320       if (_overwriteMode == NExtract::NOverwriteMode::kAutoRename)
321       {
322         if (!AutoRenamePath(fullProcessedPath))
323         {
324           UString message = UString(kCantAutoRename) + fullProcessedPath;
325           RINOK(_extractCallback2->MessageError(message));
326           return E_FAIL;
327         }
328       }
329       else if (_overwriteMode == NExtract::NOverwriteMode::kAutoRenameExisting)
330       {
331         UString existPath = fullProcessedPath;
332         if (!AutoRenamePath(existPath))
333         {
334           UString message = kCantAutoRename + fullProcessedPath;
335           RINOK(_extractCallback2->MessageError(message));
336           return E_FAIL;
337         }
338         if (!NFile::NDirectory::MyMoveFile(fullProcessedPath, existPath))
339         {
340           UString message = UString(kCantRenameFile) + fullProcessedPath;
341           RINOK(_extractCallback2->MessageError(message));
342           return E_FAIL;
343         }
344       }
345       else
346         if (!NFile::NDirectory::DeleteFileAlways(fullProcessedPath))
347         {
348           UString message = UString(kCantDeleteOutputFile) +  fullProcessedPath;
349           RINOK(_extractCallback2->MessageError(message));
350           return S_OK;
351           // return E_FAIL;
352         }
353     }
354     }
355     if (!isAnti)
356     {
357       _outFileStreamSpec = new COutFileStream;
358       CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
359       if (!_outFileStreamSpec->Open(fullProcessedPath, _isSplit ? OPEN_ALWAYS: CREATE_ALWAYS))
360       {
361         // if (::GetLastError() != ERROR_FILE_EXISTS || !isSplit)
362         {
363           UString message = L"can not open output file " + fullProcessedPath;
364           RINOK(_extractCallback2->MessageError(message));
365           return S_OK;
366         }
367       }
368       if (_isSplit)
369       {
370         RINOK(_outFileStreamSpec->Seek(_position, STREAM_SEEK_SET, NULL));
371       }
372       _outFileStream = outStreamLoc;
373       *outStream = outStreamLoc.Detach();
374     }
375     _diskFilePath = fullProcessedPath;
376   }
377   else
378   {
379     *outStream = NULL;
380   }
381   if (_crcMode)
382   {
383     _crcStreamSpec = new COutStreamWithCRC;
384     _crcStream = _crcStreamSpec;
385     CMyComPtr<ISequentialOutStream> crcStream = _crcStreamSpec;
386     _crcStreamSpec->SetStream(*outStream);
387     if (*outStream)
388       (*outStream)->Release();
389     *outStream = crcStream.Detach();
390     _crcStreamSpec->Init(true);
391   }
392   return S_OK;
393   COM_TRY_END
394 }
395 
PrepareOperation(Int32 askExtractMode)396 STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
397 {
398   COM_TRY_BEGIN
399   _extractMode = false;
400   switch (askExtractMode)
401   {
402     case NArchive::NExtract::NAskMode::kExtract:
403       if (_testMode)
404         askExtractMode = NArchive::NExtract::NAskMode::kTest;
405       else
406         _extractMode = true;
407       break;
408   };
409   return _extractCallback2->PrepareOperation(_filePath, _fi.IsDir,
410       askExtractMode, _isSplit ? &_position: 0);
411   COM_TRY_END
412 }
413 
SetOperationResult(Int32 operationResult)414 STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
415 {
416   COM_TRY_BEGIN
417   switch(operationResult)
418   {
419     case NArchive::NExtract::NOperationResult::kOK:
420     case NArchive::NExtract::NOperationResult::kUnSupportedMethod:
421     case NArchive::NExtract::NOperationResult::kCRCError:
422     case NArchive::NExtract::NOperationResult::kDataError:
423       break;
424     default:
425       _outFileStream.Release();
426       return E_FAIL;
427   }
428   if (_crcStream)
429   {
430     CrcSum += _crcStreamSpec->GetCRC();
431     _curSize = _crcStreamSpec->GetSize();
432     _curSizeDefined = true;
433     _crcStream.Release();
434   }
435   if (_outFileStream)
436   {
437     _outFileStreamSpec->SetTime(
438         (WriteCTime && _fi.CTimeDefined) ? &_fi.CTime : NULL,
439         (WriteATime && _fi.ATimeDefined) ? &_fi.ATime : NULL,
440         (WriteMTime && _fi.MTimeDefined) ? &_fi.MTime : (_arc->MTimeDefined ? &_arc->MTime : NULL));
441     _curSize = _outFileStreamSpec->ProcessedSize;
442     _curSizeDefined = true;
443     RINOK(_outFileStreamSpec->Close());
444     _outFileStream.Release();
445   }
446   if (!_curSizeDefined)
447     GetUnpackSize();
448   if (_curSizeDefined)
449     UnpackSize += _curSize;
450   if (_fi.IsDir)
451     NumFolders++;
452   else
453     NumFiles++;
454 
455   if (_extractMode && _fi.AttribDefined)
456     NFile::NDirectory::MySetFileAttributes(_diskFilePath, _fi.Attrib);
457   RINOK(_extractCallback2->SetOperationResult(operationResult, _encrypted));
458   return S_OK;
459   COM_TRY_END
460 }
461 
462 /*
463 STDMETHODIMP CArchiveExtractCallback::GetInStream(
464     const wchar_t *name, ISequentialInStream **inStream)
465 {
466   COM_TRY_BEGIN
467   CInFileStream *inFile = new CInFileStream;
468   CMyComPtr<ISequentialInStream> inStreamTemp = inFile;
469   if (!inFile->Open(_srcDirectoryPrefix + name))
470     return ::GetLastError();
471   *inStream = inStreamTemp.Detach();
472   return S_OK;
473   COM_TRY_END
474 }
475 */
476 
CryptoGetTextPassword(BSTR * password)477 STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
478 {
479   COM_TRY_BEGIN
480   if (!_cryptoGetTextPassword)
481   {
482     RINOK(_extractCallback2.QueryInterface(IID_ICryptoGetTextPassword,
483         &_cryptoGetTextPassword));
484   }
485   return _cryptoGetTextPassword->CryptoGetTextPassword(password);
486   COM_TRY_END
487 }
488 
489