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
19using namespace NWindows;
20
21static const wchar_t *kCantAutoRename = L"ERROR: Can not create file with auto name";
22static const wchar_t *kCantRenameFile = L"ERROR: Can not rename existing file ";
23static const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";
24
25void 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
56STDMETHODIMP 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
66static 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
76static 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
85STDMETHODIMP 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
103STDMETHODIMP 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
110void 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
122HRESULT 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
137HRESULT 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
147STDMETHODIMP 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
396STDMETHODIMP 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
414STDMETHODIMP 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/*
463STDMETHODIMP 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
477STDMETHODIMP 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