// ExtractCallback.cpp #include "StdAfx.h" #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" #include "../../../Common/Lang.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/ErrorMsg.h" #include "../../../Windows/FileDir.h" #include "../../../Windows/FileFind.h" #include "../../../Windows/PropVariantConv.h" #include "../../Common/FilePathAutoRename.h" #include "../../Common/StreamUtils.h" #include "../Common/ExtractingFilePath.h" #ifndef _SFX #include "../Common/ZipRegistry.h" #endif #include "../GUI/ExtractRes.h" #include "ExtractCallback.h" #include "FormatUtils.h" #include "LangUtils.h" #include "OverwriteDialog.h" #ifndef _NO_CRYPTO #include "PasswordDialog.h" #endif using namespace NWindows; using namespace NFile; using namespace NFind; CExtractCallbackImp::~CExtractCallbackImp() {} void CExtractCallbackImp::Init() { NumArchiveErrors = 0; ThereAreMessageErrors = false; #ifndef _SFX NumFolders = NumFiles = 0; NeedAddFile = false; #endif } void CExtractCallbackImp::AddError_Message(LPCWSTR s) { ThereAreMessageErrors = true; ProgressDialog->Sync.AddError_Message(s); } #ifndef _SFX STDMETHODIMP CExtractCallbackImp::SetNumFiles(UInt64 #ifndef _SFX numFiles #endif ) { #ifndef _SFX ProgressDialog->Sync.Set_NumFilesTotal(numFiles); #endif return S_OK; } #endif STDMETHODIMP CExtractCallbackImp::SetTotal(UInt64 total) { ProgressDialog->Sync.Set_NumBytesTotal(total); return S_OK; } STDMETHODIMP CExtractCallbackImp::SetCompleted(const UInt64 *value) { return ProgressDialog->Sync.Set_NumBytesCur(value); } HRESULT CExtractCallbackImp::Open_CheckBreak() { return ProgressDialog->Sync.CheckStop(); } HRESULT CExtractCallbackImp::Open_SetTotal(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) { // if (numFiles != NULL) ProgressDialog->Sync.SetNumFilesTotal(*numFiles); return S_OK; } HRESULT CExtractCallbackImp::Open_SetCompleted(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) { // if (numFiles != NULL) ProgressDialog->Sync.SetNumFilesCur(*numFiles); return ProgressDialog->Sync.CheckStop(); } #ifndef _NO_CRYPTO HRESULT CExtractCallbackImp::Open_CryptoGetTextPassword(BSTR *password) { return CryptoGetTextPassword(password); } HRESULT CExtractCallbackImp::Open_GetPasswordIfAny(bool &passwordIsDefined, UString &password) { passwordIsDefined = PasswordIsDefined; password = Password; return S_OK; } bool CExtractCallbackImp::Open_WasPasswordAsked() { return PasswordWasAsked; } void CExtractCallbackImp::Open_ClearPasswordWasAskedFlag() { PasswordWasAsked = false; } #endif #ifndef _SFX STDMETHODIMP CExtractCallbackImp::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) { ProgressDialog->Sync.Set_Ratio(inSize, outSize); return S_OK; } #endif /* STDMETHODIMP CExtractCallbackImp::SetTotalFiles(UInt64 total) { ProgressDialog->Sync.SetNumFilesTotal(total); return S_OK; } STDMETHODIMP CExtractCallbackImp::SetCompletedFiles(const UInt64 *value) { if (value != NULL) ProgressDialog->Sync.SetNumFilesCur(*value); return S_OK; } */ STDMETHODIMP CExtractCallbackImp::AskOverwrite( const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize, const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize, Int32 *answer) { COverwriteDialog dialog; dialog.OldFileInfo.SetTime(existTime); dialog.OldFileInfo.SetSize(existSize); dialog.OldFileInfo.Name = existName; dialog.NewFileInfo.SetTime(newTime); dialog.NewFileInfo.SetSize(newSize); dialog.NewFileInfo.Name = newName; ProgressDialog->WaitCreating(); INT_PTR writeAnswer = dialog.Create(*ProgressDialog); switch (writeAnswer) { case IDCANCEL: *answer = NOverwriteAnswer::kCancel; return E_ABORT; case IDYES: *answer = NOverwriteAnswer::kYes; break; case IDNO: *answer = NOverwriteAnswer::kNo; break; case IDB_YES_TO_ALL: *answer = NOverwriteAnswer::kYesToAll; break; case IDB_NO_TO_ALL: *answer = NOverwriteAnswer::kNoToAll; break; case IDB_AUTO_RENAME: *answer = NOverwriteAnswer::kAutoRename; break; default: return E_FAIL; } return S_OK; } STDMETHODIMP CExtractCallbackImp::PrepareOperation(const wchar_t *name, bool isFolder, Int32 /* askExtractMode */, const UInt64 * /* position */) { _isFolder = isFolder; return SetCurrentFilePath2(name); } STDMETHODIMP CExtractCallbackImp::MessageError(const wchar_t *s) { AddError_Message(s); return S_OK; } HRESULT CExtractCallbackImp::MessageError(const char *message, const FString &path) { ThereAreMessageErrors = true; ProgressDialog->Sync.AddError_Message_Name(GetUnicodeString(message), fs2us(path)); return S_OK; } #ifndef _SFX STDMETHODIMP CExtractCallbackImp::ShowMessage(const wchar_t *s) { AddError_Message(s); return S_OK; } #endif STDMETHODIMP CExtractCallbackImp::SetOperationResult(Int32 opRes, bool encrypted) { switch (opRes) { case NArchive::NExtract::NOperationResult::kOK: break; default: { UINT messageID = 0; UINT id = 0; switch (opRes) { case NArchive::NExtract::NOperationResult::kUnsupportedMethod: messageID = IDS_EXTRACT_MESSAGE_UNSUPPORTED_METHOD; id = IDS_EXTRACT_MSG_UNSUPPORTED_METHOD; break; case NArchive::NExtract::NOperationResult::kDataError: messageID = encrypted ? IDS_EXTRACT_MESSAGE_DATA_ERROR_ENCRYPTED: IDS_EXTRACT_MESSAGE_DATA_ERROR; id = IDS_EXTRACT_MSG_DATA_ERROR; break; case NArchive::NExtract::NOperationResult::kCRCError: messageID = encrypted ? IDS_EXTRACT_MESSAGE_CRC_ERROR_ENCRYPTED: IDS_EXTRACT_MESSAGE_CRC_ERROR; id = IDS_EXTRACT_MSG_CRC_ERROR; break; case NArchive::NExtract::NOperationResult::kUnavailable: id = IDS_EXTRACT_MSG_UNAVAILABLE_DATA; break; case NArchive::NExtract::NOperationResult::kUnexpectedEnd: id = IDS_EXTRACT_MSG_UEXPECTED_END; break; case NArchive::NExtract::NOperationResult::kDataAfterEnd: id = IDS_EXTRACT_MSG_DATA_AFTER_END; break; case NArchive::NExtract::NOperationResult::kIsNotArc: id = IDS_EXTRACT_MSG_IS_NOT_ARC; break; case NArchive::NExtract::NOperationResult::kHeadersError: id = IDS_EXTRACT_MSG_HEADERS_ERROR; break; /* default: messageID = IDS_EXTRACT_MESSAGE_UNKNOWN_ERROR; break; */ } if (_needWriteArchivePath) { if (!_currentArchivePath.IsEmpty()) AddError_Message(_currentArchivePath); _needWriteArchivePath = false; } UString msg; UString msgOld; #ifndef _SFX if (id != 0) LangString_OnlyFromLangFile(id, msg); if (messageID != 0 && msg.IsEmpty()) LangString_OnlyFromLangFile(messageID, msgOld); #endif UString s; if (msg.IsEmpty() && !msgOld.IsEmpty()) s = MyFormatNew(msgOld, _currentFilePath); else { if (msg.IsEmpty()) LangString(id, msg); if (!msg.IsEmpty()) s += msg; else { wchar_t temp[16]; ConvertUInt32ToString(opRes, temp); s += L"Error #"; s += temp; } if (encrypted) { // s += L" : "; // s += LangString(IDS_EXTRACT_MSG_ENCRYPTED); s += L" : "; s += LangString(IDS_EXTRACT_MSG_WRONG_PSW); } s += L" : "; s += _currentFilePath; } AddError_Message(s); } } #ifndef _SFX if (_isFolder) NumFolders++; else NumFiles++; ProgressDialog->Sync.Set_NumFilesCur(NumFiles); #endif return S_OK; } //////////////////////////////////////// // IExtractCallbackUI HRESULT CExtractCallbackImp::BeforeOpen(const wchar_t *name) { #ifndef _SFX RINOK(ProgressDialog->Sync.CheckStop()); ProgressDialog->Sync.Set_TitleFileName(name); #endif _currentArchivePath = name; return S_OK; } HRESULT CExtractCallbackImp::SetCurrentFilePath2(const wchar_t *path) { _currentFilePath = path; #ifndef _SFX ProgressDialog->Sync.Set_FilePath(path); #endif return S_OK; } #ifndef _SFX HRESULT CExtractCallbackImp::SetCurrentFilePath(const wchar_t *path) { #ifndef _SFX if (NeedAddFile) NumFiles++; NeedAddFile = true; ProgressDialog->Sync.Set_NumFilesCur(NumFiles); #endif return SetCurrentFilePath2(path); } #endif UString HResultToMessage(HRESULT errorCode); HRESULT CExtractCallbackImp::OpenResult(const wchar_t *name, HRESULT result, bool encrypted) { if (result != S_OK) { UString s; if (result == S_FALSE) s = MyFormatNew(encrypted ? IDS_CANT_OPEN_ENCRYPTED_ARCHIVE : IDS_CANT_OPEN_ARCHIVE, name); else { s = name; s += L": "; s += HResultToMessage(result); } MessageError(s); NumArchiveErrors++; } _currentArchivePath = name; _needWriteArchivePath = true; return S_OK; } static const UInt32 k_ErrorFlagsIds[] = { IDS_EXTRACT_MSG_IS_NOT_ARC, IDS_EXTRACT_MSG_HEADERS_ERROR, IDS_EXTRACT_MSG_HEADERS_ERROR, IDS_OPEN_MSG_UNAVAILABLE_START, IDS_OPEN_MSG_UNCONFIRMED_START, IDS_EXTRACT_MSG_UEXPECTED_END, IDS_EXTRACT_MSG_DATA_AFTER_END, IDS_EXTRACT_MSG_UNSUPPORTED_METHOD, IDS_OPEN_MSG_UNSUPPORTED_FEATURE, IDS_EXTRACT_MSG_DATA_ERROR, IDS_EXTRACT_MSG_CRC_ERROR }; UString GetOpenArcErrorMessage(UInt32 errorFlags) { UString s; for (unsigned i = 0; i < ARRAY_SIZE(k_ErrorFlagsIds); i++) { UInt32 f = ((UInt32)1 << i); if ((errorFlags & f) == 0) continue; UInt32 id = k_ErrorFlagsIds[i]; UString m = LangString(id); if (m.IsEmpty()) continue; if (f == kpv_ErrorFlags_EncryptedHeadersError) { m += L" : "; m += LangString(IDS_EXTRACT_MSG_WRONG_PSW); } if (!s.IsEmpty()) s += L'\n'; s += m; errorFlags &= ~f; } if (errorFlags != 0) { char sz[16]; sz[0] = '0'; sz[1] = 'x'; ConvertUInt32ToHex(errorFlags, sz + 2); if (!s.IsEmpty()) s += L'\n'; s += GetUnicodeString(AString(sz)); } return s; } HRESULT CExtractCallbackImp::SetError(int level, const wchar_t *name, UInt32 errorFlags, const wchar_t *errors, UInt32 warningFlags, const wchar_t *warnings) { NumArchiveErrors++; if (_needWriteArchivePath) { if (!_currentArchivePath.IsEmpty()) AddError_Message(_currentArchivePath); _needWriteArchivePath = false; } if (level != 0) { UString s; s += name; s += L": "; MessageError(s); } if (errorFlags != 0) MessageError(GetOpenArcErrorMessage(errorFlags)); if (errors && wcslen(errors) != 0) MessageError(errors); if (warningFlags != 0) MessageError((UString)L"Warnings: " + GetOpenArcErrorMessage(warningFlags)); if (warnings && wcslen(warnings) != 0) MessageError((UString)L"Warnings: " + warnings); return S_OK; } HRESULT CExtractCallbackImp::OpenTypeWarning(const wchar_t *name, const wchar_t *okType, const wchar_t *errorType) { UString s = L"Warning:\n"; s += name; s += L"\n"; if (wcscmp(okType, errorType) == 0) { s += L"The archive is open with offset"; } else { s += L"Can not open the file as ["; s += errorType; s += L"] archive\n"; s += L"The file is open as ["; s += okType; s += L"] archive"; } MessageError(s); NumArchiveErrors++; return S_OK; } HRESULT CExtractCallbackImp::ThereAreNoFiles() { return S_OK; } HRESULT CExtractCallbackImp::ExtractResult(HRESULT result) { if (result == S_OK) return result; NumArchiveErrors++; if (result == E_ABORT || result == ERROR_DISK_FULL) return result; MessageError(_currentFilePath); MessageError(NError::MyFormatMessage(result)); return S_OK; } #ifndef _NO_CRYPTO HRESULT CExtractCallbackImp::SetPassword(const UString &password) { PasswordIsDefined = true; Password = password; return S_OK; } STDMETHODIMP CExtractCallbackImp::CryptoGetTextPassword(BSTR *password) { PasswordWasAsked = true; if (!PasswordIsDefined) { CPasswordDialog dialog; #ifndef _SFX bool showPassword = NExtract::Read_ShowPassword(); dialog.ShowPassword = showPassword; #endif ProgressDialog->WaitCreating(); if (dialog.Create(*ProgressDialog) != IDOK) return E_ABORT; Password = dialog.Password; PasswordIsDefined = true; #ifndef _SFX if (dialog.ShowPassword != showPassword) NExtract::Save_ShowPassword(dialog.ShowPassword); #endif } return StringToBstr(Password, password); } #endif #ifndef _SFX STDMETHODIMP CExtractCallbackImp::AskWrite( const wchar_t *srcPath, Int32 srcIsFolder, const FILETIME *srcTime, const UInt64 *srcSize, const wchar_t *destPath, BSTR *destPathResult, Int32 *writeAnswer) { UString destPathResultTemp = destPath; // RINOK(StringToBstr(destPath, destPathResult)); *destPathResult = 0; *writeAnswer = BoolToInt(false); FString destPathSys = us2fs(destPath); bool srcIsFolderSpec = IntToBool(srcIsFolder); CFileInfo destFileInfo; if (destFileInfo.Find(destPathSys)) { if (srcIsFolderSpec) { if (!destFileInfo.IsDir()) { RINOK(MessageError("can not replace file with folder with same name: ", destPathSys)); return E_ABORT; } *writeAnswer = BoolToInt(false); return S_OK; } if (destFileInfo.IsDir()) { RINOK(MessageError("can not replace folder with file with same name: ", destPathSys)); return E_FAIL; } switch (OverwriteMode) { case NExtract::NOverwriteMode::kSkip: return S_OK; case NExtract::NOverwriteMode::kAsk: { Int32 overwiteResult; UString destPathSpec = destPath; int slashPos = destPathSpec.ReverseFind(L'/'); #ifdef _WIN32 int slash1Pos = destPathSpec.ReverseFind(L'\\'); slashPos = MyMax(slashPos, slash1Pos); #endif destPathSpec.DeleteFrom(slashPos + 1); destPathSpec += fs2us(destFileInfo.Name); RINOK(AskOverwrite( destPathSpec, &destFileInfo.MTime, &destFileInfo.Size, srcPath, srcTime, srcSize, &overwiteResult)); switch (overwiteResult) { case NOverwriteAnswer::kCancel: return E_ABORT; case NOverwriteAnswer::kNo: return S_OK; case NOverwriteAnswer::kNoToAll: OverwriteMode = NExtract::NOverwriteMode::kSkip; return S_OK; case NOverwriteAnswer::kYes: break; case NOverwriteAnswer::kYesToAll: OverwriteMode = NExtract::NOverwriteMode::kOverwrite; break; case NOverwriteAnswer::kAutoRename: OverwriteMode = NExtract::NOverwriteMode::kRename; break; default: return E_FAIL; } } } if (OverwriteMode == NExtract::NOverwriteMode::kRename) { if (!AutoRenamePath(destPathSys)) { RINOK(MessageError("can not create name for file: ", destPathSys)); return E_ABORT; } destPathResultTemp = fs2us(destPathSys); } else if (!NDir::DeleteFileAlways(destPathSys)) { RINOK(MessageError("can not delete output file: ", destPathSys)); return E_ABORT; } } *writeAnswer = BoolToInt(true); return StringToBstr(destPathResultTemp, destPathResult); } STDMETHODIMP CExtractCallbackImp::UseExtractToStream(Int32 *res) { *res = BoolToInt(StreamMode); return S_OK; } static HRESULT GetTime(IGetProp *getProp, PROPID propID, FILETIME &ft, bool &ftDefined) { ftDefined = false; NCOM::CPropVariant prop; RINOK(getProp->GetProp(propID, &prop)); if (prop.vt == VT_FILETIME) { ft = prop.filetime; ftDefined = (ft.dwHighDateTime != 0 || ft.dwLowDateTime != 0); } else if (prop.vt != VT_EMPTY) return E_FAIL; return S_OK; } static HRESULT GetItemBoolProp(IGetProp *getProp, PROPID propID, bool &result) { NCOM::CPropVariant prop; result = false; RINOK(getProp->GetProp(propID, &prop)); if (prop.vt == VT_BOOL) result = VARIANT_BOOLToBool(prop.boolVal); else if (prop.vt != VT_EMPTY) return E_FAIL; return S_OK; } STDMETHODIMP CExtractCallbackImp::GetStream7(const wchar_t *name, Int32 isDir, ISequentialOutStream **outStream, Int32 askExtractMode, IGetProp *getProp) { COM_TRY_BEGIN *outStream = 0; _newVirtFileWasAdded = false; _hashStreamWasUsed = false; _needUpdateStat = false; if (_hashStream) _hashStreamSpec->ReleaseStream(); GetItemBoolProp(getProp, kpidIsAltStream, _isAltStream); if (!ProcessAltStreams && _isAltStream) return S_OK; _filePath = name; _isFolder = IntToBool(isDir); _curSize = 0; _curSizeDefined = false; UInt64 size = 0; bool sizeDefined; { NCOM::CPropVariant prop; RINOK(getProp->GetProp(kpidSize, &prop)); sizeDefined = ConvertPropVariantToUInt64(prop, size); } if (sizeDefined) { _curSize = size; _curSizeDefined = true; } if (askExtractMode != NArchive::NExtract::NAskMode::kExtract && askExtractMode != NArchive::NExtract::NAskMode::kTest) return S_OK; _needUpdateStat = true; CMyComPtr outStreamLoc; if (VirtFileSystem && askExtractMode == NArchive::NExtract::NAskMode::kExtract) { CVirtFile &file = VirtFileSystemSpec->AddNewFile(); _newVirtFileWasAdded = true; file.Name = name; file.IsDir = IntToBool(isDir); file.IsAltStream = _isAltStream; file.Size = 0; RINOK(GetTime(getProp, kpidCTime, file.CTime, file.CTimeDefined)); RINOK(GetTime(getProp, kpidATime, file.ATime, file.ATimeDefined)); RINOK(GetTime(getProp, kpidMTime, file.MTime, file.MTimeDefined)); NCOM::CPropVariant prop; RINOK(getProp->GetProp(kpidAttrib, &prop)); if (prop.vt == VT_UI4) { file.Attrib = prop.ulVal; file.AttribDefined = true; } // else if (isDir) file.Attrib = FILE_ATTRIBUTE_DIRECTORY; file.ExpectedSize = 0; if (sizeDefined) file.ExpectedSize = size; outStreamLoc = VirtFileSystem; } if (_hashStream) { { _hashStreamSpec->SetStream(outStreamLoc); outStreamLoc = _hashStream; _hashStreamSpec->Init(true); _hashStreamWasUsed = true; } } if (outStreamLoc) *outStream = outStreamLoc.Detach(); return S_OK; COM_TRY_END } STDMETHODIMP CExtractCallbackImp::PrepareOperation7(Int32 askExtractMode) { COM_TRY_BEGIN _needUpdateStat = ( askExtractMode == NArchive::NExtract::NAskMode::kExtract || askExtractMode == NArchive::NExtract::NAskMode::kTest); /* _extractMode = false; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: if (_testMode) askExtractMode = NArchive::NExtract::NAskMode::kTest; else _extractMode = true; break; }; */ return SetCurrentFilePath2(_filePath); COM_TRY_END } STDMETHODIMP CExtractCallbackImp::SetOperationResult7(Int32 opRes, bool encrypted) { COM_TRY_BEGIN if (VirtFileSystem && _newVirtFileWasAdded) { // FIXME: probably we must request file size from VirtFileSystem // _curSize = VirtFileSystem->GetLastFileSize() // _curSizeDefined = true; RINOK(VirtFileSystemSpec->CloseMemFile()); } if (_hashStream && _hashStreamWasUsed) { _hashStreamSpec->_hash->Final(_isFolder, _isAltStream, _filePath); _curSize = _hashStreamSpec->GetSize(); _curSizeDefined = true; _hashStreamSpec->ReleaseStream(); _hashStreamWasUsed = false; } else if (_hashCalc && _needUpdateStat) { _hashCalc->SetSize(_curSize); _hashCalc->Final(_isFolder, _isAltStream, _filePath); } return SetOperationResult(opRes, encrypted); COM_TRY_END } static const size_t k_SizeT_MAX = (size_t)((size_t)0 - 1); static const UInt32 kBlockSize = ((UInt32)1 << 31); STDMETHODIMP CVirtFileSystem::Write(const void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (size == 0) return S_OK; if (!_fileMode) { CVirtFile &file = Files.Back(); size_t rem = file.Data.Size() - (size_t)file.Size; bool useMem = true; if (rem < size) { UInt64 b = 0; if (file.Data.Size() == 0) b = file.ExpectedSize; UInt64 a = file.Size + size; if (b < a) b = a; a = (UInt64)file.Data.Size() * 2; if (b < a) b = a; useMem = false; if (b <= k_SizeT_MAX && b <= MaxTotalAllocSize) useMem = file.Data.ReAlloc_KeepData((size_t)b, (size_t)file.Size); } if (useMem) { memcpy(file.Data + file.Size, data, size); file.Size += size; if (processedSize) *processedSize = (UInt32)size; return S_OK; } _fileMode = true; } RINOK(FlushToDisk(false)); return _outFileStream->Write(data, size, processedSize); } HRESULT CVirtFileSystem::FlushToDisk(bool closeLast) { if (!_outFileStream) { _outFileStreamSpec = new COutFileStream; _outFileStream = _outFileStreamSpec; } while (_numFlushed < Files.Size()) { const CVirtFile &file = Files[_numFlushed]; const FString path = DirPrefix + us2fs(GetCorrectFsPath(file.Name)); if (!_fileIsOpen) { if (!_outFileStreamSpec->Create(path, false)) { _outFileStream.Release(); return E_FAIL; // MessageBoxMyError(UString(L"Can't create file ") + fs2us(tempFilePath)); } _fileIsOpen = true; RINOK(WriteStream(_outFileStream, file.Data, (size_t)file.Size)); } if (_numFlushed == Files.Size() - 1 && !closeLast) break; if (file.CTimeDefined || file.ATimeDefined || file.MTimeDefined) _outFileStreamSpec->SetTime( file.CTimeDefined ? &file.CTime : NULL, file.ATimeDefined ? &file.ATime : NULL, file.MTimeDefined ? &file.MTime : NULL); _outFileStreamSpec->Close(); _numFlushed++; _fileIsOpen = false; if (file.AttribDefined) NDir::SetFileAttrib(path, file.Attrib); } return S_OK; } #endif