// Extract.cpp #include "StdAfx.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/FileDir.h" #include "../../../Windows/PropVariant.h" #include "../../../Windows/PropVariantConv.h" #include "../Common/ExtractingFilePath.h" #include "Extract.h" #include "SetProperties.h" using namespace NWindows; using namespace NFile; using namespace NDir; static HRESULT DecompressArchive( CCodecs *codecs, const CArchiveLink &arcLink, UInt64 packSize, const NWildcard::CCensorNode &wildcardCensor, const CExtractOptions &options, bool calcCrc, IExtractCallbackUI *callback, CArchiveExtractCallback *ecs, UString &errorMessage, UInt64 &stdInProcessed) { const CArc &arc = arcLink.Arcs.Back(); stdInProcessed = 0; IInArchive *archive = arc.Archive; CRecordVector realIndices; UStringVector removePathParts; FString outDir = options.OutputDir; UString replaceName = arc.DefaultName; if (arcLink.Arcs.Size() > 1) { // Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1". // So it extracts different archives to one folder. // We will use top level archive name const CArc &arc0 = arcLink.Arcs[0]; if (StringsAreEqualNoCase_Ascii(codecs->Formats[arc0.FormatIndex].Name, "pe")) replaceName = arc0.DefaultName; } outDir.Replace(FSTRING_ANY_MASK, us2fs(GetCorrectFsPath(replaceName))); bool elimIsPossible = false; UString elimPrefix; // only pure name without dir delimiter FString outDirReduced = outDir; if (options.ElimDup.Val) { UString dirPrefix; SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix); if (!elimPrefix.IsEmpty()) { if (IsCharDirLimiter(elimPrefix.Back())) elimPrefix.DeleteBack(); if (!elimPrefix.IsEmpty()) { outDirReduced = us2fs(dirPrefix); elimIsPossible = true; } } } if (!options.StdInMode) { UInt32 numItems; RINOK(archive->GetNumberOfItems(&numItems)); UString filePath; for (UInt32 i = 0; i < numItems; i++) { RINOK(arc.GetItemPath(i, filePath)); if (elimIsPossible && options.ElimDup.Val) { if (!IsPath1PrefixedByPath2(filePath, elimPrefix)) elimIsPossible = false; else { wchar_t c = filePath[elimPrefix.Len()]; if (c != 0 && !IsCharDirLimiter(c)) elimIsPossible = false; } } bool isFolder; RINOK(Archive_IsItem_Folder(archive, i, isFolder)); bool isAltStream; RINOK(Archive_IsItem_AltStream(archive, i, isAltStream)); if (!options.NtOptions.AltStreams.Val && isAltStream) continue; if (!wildcardCensor.CheckPath(isAltStream, filePath, !isFolder)) continue; realIndices.Add(i); } if (realIndices.Size() == 0) { callback->ThereAreNoFiles(); return callback->ExtractResult(S_OK); } } if (elimIsPossible) outDir = outDirReduced; #ifdef _WIN32 // GetCorrectFullFsPath doesn't like "..". // outDir.TrimRight(); // outDir = GetCorrectFullFsPath(outDir); #endif if (outDir.IsEmpty()) outDir = FString(FTEXT(".")) + FString(FSTRING_PATH_SEPARATOR); else if (!CreateComplexDir(outDir)) { HRESULT res = ::GetLastError(); if (res == S_OK) res = E_FAIL; errorMessage = ((UString)L"Can not create output directory ") + fs2us(outDir); return res; } ecs->Init( options.NtOptions, options.StdInMode ? &wildcardCensor : NULL, &arc, callback, options.StdOutMode, options.TestMode, outDir, removePathParts, packSize); #ifdef SUPPORT_LINKS if (!options.StdInMode && !options.TestMode && options.NtOptions.HardLinks.Val) { RINOK(ecs->PrepareHardLinks(&realIndices)); } #endif HRESULT result; Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0; if (options.StdInMode) { result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs); NCOM::CPropVariant prop; if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK) ConvertPropVariantToUInt64(prop, stdInProcessed); } else result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, ecs); if (result == S_OK && !options.StdInMode) result = ecs->SetDirsTimes(); return callback->ExtractResult(result); } /* v9.31: BUG was fixed: Sorted list for file paths was sorted with case insensitive compare function. But FindInSorted function did binary search via case sensitive compare function */ int Find_FileName_InSortedVector(const UStringVector &fileName, const UString &name) { unsigned left = 0, right = fileName.Size(); while (left != right) { unsigned mid = (left + right) / 2; const UString &midValue = fileName[mid]; int compare = CompareFileNames(name, midValue); if (compare == 0) return mid; if (compare < 0) right = mid; else left = mid + 1; } return -1; } HRESULT Extract( CCodecs *codecs, const CObjectVector &types, const CIntVector &excludedFormats, UStringVector &arcPaths, UStringVector &arcPathsFull, const NWildcard::CCensorNode &wildcardCensor, const CExtractOptions &options, IOpenCallbackUI *openCallback, IExtractCallbackUI *extractCallback, #ifndef _SFX IHashCalc *hash, #endif UString &errorMessage, CDecompressStat &stat) { stat.Clear(); UInt64 totalPackSize = 0; CRecordVector arcSizes; unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size(); unsigned i; for (i = 0; i < numArcs; i++) { NFind::CFileInfo fi; fi.Size = 0; if (!options.StdInMode) { const FString &arcPath = us2fs(arcPaths[i]); if (!fi.Find(arcPath)) throw "there is no such archive"; if (fi.IsDir()) throw "can't decompress folder"; } arcSizes.Add(fi.Size); totalPackSize += fi.Size; } CBoolArr skipArcs(numArcs); for (i = 0; i < numArcs; i++) skipArcs[i] = false; CArchiveExtractCallback *ecs = new CArchiveExtractCallback; CMyComPtr ec(ecs); bool multi = (numArcs > 1); ecs->InitForMulti(multi, options.PathMode, options.OverwriteMode); #ifndef _SFX ecs->SetHashMethods(hash); #endif if (multi) { RINOK(extractCallback->SetTotal(totalPackSize)); } UInt64 totalPackProcessed = 0; bool thereAreNotOpenArcs = false; for (i = 0; i < numArcs; i++) { if (skipArcs[i]) continue; const UString &arcPath = arcPaths[i]; NFind::CFileInfo fi; if (options.StdInMode) { fi.Size = 0; fi.Attrib = 0; } else { if (!fi.Find(us2fs(arcPath)) || fi.IsDir()) throw "there is no such archive"; } #ifndef _NO_CRYPTO openCallback->Open_ClearPasswordWasAskedFlag(); #endif RINOK(extractCallback->BeforeOpen(arcPath)); CArchiveLink arcLink; CObjectVector types2 = types; /* #ifndef _SFX if (types.IsEmpty()) { int pos = arcPath.ReverseFind(L'.'); if (pos >= 0) { UString s = arcPath.Ptr(pos + 1); int index = codecs->FindFormatForExtension(s); if (index >= 0 && s == L"001") { s = arcPath.Left(pos); pos = s.ReverseFind(L'.'); if (pos >= 0) { int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1)); if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0 { types2.Add(index2); types2.Add(index); } } } } } #endif */ COpenOptions op; #ifndef _SFX op.props = &options.Properties; #endif op.codecs = codecs; op.types = &types2; op.excludedFormats = &excludedFormats; op.stdInMode = options.StdInMode; op.stream = NULL; op.filePath = arcPath; HRESULT result = arcLink.Open2(op, openCallback); if (result == E_ABORT) return result; bool crypted = false; #ifndef _NO_CRYPTO crypted = openCallback->Open_WasPasswordAsked(); #endif if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0) result = S_FALSE; // arcLink.Set_ErrorsText(); RINOK(extractCallback->OpenResult(arcPath, result, crypted)); { FOR_VECTOR (r, arcLink.Arcs) { const CArc &arc = arcLink.Arcs[r]; const CArcErrorInfo &er = arc.ErrorInfo; if (er.IsThereErrorOrWarning()) { RINOK(extractCallback->SetError(r, arc.Path, er.GetErrorFlags(), er.ErrorMessage, er.GetWarningFlags(), er.WarningMessage)); } } } if (result != S_OK) { thereAreNotOpenArcs = true; if (!options.StdInMode) { NFind::CFileInfo fi; if (fi.Find(us2fs(arcPath))) if (!fi.IsDir()) totalPackProcessed += fi.Size; } continue; } if (!options.StdInMode) { // numVolumes += arcLink.VolumePaths.Size(); // arcLink.VolumesSize; // totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes); // numArcs = arcPaths.Size(); if (arcLink.VolumePaths.Size() != 0) { Int64 correctionSize = arcLink.VolumesSize; FOR_VECTOR (v, arcLink.VolumePaths) { int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]); if (index >= 0) { if ((unsigned)index > i) { skipArcs[index] = true; correctionSize -= arcSizes[index]; } } } if (correctionSize != 0) { Int64 newPackSize = (Int64)totalPackSize + correctionSize; if (newPackSize < 0) newPackSize = 0; totalPackSize = newPackSize; RINOK(extractCallback->SetTotal(totalPackSize)); } } } #ifndef _NO_CRYPTO bool passwordIsDefined; UString password; RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password)); if (passwordIsDefined) { RINOK(extractCallback->SetPassword(password)); } #endif FOR_VECTOR (k, arcLink.Arcs) { const CArc &arc = arcLink.Arcs[k]; const CArcErrorInfo &er = arc.ErrorInfo; if (er.ErrorFormatIndex >= 0) { RINOK(extractCallback->OpenTypeWarning(arc.Path, codecs->GetFormatNamePtr(arc.FormatIndex), codecs->GetFormatNamePtr(er.ErrorFormatIndex))) /* UString s = L"Can not open the file as [" + codecs->Formats[arc.ErrorFormatIndex].Name + L"] archive\n"; s += L"The file is open as [" + codecs->Formats[arc.FormatIndex].Name + L"] archive"; RINOK(extractCallback->MessageError(s)); */ } { const UString &s = er.ErrorMessage; if (!s.IsEmpty()) { RINOK(extractCallback->MessageError(s)); } } } CArc &arc = arcLink.Arcs.Back(); arc.MTimeDefined = (!options.StdInMode && !fi.IsDevice); arc.MTime = fi.MTime; UInt64 packProcessed; bool calcCrc = #ifndef _SFX (hash != NULL); #else false; #endif RINOK(DecompressArchive( codecs, arcLink, fi.Size + arcLink.VolumesSize, wildcardCensor, options, calcCrc, extractCallback, ecs, errorMessage, packProcessed)); if (!options.StdInMode) packProcessed = fi.Size + arcLink.VolumesSize; totalPackProcessed += packProcessed; ecs->LocalProgressSpec->InSize += packProcessed; ecs->LocalProgressSpec->OutSize = ecs->UnpackSize; if (!errorMessage.IsEmpty()) return E_FAIL; } if (multi || thereAreNotOpenArcs) { RINOK(extractCallback->SetTotal(totalPackSize)); RINOK(extractCallback->SetCompleted(&totalPackProcessed)); } stat.NumFolders = ecs->NumFolders; stat.NumFiles = ecs->NumFiles; stat.NumAltStreams = ecs->NumAltStreams; stat.UnpackSize = ecs->UnpackSize; stat.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize; stat.NumArchives = arcPaths.Size(); stat.PackSize = ecs->LocalProgressSpec->InSize; return S_OK; }