// ArchiveExtractCallback.cpp #include "StdAfx.h" #undef sprintf #undef printf // #include // #include "../../../../C/CpuTicks.h" #include "../../../../C/Alloc.h" #include "../../../../C/CpuArch.h" #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" #include "../../../Common/StringConvert.h" #include "../../../Common/UTFConvert.h" #include "../../../Common/Wildcard.h" #include "../../../Windows/ErrorMsg.h" #include "../../../Windows/FileDir.h" #include "../../../Windows/FileFind.h" #include "../../../Windows/FileName.h" #include "../../../Windows/PropVariant.h" #include "../../../Windows/PropVariantConv.h" #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) #define Z7_USE_SECURITY_CODE #include "../../../Windows/SecurityUtils.h" #endif #include "../../Common/FilePathAutoRename.h" #include "../../Common/StreamUtils.h" #include "../Common/ExtractingFilePath.h" #include "../Common/PropIDUtils.h" #include "ArchiveExtractCallback.h" using namespace NWindows; using namespace NFile; using namespace NDir; static const char * const kCantAutoRename = "Cannot create file with auto name"; static const char * const kCantRenameFile = "Cannot rename existing file"; static const char * const kCantDeleteOutputFile = "Cannot delete output file"; static const char * const kCantDeleteOutputDir = "Cannot delete output folder"; static const char * const kCantOpenOutFile = "Cannot open output file"; static const char * const kCantOpenInFile = "Cannot open input file"; static const char * const kCantSetFileLen = "Cannot set length for output file"; #ifdef SUPPORT_LINKS static const char * const kCantCreateHardLink = "Cannot create hard link"; static const char * const kCantCreateSymLink = "Cannot create symbolic link"; #endif #ifndef Z7_SFX Z7_COM7F_IMF(COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) result = _stream->Write(data, size, &size); if (_calculate) _hash->Update(data, size); _size += size; if (processedSize) *processedSize = size; return result; } #endif // Z7_SFX #ifdef Z7_USE_SECURITY_CODE bool InitLocalPrivileges(); bool InitLocalPrivileges() { NSecurity::CAccessToken token; if (!token.OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES)) return false; TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!::LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) return false; if (!token.AdjustPrivileges(&tp)) return false; return (GetLastError() == ERROR_SUCCESS); } #endif // Z7_USE_SECURITY_CODE #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) static const char * const kOfficeExtensions = " doc dot wbk" " docx docm dotx dotm docb wll wwl" " xls xlt xlm" " xlsx xlsm xltx xltm xlsb xla xlam" " ppt pot pps ppa ppam" " pptx pptm potx potm ppam ppsx ppsm sldx sldm" " "; static bool FindExt2(const char *p, const UString &name) { const int pathPos = name.ReverseFind_PathSepar(); const int dotPos = name.ReverseFind_Dot(); if (dotPos < 0 || dotPos < pathPos || dotPos == (int)name.Len() - 1) return false; AString s; for (unsigned pos = (unsigned)(dotPos + 1);; pos++) { const wchar_t c = name[pos]; if (c <= 0) break; if (c >= 0x80) return false; s += (char)MyCharLower_Ascii((char)c); } for (unsigned i = 0; p[i] != 0;) { unsigned j; for (j = i; p[j] != ' '; j++); if (s.Len() == j - i && memcmp(p + i, (const char *)s, s.Len()) == 0) return true; i = j + 1; } return false; } static const FChar * const k_ZoneId_StreamName = FTEXT(":Zone.Identifier"); void ReadZoneFile_Of_BaseFile(CFSTR fileName2, CByteBuffer &buf) { FString fileName (fileName2); fileName += k_ZoneId_StreamName; buf.Free(); NIO::CInFile file; if (!file.Open(fileName)) return; UInt64 fileSize; if (!file.GetLength(fileSize)) return; if (fileSize == 0 || fileSize >= ((UInt32)1 << 16)) return; buf.Alloc((size_t)fileSize); size_t processed; if (file.ReadFull(buf, (size_t)fileSize, processed) && processed == fileSize) return; buf.Free(); } static bool WriteZoneFile(CFSTR fileName, const CByteBuffer &buf) { NIO::COutFile file; if (!file.Create(fileName, true)) return false; return file.WriteFull(buf, buf.Size()); } #endif #ifdef SUPPORT_LINKS int CHardLinkNode::Compare(const CHardLinkNode &a) const { if (StreamId < a.StreamId) return -1; if (StreamId > a.StreamId) return 1; return MyCompare(INode, a.INode); } static HRESULT Archive_Get_HardLinkNode(IInArchive *archive, UInt32 index, CHardLinkNode &h, bool &defined) { h.INode = 0; h.StreamId = (UInt64)(Int64)-1; defined = false; { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidINode, &prop)) if (!ConvertPropVariantToUInt64(prop, h.INode)) return S_OK; } { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidStreamId, &prop)) ConvertPropVariantToUInt64(prop, h.StreamId); } defined = true; return S_OK; } HRESULT CArchiveExtractCallback::PrepareHardLinks(const CRecordVector *realIndices) { _hardLinks.Clear(); if (!_arc->Ask_INode) return S_OK; IInArchive *archive = _arc->Archive; CRecordVector &hardIDs = _hardLinks.IDs; { UInt32 numItems; if (realIndices) numItems = realIndices->Size(); else { RINOK(archive->GetNumberOfItems(&numItems)) } for (UInt32 i = 0; i < numItems; i++) { CHardLinkNode h; bool defined; const UInt32 realIndex = realIndices ? (*realIndices)[i] : i; RINOK(Archive_Get_HardLinkNode(archive, realIndex, h, defined)) if (defined) { bool isAltStream = false; RINOK(Archive_IsItem_AltStream(archive, realIndex, isAltStream)) if (!isAltStream) { bool isDir = false; RINOK(Archive_IsItem_Dir(archive, realIndex, isDir)) if (!isDir) hardIDs.Add(h); } } } } hardIDs.Sort2(); { // we keep only items that have 2 or more items unsigned k = 0; unsigned numSame = 1; for (unsigned i = 1; i < hardIDs.Size(); i++) { if (hardIDs[i].Compare(hardIDs[i - 1]) != 0) numSame = 1; else if (++numSame == 2) { if (i - 1 != k) hardIDs[k] = hardIDs[i - 1]; k++; } } hardIDs.DeleteFrom(k); } _hardLinks.PrepareLinks(); return S_OK; } #endif // SUPPORT_LINKS CArchiveExtractCallback::CArchiveExtractCallback(): _arc(NULL), Write_CTime(true), Write_ATime(true), Write_MTime(true), _multiArchives(false) { LocalProgressSpec = new CLocalProgress(); _localProgress = LocalProgressSpec; #ifdef Z7_USE_SECURITY_CODE _saclEnabled = InitLocalPrivileges(); #endif } void CArchiveExtractCallback::InitBeforeNewArchive() { #if defined(_WIN32) && !defined(UNDER_CE) ZoneBuf.Free(); #endif } void CArchiveExtractCallback::Init( const CExtractNtOptions &ntOptions, const NWildcard::CCensorNode *wildcardCensor, const CArc *arc, IFolderArchiveExtractCallback *extractCallback2, bool stdOutMode, bool testMode, const FString &directoryPath, const UStringVector &removePathParts, bool removePartsForAltStreams, UInt64 packSize) { ClearExtractedDirsInfo(); _outFileStream.Release(); _bufPtrSeqOutStream.Release(); #ifdef SUPPORT_LINKS _hardLinks.Clear(); #endif #ifdef SUPPORT_ALT_STREAMS _renamedFiles.Clear(); #endif _ntOptions = ntOptions; _wildcardCensor = wildcardCensor; _stdOutMode = stdOutMode; _testMode = testMode; // _progressTotal = 0; // _progressTotal_Defined = false; _packTotal = packSize; _progressTotal = packSize; _progressTotal_Defined = true; _extractCallback2 = extractCallback2; /* _compressProgress.Release(); _extractCallback2.QueryInterface(IID_ICompressProgressInfo, &_compressProgress); _callbackMessage.Release(); _extractCallback2.QueryInterface(IID_IArchiveExtractCallbackMessage2, &_callbackMessage); */ _folderArchiveExtractCallback2.Release(); _extractCallback2.QueryInterface(IID_IFolderArchiveExtractCallback2, &_folderArchiveExtractCallback2); #ifndef Z7_SFX ExtractToStreamCallback.Release(); _extractCallback2.QueryInterface(IID_IFolderExtractToStreamCallback, &ExtractToStreamCallback); if (ExtractToStreamCallback) { Int32 useStreams = 0; if (ExtractToStreamCallback->UseExtractToStream(&useStreams) != S_OK) useStreams = 0; if (useStreams == 0) ExtractToStreamCallback.Release(); } #endif LocalProgressSpec->Init(extractCallback2, true); LocalProgressSpec->SendProgress = false; _removePathParts = removePathParts; _removePartsForAltStreams = removePartsForAltStreams; #ifndef Z7_SFX _baseParentFolder = (UInt32)(Int32)-1; _use_baseParentFolder_mode = false; #endif _arc = arc; _dirPathPrefix = directoryPath; _dirPathPrefix_Full = directoryPath; #if defined(_WIN32) && !defined(UNDER_CE) if (!NName::IsAltPathPrefix(_dirPathPrefix)) #endif { NName::NormalizeDirPathPrefix(_dirPathPrefix); NDir::MyGetFullPathName(directoryPath, _dirPathPrefix_Full); NName::NormalizeDirPathPrefix(_dirPathPrefix_Full); } } Z7_COM7F_IMF(CArchiveExtractCallback::SetTotal(UInt64 size)) { COM_TRY_BEGIN _progressTotal = size; _progressTotal_Defined = true; if (!_multiArchives && _extractCallback2) return _extractCallback2->SetTotal(size); return S_OK; COM_TRY_END } static void NormalizeVals(UInt64 &v1, UInt64 &v2) { const UInt64 kMax = (UInt64)1 << 31; while (v1 > kMax) { v1 >>= 1; v2 >>= 1; } } static UInt64 MyMultDiv64(UInt64 unpCur, UInt64 unpTotal, UInt64 packTotal) { NormalizeVals(packTotal, unpTotal); NormalizeVals(unpCur, unpTotal); if (unpTotal == 0) unpTotal = 1; return unpCur * packTotal / unpTotal; } Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)) { COM_TRY_BEGIN if (!_extractCallback2) return S_OK; UInt64 packCur; if (_multiArchives) { packCur = LocalProgressSpec->InSize; if (completeValue && _progressTotal_Defined) packCur += MyMultDiv64(*completeValue, _progressTotal, _packTotal); completeValue = &packCur; } return _extractCallback2->SetCompleted(completeValue); COM_TRY_END } Z7_COM7F_IMF(CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { COM_TRY_BEGIN return _localProgress->SetRatioInfo(inSize, outSize); COM_TRY_END } void CArchiveExtractCallback::CreateComplexDirectory(const UStringVector &dirPathParts, FString &fullPath) { // we use (_item.IsDir) in this function bool isAbsPath = false; if (!dirPathParts.IsEmpty()) { const UString &s = dirPathParts[0]; if (s.IsEmpty()) isAbsPath = true; #if defined(_WIN32) && !defined(UNDER_CE) else { if (NName::IsDrivePath2(s)) isAbsPath = true; } #endif } if (_pathMode == NExtract::NPathMode::kAbsPaths && isAbsPath) fullPath.Empty(); else fullPath = _dirPathPrefix; FOR_VECTOR (i, dirPathParts) { if (i != 0) fullPath.Add_PathSepar(); const UString &s = dirPathParts[i]; fullPath += us2fs(s); const bool isFinalDir = (i == dirPathParts.Size() - 1 && _item.IsDir); if (fullPath.IsEmpty()) { if (isFinalDir) _itemFailure = true; continue; } #if defined(_WIN32) && !defined(UNDER_CE) if (_pathMode == NExtract::NPathMode::kAbsPaths) if (i == 0 && s.Len() == 2 && NName::IsDrivePath2(s)) { if (isFinalDir) { // we don't want to call SetAttrib() for root drive path _itemFailure = true; } continue; } #endif // bool res = CreateDir(fullPath); // if (!res) if (isFinalDir) { if (!NFile::NFind::DoesDirExist(fullPath)) { _itemFailure = true; SendMessageError("Cannot create folder", fullPath); // SendMessageError_with_LastError() } } } } HRESULT CArchiveExtractCallback::GetTime(UInt32 index, PROPID propID, CArcTime &ft) { ft.Clear(); NCOM::CPropVariant prop; RINOK(_arc->Archive->GetProperty(index, propID, &prop)) if (prop.vt == VT_FILETIME) ft.Set_From_Prop(prop); else if (prop.vt != VT_EMPTY) return E_FAIL; return S_OK; } HRESULT CArchiveExtractCallback::GetUnpackSize() { return _arc->GetItem_Size(_index, _curSize, _curSize_Defined); } static void AddPathToMessage(UString &s, const FString &path) { s += " : "; s += fs2us(path); } HRESULT CArchiveExtractCallback::SendMessageError(const char *message, const FString &path) { UString s (message); AddPathToMessage(s, path); return _extractCallback2->MessageError(s); } HRESULT CArchiveExtractCallback::SendMessageError_with_LastError(const char *message, const FString &path) { DWORD errorCode = GetLastError(); if (errorCode == 0) errorCode = (DWORD)E_FAIL; UString s (message); { s += " : "; s += NError::MyFormatMessage(errorCode); } AddPathToMessage(s, path); return _extractCallback2->MessageError(s); } HRESULT CArchiveExtractCallback::SendMessageError2(HRESULT errorCode, const char *message, const FString &path1, const FString &path2) { UString s (message); if (errorCode != 0) { s += " : "; s += NError::MyFormatMessage(errorCode); } AddPathToMessage(s, path1); AddPathToMessage(s, path2); return _extractCallback2->MessageError(s); } #ifndef Z7_SFX Z7_COM7F_IMF(CGetProp::GetProp(PROPID propID, PROPVARIANT *value)) { /* if (propID == kpidName) { COM_TRY_BEGIN NCOM::CPropVariant prop = Name; prop.Detach(value); return S_OK; COM_TRY_END } */ return Arc->Archive->GetProperty(IndexInArc, propID, value); } #endif // Z7_SFX #ifdef SUPPORT_LINKS static UString GetDirPrefixOf(const UString &src) { UString s (src); if (!s.IsEmpty()) { if (IsPathSepar(s.Back())) s.DeleteBack(); int pos = s.ReverseFind_PathSepar(); s.DeleteFrom((unsigned)(pos + 1)); } return s; } #endif // SUPPORT_LINKS struct CLinkLevelsInfo { bool IsAbsolute; int LowLevel; int FinalLevel; void Parse(const UString &path); }; void CLinkLevelsInfo::Parse(const UString &path) { IsAbsolute = NName::IsAbsolutePath(path); LowLevel = 0; FinalLevel = 0; UStringVector parts; SplitPathToParts(path, parts); int level = 0; FOR_VECTOR (i, parts) { const UString &s = parts[i]; if (s.IsEmpty()) { if (i == 0) IsAbsolute = true; continue; } if (s == L".") continue; if (s == L"..") { level--; if (LowLevel > level) LowLevel = level; } else level++; } FinalLevel = level; } bool IsSafePath(const UString &path); bool IsSafePath(const UString &path) { CLinkLevelsInfo levelsInfo; levelsInfo.Parse(path); return !levelsInfo.IsAbsolute && levelsInfo.LowLevel >= 0 && levelsInfo.FinalLevel > 0; } bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include); bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include) { bool found = false; // CheckPathVect() doesn't check path to Parent nodes if (node.CheckPathVect(item.PathParts, !item.MainIsDir, include)) { if (!include) return true; #ifdef SUPPORT_ALT_STREAMS if (!item.IsAltStream) return true; #endif found = true; } #ifdef SUPPORT_ALT_STREAMS if (!item.IsAltStream) return false; UStringVector pathParts2 = item.PathParts; if (pathParts2.IsEmpty()) pathParts2.AddNew(); UString &back = pathParts2.Back(); back += ':'; back += item.AltStreamName; bool include2; if (node.CheckPathVect(pathParts2, true, // isFile, include2)) { include = include2; return true; } #endif // SUPPORT_ALT_STREAMS return found; } bool CensorNode_CheckPath(const NWildcard::CCensorNode &node, const CReadArcItem &item) { bool include; if (CensorNode_CheckPath2(node, item, include)) return include; return false; } static FString MakePath_from_2_Parts(const FString &prefix, const FString &path) { FString s (prefix); #if defined(_WIN32) && !defined(UNDER_CE) if (!path.IsEmpty() && path[0] == ':' && !prefix.IsEmpty() && IsPathSepar(prefix.Back())) { if (!NName::IsDriveRootPath_SuperAllowed(prefix)) s.DeleteBack(); } #endif s += path; return s; } #ifdef SUPPORT_LINKS /* struct CTempMidBuffer { void *Buf; CTempMidBuffer(size_t size): Buf(NULL) { Buf = ::MidAlloc(size); } ~CTempMidBuffer() { ::MidFree(Buf); } }; HRESULT CArchiveExtractCallback::MyCopyFile(ISequentialOutStream *outStream) { const size_t kBufSize = 1 << 16; CTempMidBuffer buf(kBufSize); if (!buf.Buf) return E_OUTOFMEMORY; NIO::CInFile inFile; NIO::COutFile outFile; if (!inFile.Open(_copyFile_Path)) return SendMessageError_with_LastError("Open error", _copyFile_Path); for (;;) { UInt32 num; if (!inFile.Read(buf.Buf, kBufSize, num)) return SendMessageError_with_LastError("Read error", _copyFile_Path); if (num == 0) return S_OK; RINOK(WriteStream(outStream, buf.Buf, num)); } } */ HRESULT CArchiveExtractCallback::ReadLink() { IInArchive *archive = _arc->Archive; const UInt32 index = _index; _link.Clear(); { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidHardLink, &prop)) if (prop.vt == VT_BSTR) { _link.isHardLink = true; // _link.isCopyLink = false; _link.isRelative = false; // RAR5, TAR: hard links are from root folder of archive _link.linkPath.SetFromBstr(prop.bstrVal); } else if (prop.vt != VT_EMPTY) return E_FAIL; } /* { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidCopyLink, &prop)); if (prop.vt == VT_BSTR) { _link.isHardLink = false; _link.isCopyLink = true; _link.isRelative = false; // RAR5: copy links are from root folder of archive _link.linkPath.SetFromBstr(prop.bstrVal); } else if (prop.vt != VT_EMPTY) return E_FAIL; } */ { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidSymLink, &prop)) if (prop.vt == VT_BSTR) { _link.isHardLink = false; // _link.isCopyLink = false; _link.isRelative = true; // RAR5, TAR: symbolic links can be relative _link.linkPath.SetFromBstr(prop.bstrVal); } else if (prop.vt != VT_EMPTY) return E_FAIL; } NtReparse_Data = NULL; NtReparse_Size = 0; if (_link.linkPath.IsEmpty() && _arc->GetRawProps) { const void *data; UInt32 dataSize; UInt32 propType; _arc->GetRawProps->GetRawProp(_index, kpidNtReparse, &data, &dataSize, &propType); // if (dataSize == 1234567) // for debug: unpacking without reparse if (dataSize != 0) { if (propType != NPropDataType::kRaw) return E_FAIL; // 21.06: we need kpidNtReparse in linux for wim archives created in Windows // #ifdef _WIN32 NtReparse_Data = data; NtReparse_Size = dataSize; CReparseAttr reparse; bool isOkReparse = reparse.Parse((const Byte *)data, dataSize); if (isOkReparse) { _link.isHardLink = false; // _link.isCopyLink = false; _link.linkPath = reparse.GetPath(); _link.isJunction = reparse.IsMountPoint(); if (reparse.IsSymLink_WSL()) { _link.isWSL = true; _link.isRelative = reparse.IsRelative_WSL(); } else _link.isRelative = reparse.IsRelative_Win(); // const AString s = GetAnsiString(_link.linkPath); // printf("\n_link.linkPath: %s\n", s.Ptr()); #ifndef _WIN32 _link.linkPath.Replace(L'\\', WCHAR_PATH_SEPARATOR); #endif } // #endif } } if (_link.linkPath.IsEmpty()) return S_OK; { #ifdef _WIN32 _link.linkPath.Replace(L'/', WCHAR_PATH_SEPARATOR); #endif // rar5 uses "\??\" prefix for absolute links if (_link.linkPath.IsPrefixedBy(WSTRING_PATH_SEPARATOR L"??" WSTRING_PATH_SEPARATOR)) { _link.isRelative = false; _link.linkPath.DeleteFrontal(4); } for (;;) // while (NName::IsAbsolutePath(linkPath)) { unsigned n = NName::GetRootPrefixSize(_link.linkPath); if (n == 0) break; _link.isRelative = false; _link.linkPath.DeleteFrontal(n); } } if (_link.linkPath.IsEmpty()) return S_OK; if (!_link.isRelative && _removePathParts.Size() != 0) { UStringVector pathParts; SplitPathToParts(_link.linkPath, pathParts); bool badPrefix = false; FOR_VECTOR (i, _removePathParts) { if (CompareFileNames(_removePathParts[i], pathParts[i]) != 0) { badPrefix = true; break; } } if (!badPrefix) pathParts.DeleteFrontal(_removePathParts.Size()); _link.linkPath = MakePathFromParts(pathParts); } /* if (!_link.linkPath.IsEmpty()) { printf("\n_link %s to -> %s\n", GetOemString(_item.Path).Ptr(), GetOemString(_link.linkPath).Ptr()); } */ return S_OK; } #endif // SUPPORT_LINKS #ifndef _WIN32 static HRESULT GetOwner(IInArchive *archive, UInt32 index, UInt32 pidName, UInt32 pidId, COwnerInfo &res) { { NWindows::NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, pidId, &prop)) if (prop.vt == VT_UI4) { res.Id_Defined = true; res.Id = prop.ulVal; // for debug // res.Id++; // for debug // if (pidId == kpidGroupId) res.Id += 7; // for debug // res.Id = 0; // for debug } else if (prop.vt != VT_EMPTY) return E_INVALIDARG; } { NWindows::NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, pidName, &prop)) if (prop.vt == VT_BSTR) { const UString s = prop.bstrVal; ConvertUnicodeToUTF8(s, res.Name); } else if (prop.vt == VT_UI4) { res.Id_Defined = true; res.Id = prop.ulVal; } else if (prop.vt != VT_EMPTY) return E_INVALIDARG; } return S_OK; } #endif HRESULT CArchiveExtractCallback::Read_fi_Props() { IInArchive *archive = _arc->Archive; const UInt32 index = _index; _fi.Attrib_Defined = false; #ifndef _WIN32 _fi.Owner.Clear(); _fi.Group.Clear(); #endif { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidPosixAttrib, &prop)) if (prop.vt == VT_UI4) { _fi.SetFromPosixAttrib(prop.ulVal); } else if (prop.vt != VT_EMPTY) return E_FAIL; } { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidAttrib, &prop)) if (prop.vt == VT_UI4) { _fi.Attrib = prop.ulVal; _fi.Attrib_Defined = true; } else if (prop.vt != VT_EMPTY) return E_FAIL; } RINOK(GetTime(index, kpidCTime, _fi.CTime)) RINOK(GetTime(index, kpidATime, _fi.ATime)) RINOK(GetTime(index, kpidMTime, _fi.MTime)) #ifndef _WIN32 if (_ntOptions.ExtractOwner) { // SendMessageError_with_LastError("_ntOptions.ExtractOwner", _diskFilePath); GetOwner(archive, index, kpidUser, kpidUserId, _fi.Owner); GetOwner(archive, index, kpidGroup, kpidGroupId, _fi.Group); } #endif return S_OK; } void CArchiveExtractCallback::CorrectPathParts() { UStringVector &pathParts = _item.PathParts; #ifdef SUPPORT_ALT_STREAMS if (!_item.IsAltStream || !pathParts.IsEmpty() || !(_removePartsForAltStreams || _pathMode == NExtract::NPathMode::kNoPathsAlt)) #endif Correct_FsPath(_pathMode == NExtract::NPathMode::kAbsPaths, _keepAndReplaceEmptyDirPrefixes, pathParts, _item.MainIsDir); #ifdef SUPPORT_ALT_STREAMS if (_item.IsAltStream) { UString s (_item.AltStreamName); Correct_AltStream_Name(s); bool needColon = true; if (pathParts.IsEmpty()) { pathParts.AddNew(); if (_removePartsForAltStreams || _pathMode == NExtract::NPathMode::kNoPathsAlt) needColon = false; } #ifdef _WIN32 else if (_pathMode == NExtract::NPathMode::kAbsPaths && NWildcard::GetNumPrefixParts_if_DrivePath(pathParts) == pathParts.Size()) pathParts.AddNew(); #endif UString &name = pathParts.Back(); if (needColon) name += (char)(_ntOptions.ReplaceColonForAltStream ? '_' : ':'); name += s; } #endif // SUPPORT_ALT_STREAMS } void CArchiveExtractCallback::GetFiTimesCAM(CFiTimesCAM &pt) { pt.CTime_Defined = false; pt.ATime_Defined = false; pt.MTime_Defined = false; if (Write_MTime) { if (_fi.MTime.Def) { _fi.MTime.Write_To_FiTime(pt.MTime); pt.MTime_Defined = true; } else if (_arc->MTime.Def) { _arc->MTime.Write_To_FiTime(pt.MTime); pt.MTime_Defined = true; } } if (Write_CTime && _fi.CTime.Def) { _fi.CTime.Write_To_FiTime(pt.CTime); pt.CTime_Defined = true; } if (Write_ATime && _fi.ATime.Def) { _fi.ATime.Write_To_FiTime(pt.ATime); pt.ATime_Defined = true; } } void CArchiveExtractCallback::CreateFolders() { // 21.04 : we don't change original (_item.PathParts) here UStringVector pathParts = _item.PathParts; if (!pathParts.IsEmpty()) { /* v23: if we extract symlink, and we know that it links to dir: Linux: we don't create dir item (symlink_from_path) here. Windows: SetReparseData() will create dir item, if it doesn't exist, but if we create dir item here, it's not problem. */ if (!_item.IsDir #ifdef SUPPORT_LINKS #ifndef WIN32 || !_link.linkPath.IsEmpty() #endif #endif ) pathParts.DeleteBack(); } if (pathParts.IsEmpty()) return; FString fullPathNew; CreateComplexDirectory(pathParts, fullPathNew); if (!_item.IsDir) return; if (_itemFailure) return; CDirPathTime pt; GetFiTimesCAM(pt); if (pt.IsSomeTimeDefined()) { pt.Path = fullPathNew; pt.SetDirTime(); _extractedFolders.Add(pt); } } /* CheckExistFile(fullProcessedPath) it can change: fullProcessedPath, _isRenamed, _overwriteMode (needExit = true) means that we must exit GetStream() even for S_OK result. */ HRESULT CArchiveExtractCallback::CheckExistFile(FString &fullProcessedPath, bool &needExit) { needExit = true; // it was set already before NFind::CFileInfo fileInfo; if (fileInfo.Find(fullProcessedPath)) { if (_overwriteMode == NExtract::NOverwriteMode::kSkip) return S_OK; if (_overwriteMode == NExtract::NOverwriteMode::kAsk) { const int slashPos = fullProcessedPath.ReverseFind_PathSepar(); const FString realFullProcessedPath = fullProcessedPath.Left((unsigned)(slashPos + 1)) + fileInfo.Name; /* (fileInfo) can be symbolic link. we can show final file properties here. */ FILETIME ft1; FiTime_To_FILETIME(fileInfo.MTime, ft1); Int32 overwriteResult; RINOK(_extractCallback2->AskOverwrite( fs2us(realFullProcessedPath), &ft1, &fileInfo.Size, _item.Path, _fi.MTime.Def ? &_fi.MTime.FT : NULL, _curSize_Defined ? &_curSize : NULL, &overwriteResult)) switch (overwriteResult) { 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; } } // NExtract::NOverwriteMode::kAsk if (_overwriteMode == NExtract::NOverwriteMode::kRename) { if (!AutoRenamePath(fullProcessedPath)) { RINOK(SendMessageError(kCantAutoRename, fullProcessedPath)) return E_FAIL; } _isRenamed = true; } else if (_overwriteMode == NExtract::NOverwriteMode::kRenameExisting) { FString existPath (fullProcessedPath); if (!AutoRenamePath(existPath)) { RINOK(SendMessageError(kCantAutoRename, fullProcessedPath)) return E_FAIL; } // MyMoveFile can rename folders. So it's OK to use it for folders too if (!MyMoveFile(fullProcessedPath, existPath)) { HRESULT errorCode = GetLastError_noZero_HRESULT(); RINOK(SendMessageError2(errorCode, kCantRenameFile, existPath, fullProcessedPath)) return E_FAIL; } } else // not Rename* { if (fileInfo.IsDir()) { // do we need to delete all files in folder? if (!RemoveDir(fullProcessedPath)) { RINOK(SendMessageError_with_LastError(kCantDeleteOutputDir, fullProcessedPath)) return S_OK; } } else // fileInfo is not Dir { if (NFind::DoesFileExist_Raw(fullProcessedPath)) if (!DeleteFileAlways(fullProcessedPath)) if (GetLastError() != ERROR_FILE_NOT_FOUND) // check it in linux { RINOK(SendMessageError_with_LastError(kCantDeleteOutputFile, fullProcessedPath)) return S_OK; // return E_FAIL; } } // fileInfo is not Dir } // not Rename* } else // not Find(fullProcessedPath) { #if defined(_WIN32) && !defined(UNDER_CE) // we need to clear READ-ONLY of parent before creating alt stream int colonPos = NName::FindAltStreamColon(fullProcessedPath); if (colonPos >= 0 && fullProcessedPath[(unsigned)colonPos + 1] != 0) { FString parentFsPath (fullProcessedPath); parentFsPath.DeleteFrom((unsigned)colonPos); NFind::CFileInfo parentFi; if (parentFi.Find(parentFsPath)) { if (parentFi.IsReadOnly()) SetFileAttrib(parentFsPath, parentFi.Attrib & ~(DWORD)FILE_ATTRIBUTE_READONLY); } } #endif // defined(_WIN32) && !defined(UNDER_CE) } needExit = false; return S_OK; } HRESULT CArchiveExtractCallback::GetExtractStream(CMyComPtr &outStreamLoc, bool &needExit) { needExit = true; RINOK(Read_fi_Props()) #ifdef SUPPORT_LINKS IInArchive *archive = _arc->Archive; #endif const UInt32 index = _index; bool isAnti = false; RINOK(_arc->IsItem_Anti(index, isAnti)) CorrectPathParts(); UString processedPath (MakePathFromParts(_item.PathParts)); if (!isAnti) { // 21.04: CreateFolders doesn't change (_item.PathParts) CreateFolders(); } FString fullProcessedPath (us2fs(processedPath)); if (_pathMode != NExtract::NPathMode::kAbsPaths || !NName::IsAbsolutePath(processedPath)) { fullProcessedPath = MakePath_from_2_Parts(_dirPathPrefix, fullProcessedPath); } #ifdef SUPPORT_ALT_STREAMS if (_item.IsAltStream && _item.ParentIndex != (UInt32)(Int32)-1) { const int renIndex = _renamedFiles.FindInSorted(CIndexToPathPair(_item.ParentIndex)); if (renIndex != -1) { const CIndexToPathPair &pair = _renamedFiles[(unsigned)renIndex]; fullProcessedPath = pair.Path; fullProcessedPath += ':'; UString s (_item.AltStreamName); Correct_AltStream_Name(s); fullProcessedPath += us2fs(s); } } #endif // SUPPORT_ALT_STREAMS if (_item.IsDir) { _diskFilePath = fullProcessedPath; if (isAnti) RemoveDir(_diskFilePath); #ifdef SUPPORT_LINKS if (_link.linkPath.IsEmpty()) #endif { if (!isAnti) SetAttrib(); return S_OK; } } else if (!_isSplit) { RINOK(CheckExistFile(fullProcessedPath, needExit)) if (needExit) return S_OK; needExit = true; } _diskFilePath = fullProcessedPath; if (isAnti) { needExit = false; return S_OK; } // not anti #ifdef SUPPORT_LINKS if (!_link.linkPath.IsEmpty()) { #ifndef UNDER_CE { bool linkWasSet = false; RINOK(SetFromLinkPath(fullProcessedPath, _link, linkWasSet)) if (linkWasSet) { _isSymLinkCreated = _link.IsSymLink(); SetAttrib(); // printf("\nlinkWasSet %s\n", GetAnsiString(_diskFilePath)); } } #endif // UNDER_CE // if (_copyFile_Path.IsEmpty()) { needExit = false; return S_OK; } } if (!_hardLinks.IDs.IsEmpty() && !_item.IsAltStream && !_item.IsDir) { CHardLinkNode h; bool defined; RINOK(Archive_Get_HardLinkNode(archive, index, h, defined)) if (defined) { const int linkIndex = _hardLinks.IDs.FindInSorted2(h); if (linkIndex != -1) { FString &hl = _hardLinks.Links[(unsigned)linkIndex]; if (hl.IsEmpty()) hl = fullProcessedPath; else { if (!MyCreateHardLink(fullProcessedPath, hl)) { HRESULT errorCode = GetLastError_noZero_HRESULT(); RINOK(SendMessageError2(errorCode, kCantCreateHardLink, fullProcessedPath, hl)) return S_OK; } // printf("\nHard linkWasSet Archive_Get_HardLinkNode %s\n", GetAnsiString(_diskFilePath)); // _needSetAttrib = true; // do we need to set attribute ? SetAttrib(); needExit = false; return S_OK; } } } } #endif // SUPPORT_LINKS // ---------- CREATE WRITE FILE ----- _outFileStreamSpec = new COutFileStream; CMyComPtr outFileStream_Loc(_outFileStreamSpec); if (!_outFileStreamSpec->Open(fullProcessedPath, _isSplit ? OPEN_ALWAYS: CREATE_ALWAYS)) { // if (::GetLastError() != ERROR_FILE_EXISTS || !isSplit) { RINOK(SendMessageError_with_LastError(kCantOpenOutFile, fullProcessedPath)) return S_OK; } } _needSetAttrib = true; bool is_SymLink_in_Data = false; if (_curSize_Defined && _curSize > 0 && _curSize < (1 << 12)) { if (_fi.IsLinuxSymLink()) { is_SymLink_in_Data = true; _is_SymLink_in_Data_Linux = true; } else if (_fi.IsReparse()) { is_SymLink_in_Data = true; _is_SymLink_in_Data_Linux = false; } } if (is_SymLink_in_Data) { _outMemBuf.Alloc((size_t)_curSize); _bufPtrSeqOutStream_Spec = new CBufPtrSeqOutStream; _bufPtrSeqOutStream = _bufPtrSeqOutStream_Spec; _bufPtrSeqOutStream_Spec->Init(_outMemBuf, _outMemBuf.Size()); outStreamLoc = _bufPtrSeqOutStream; } else // not reprase { if (_ntOptions.PreAllocateOutFile && !_isSplit && _curSize_Defined && _curSize > (1 << 12)) { // UInt64 ticks = GetCpuTicks(); _fileLength_that_WasSet = _curSize; bool res = _outFileStreamSpec->File.SetLength(_curSize); _fileLength_WasSet = res; // ticks = GetCpuTicks() - ticks; // printf("\nticks = %10d\n", (unsigned)ticks); if (!res) { RINOK(SendMessageError_with_LastError(kCantSetFileLen, fullProcessedPath)) } /* _outFileStreamSpec->File.Close(); ticks = GetCpuTicks() - ticks; printf("\nticks = %10d\n", (unsigned)ticks); return S_FALSE; */ /* File.SetLength() on FAT (xp64): is fast, but then File.Close() can be slow, if we don't write any data. File.SetLength() for remote share file (exFAT) can be slow in some cases, and the Windows can return "network error" after 1 minute, while remote file still can grow. We need some way to detect such bad cases and disable PreAllocateOutFile mode. */ res = _outFileStreamSpec->SeekToBegin_bool(); if (!res) { RINOK(SendMessageError_with_LastError("Cannot seek to begin of file", fullProcessedPath)) } } // PreAllocateOutFile #ifdef SUPPORT_ALT_STREAMS if (_isRenamed && !_item.IsAltStream) { CIndexToPathPair pair(index, fullProcessedPath); unsigned oldSize = _renamedFiles.Size(); unsigned insertIndex = _renamedFiles.AddToUniqueSorted(pair); if (oldSize == _renamedFiles.Size()) _renamedFiles[insertIndex].Path = fullProcessedPath; } #endif // SUPPORT_ALT_STREAMS if (_isSplit) { RINOK(outFileStream_Loc->Seek((Int64)_position, STREAM_SEEK_SET, NULL)) } outStreamLoc = outFileStream_Loc; } // if not reprase _outFileStream = outFileStream_Loc; needExit = false; return S_OK; } HRESULT CArchiveExtractCallback::GetItem(UInt32 index) { #ifndef Z7_SFX _item._use_baseParentFolder_mode = _use_baseParentFolder_mode; if (_use_baseParentFolder_mode) { _item._baseParentFolder = (int)_baseParentFolder; if (_pathMode == NExtract::NPathMode::kFullPaths || _pathMode == NExtract::NPathMode::kAbsPaths) _item._baseParentFolder = -1; } #endif // Z7_SFX #ifdef SUPPORT_ALT_STREAMS _item.WriteToAltStreamIfColon = _ntOptions.WriteToAltStreamIfColon; #endif return _arc->GetItem(index, _item); } Z7_COM7F_IMF(CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)) { COM_TRY_BEGIN *outStream = NULL; #ifndef Z7_SFX if (_hashStream) _hashStreamSpec->ReleaseStream(); _hashStreamWasUsed = false; #endif _outFileStream.Release(); _bufPtrSeqOutStream.Release(); _encrypted = false; _position = 0; _isSplit = false; _curSize = 0; _curSize_Defined = false; _fileLength_WasSet = false; _fileLength_that_WasSet = 0; _index = index; _diskFilePath.Empty(); _isRenamed = false; // _fi.Clear(); // _is_SymLink_in_Data = false; _is_SymLink_in_Data_Linux = false; _needSetAttrib = false; _isSymLinkCreated = false; _itemFailure = false; #ifdef SUPPORT_LINKS // _copyFile_Path.Empty(); _link.Clear(); #endif _extractMode = false; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: if (_testMode) { // askExtractMode = NArchive::NExtract::NAskMode::kTest; } else _extractMode = true; break; } IInArchive *archive = _arc->Archive; RINOK(GetItem(index)) { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, kpidPosition, &prop)) if (prop.vt != VT_EMPTY) { if (prop.vt != VT_UI8) return E_FAIL; _position = prop.uhVal.QuadPart; _isSplit = true; } } #ifdef SUPPORT_LINKS RINOK(ReadLink()) #endif // SUPPORT_LINKS RINOK(Archive_GetItemBoolProp(archive, index, kpidEncrypted, _encrypted)) RINOK(GetUnpackSize()) #ifdef SUPPORT_ALT_STREAMS if (!_ntOptions.AltStreams.Val && _item.IsAltStream) return S_OK; #endif // SUPPORT_ALT_STREAMS // we can change (_item.PathParts) in this function UStringVector &pathParts = _item.PathParts; if (_wildcardCensor) { if (!CensorNode_CheckPath(*_wildcardCensor, _item)) return S_OK; } #ifndef Z7_SFX if (_use_baseParentFolder_mode) { if (!pathParts.IsEmpty()) { unsigned numRemovePathParts = 0; #ifdef SUPPORT_ALT_STREAMS if (_pathMode == NExtract::NPathMode::kNoPathsAlt && _item.IsAltStream) numRemovePathParts = pathParts.Size(); else #endif if (_pathMode == NExtract::NPathMode::kNoPaths || _pathMode == NExtract::NPathMode::kNoPathsAlt) numRemovePathParts = pathParts.Size() - 1; pathParts.DeleteFrontal(numRemovePathParts); } } else #endif // Z7_SFX { if (pathParts.IsEmpty()) { if (_item.IsDir) return S_OK; /* #ifdef SUPPORT_ALT_STREAMS if (!_item.IsAltStream) #endif return E_FAIL; */ } unsigned numRemovePathParts = 0; switch (_pathMode) { case NExtract::NPathMode::kFullPaths: case NExtract::NPathMode::kCurPaths: { if (_removePathParts.IsEmpty()) break; bool badPrefix = false; if (pathParts.Size() < _removePathParts.Size()) badPrefix = true; else { if (pathParts.Size() == _removePathParts.Size()) { if (_removePartsForAltStreams) { #ifdef SUPPORT_ALT_STREAMS if (!_item.IsAltStream) #endif badPrefix = true; } else { if (!_item.MainIsDir) badPrefix = true; } } if (!badPrefix) FOR_VECTOR (i, _removePathParts) { if (CompareFileNames(_removePathParts[i], pathParts[i]) != 0) { badPrefix = true; break; } } } if (badPrefix) { if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode) return E_FAIL; } else numRemovePathParts = _removePathParts.Size(); break; } case NExtract::NPathMode::kNoPaths: { if (!pathParts.IsEmpty()) numRemovePathParts = pathParts.Size() - 1; break; } case NExtract::NPathMode::kNoPathsAlt: { #ifdef SUPPORT_ALT_STREAMS if (_item.IsAltStream) numRemovePathParts = pathParts.Size(); else #endif if (!pathParts.IsEmpty()) numRemovePathParts = pathParts.Size() - 1; break; } case NExtract::NPathMode::kAbsPaths: // default: break; } pathParts.DeleteFrontal(numRemovePathParts); } #ifndef Z7_SFX if (ExtractToStreamCallback) { if (!GetProp) { GetProp_Spec = new CGetProp; GetProp = GetProp_Spec; } GetProp_Spec->Arc = _arc; GetProp_Spec->IndexInArc = index; UString name (MakePathFromParts(pathParts)); #ifdef SUPPORT_ALT_STREAMS if (_item.IsAltStream) { if (!pathParts.IsEmpty() || (!_removePartsForAltStreams && _pathMode != NExtract::NPathMode::kNoPathsAlt)) name += ':'; name += _item.AltStreamName; } #endif return ExtractToStreamCallback->GetStream7(name, BoolToInt(_item.IsDir), outStream, askExtractMode, GetProp); } #endif // Z7_SFX CMyComPtr outStreamLoc; if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode) { if (_stdOutMode) outStreamLoc = new CStdOutFileStream; else { bool needExit = true; RINOK(GetExtractStream(outStreamLoc, needExit)) if (needExit) return S_OK; } } #ifndef Z7_SFX if (_hashStream) { if (askExtractMode == NArchive::NExtract::NAskMode::kExtract || askExtractMode == NArchive::NExtract::NAskMode::kTest) { _hashStreamSpec->SetStream(outStreamLoc); outStreamLoc = _hashStream; _hashStreamSpec->Init(true); _hashStreamWasUsed = true; } } #endif // Z7_SFX if (outStreamLoc) { /* #ifdef SUPPORT_LINKS if (!_copyFile_Path.IsEmpty()) { RINOK(PrepareOperation(askExtractMode)); RINOK(MyCopyFile(outStreamLoc)); return SetOperationResult(NArchive::NExtract::NOperationResult::kOK); } if (_link.isCopyLink && _testMode) return S_OK; #endif */ *outStream = outStreamLoc.Detach(); } return S_OK; COM_TRY_END } Z7_COM7F_IMF(CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)) { COM_TRY_BEGIN #ifndef Z7_SFX if (ExtractToStreamCallback) return ExtractToStreamCallback->PrepareOperation7(askExtractMode); #endif _extractMode = false; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: if (_testMode) askExtractMode = NArchive::NExtract::NAskMode::kTest; else _extractMode = true; break; } return _extractCallback2->PrepareOperation(_item.Path, BoolToInt(_item.IsDir), askExtractMode, _isSplit ? &_position: NULL); COM_TRY_END } HRESULT CArchiveExtractCallback::CloseFile() { if (!_outFileStream) return S_OK; HRESULT hres = S_OK; const UInt64 processedSize = _outFileStreamSpec->ProcessedSize; if (_fileLength_WasSet && _fileLength_that_WasSet > processedSize) { const bool res = _outFileStreamSpec->File.SetLength(processedSize); _fileLength_WasSet = res; if (!res) { const HRESULT hres2 = SendMessageError_with_LastError(kCantSetFileLen, us2fs(_item.Path)); if (hres == S_OK) hres = hres2; } } _curSize = processedSize; _curSize_Defined = true; #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) if (ZoneBuf.Size() != 0 && !_item.IsAltStream) { // if (NFind::DoesFileExist_Raw(tempFilePath)) if (ZoneMode != NExtract::NZoneIdMode::kOffice || FindExt2(kOfficeExtensions, fs2us(_diskFilePath))) { // we must write zone file before setting of timestamps const FString path = _diskFilePath + k_ZoneId_StreamName; if (!WriteZoneFile(path, ZoneBuf)) { // we can't write it in FAT // SendMessageError_with_LastError("Can't write Zone.Identifier stream", path); } } } #endif CFiTimesCAM t; GetFiTimesCAM(t); // #ifdef _WIN32 if (t.IsSomeTimeDefined()) _outFileStreamSpec->SetTime( t.CTime_Defined ? &t.CTime : NULL, t.ATime_Defined ? &t.ATime : NULL, t.MTime_Defined ? &t.MTime : NULL); // #endif RINOK(_outFileStreamSpec->Close()) _outFileStream.Release(); return hres; } #ifdef SUPPORT_LINKS HRESULT CArchiveExtractCallback::SetFromLinkPath( const FString &fullProcessedPath, const CLinkInfo &linkInfo, bool &linkWasSet) { linkWasSet = false; if (!_ntOptions.SymLinks.Val && !linkInfo.isHardLink) return S_OK; UString relatPath; /* if (linkInfo.isRelative) linkInfo.linkPath is final link path that must be stored to file link field else linkInfo.linkPath is path from root of archive. So we must add _dirPathPrefix_Full before linkPath. */ if (linkInfo.isRelative) relatPath = GetDirPrefixOf(_item.Path); relatPath += linkInfo.linkPath; if (!IsSafePath(relatPath)) { return SendMessageError2( 0, // errorCode "Dangerous link path was ignored", us2fs(_item.Path), us2fs(linkInfo.linkPath)); // us2fs(relatPath) } FString existPath; if (linkInfo.isHardLink /* || linkInfo.IsCopyLink */ || !linkInfo.isRelative) { if (!NName::GetFullPath(_dirPathPrefix_Full, us2fs(relatPath), existPath)) { RINOK(SendMessageError("Incorrect path", us2fs(relatPath))) } } else { existPath = us2fs(linkInfo.linkPath); // printf("\nlinkPath = : %s\n", GetOemString(linkInfo.linkPath).Ptr()); } if (existPath.IsEmpty()) return SendMessageError("Empty link", fullProcessedPath); if (linkInfo.isHardLink /* || linkInfo.IsCopyLink */) { // if (linkInfo.isHardLink) { if (!MyCreateHardLink(fullProcessedPath, existPath)) { const HRESULT errorCode = GetLastError_noZero_HRESULT(); RINOK(SendMessageError2(errorCode, kCantCreateHardLink, fullProcessedPath, existPath)) } linkWasSet = true; return S_OK; } /* // IsCopyLink { NFind::CFileInfo fi; if (!fi.Find(existPath)) { RINOK(SendMessageError2("Cannot find the file for copying", existPath, fullProcessedPath)); } else { if (_curSize_Defined && _curSize == fi.Size) _copyFile_Path = existPath; else { RINOK(SendMessageError2("File size collision for file copying", existPath, fullProcessedPath)); } // RINOK(MyCopyFile(existPath, fullProcessedPath)); } } */ } // is Symbolic link /* if (_item.IsDir && !isRelative) { // Windows before Vista doesn't support symbolic links. // we could convert such symbolic links to Junction Points // isJunction = true; // convertToAbs = true; } */ if (!_ntOptions.SymLinks_AllowDangerous.Val) { #ifdef _WIN32 if (_item.IsDir) #endif if (linkInfo.isRelative) { CLinkLevelsInfo levelsInfo; levelsInfo.Parse(linkInfo.linkPath); if (levelsInfo.FinalLevel < 1 || levelsInfo.IsAbsolute) { return SendMessageError2( 0, // errorCode "Dangerous symbolic link path was ignored", us2fs(_item.Path), us2fs(linkInfo.linkPath)); } } } #ifdef _WIN32 CByteBuffer data; // printf("\nFillLinkData(): %s\n", GetOemString(existPath).Ptr()); if (!FillLinkData(data, fs2us(existPath), !linkInfo.isJunction, linkInfo.isWSL)) return SendMessageError("Cannot fill link data", us2fs(_item.Path)); /* if (NtReparse_Size != data.Size() || memcmp(NtReparse_Data, data, data.Size()) != 0) { SendMessageError("reconstructed Reparse is different", fs2us(existPath)); } */ CReparseAttr attr; if (!attr.Parse(data, data.Size())) { RINOK(SendMessageError("Internal error for symbolic link file", us2fs(_item.Path))) return S_OK; } if (!NFile::NIO::SetReparseData(fullProcessedPath, _item.IsDir, data, (DWORD)data.Size())) { RINOK(SendMessageError_with_LastError(kCantCreateSymLink, fullProcessedPath)) return S_OK; } linkWasSet = true; return S_OK; #else // ! _WIN32 if (!NFile::NIO::SetSymLink(fullProcessedPath, existPath)) { RINOK(SendMessageError_with_LastError(kCantCreateSymLink, fullProcessedPath)) return S_OK; } linkWasSet = true; return S_OK; #endif // ! _WIN32 } bool CLinkInfo::Parse(const Byte *data, size_t dataSize, bool isLinuxData) { Clear(); // this->isLinux = isLinuxData; if (isLinuxData) { isJunction = false; isHardLink = false; AString utf; if (dataSize >= (1 << 12)) return false; utf.SetFrom_CalcLen((const char *)data, (unsigned)dataSize); UString u; if (!ConvertUTF8ToUnicode(utf, u)) return false; linkPath = u; // in linux symbolic data: we expect that linux separator '/' is used // if windows link was created, then we also must use linux separator if (u.IsEmpty()) return false; const wchar_t c = u[0]; isRelative = !IS_PATH_SEPAR(c); return true; } CReparseAttr reparse; if (!reparse.Parse(data, dataSize)) return false; isHardLink = false; // isCopyLink = false; linkPath = reparse.GetPath(); isJunction = reparse.IsMountPoint(); if (reparse.IsSymLink_WSL()) { isWSL = true; isRelative = reparse.IsRelative_WSL(); } else isRelative = reparse.IsRelative_Win(); // FIXME !!! #ifndef _WIN32 linkPath.Replace(L'\\', WCHAR_PATH_SEPARATOR); #endif return true; } #endif // SUPPORT_LINKS HRESULT CArchiveExtractCallback::CloseReparseAndFile() { HRESULT res = S_OK; #ifdef SUPPORT_LINKS size_t reparseSize = 0; bool repraseMode = false; bool needSetReparse = false; CLinkInfo linkInfo; if (_bufPtrSeqOutStream) { repraseMode = true; reparseSize = _bufPtrSeqOutStream_Spec->GetPos(); if (_curSize_Defined && reparseSize == _outMemBuf.Size()) { /* CReparseAttr reparse; DWORD errorCode = 0; needSetReparse = reparse.Parse(_outMemBuf, reparseSize, errorCode); if (needSetReparse) { UString linkPath = reparse.GetPath(); #ifndef _WIN32 linkPath.Replace(L'\\', WCHAR_PATH_SEPARATOR); #endif } */ needSetReparse = linkInfo.Parse(_outMemBuf, reparseSize, _is_SymLink_in_Data_Linux); if (!needSetReparse) res = SendMessageError_with_LastError("Incorrect reparse stream", us2fs(_item.Path)); } else { res = SendMessageError_with_LastError("Unknown reparse stream", us2fs(_item.Path)); } if (!needSetReparse && _outFileStream) { const HRESULT res2 = WriteStream(_outFileStream, _outMemBuf, reparseSize); if (res == S_OK) res = res2; } _bufPtrSeqOutStream.Release(); } #endif // SUPPORT_LINKS const HRESULT res2 = CloseFile(); if (res == S_OK) res = res2; RINOK(res) #ifdef SUPPORT_LINKS if (repraseMode) { _curSize = reparseSize; _curSize_Defined = true; #ifdef SUPPORT_LINKS if (needSetReparse) { // in Linux : we must delete empty file before symbolic link creation // in Windows : we can create symbolic link even without file deleting if (!DeleteFileAlways(_diskFilePath)) { RINOK(SendMessageError_with_LastError("can't delete file", _diskFilePath)) } { /* // for DEBUG ONLY: we can extract sym links as WSL links // to eliminate (non-admin) errors for sym links. #ifdef _WIN32 if (!linkInfo.isHardLink && !linkInfo.isJunction) linkInfo.isWSL = true; #endif */ bool linkWasSet = false; RINOK(SetFromLinkPath(_diskFilePath, linkInfo, linkWasSet)) if (linkWasSet) _isSymLinkCreated = linkInfo.IsSymLink(); else _needSetAttrib = false; } /* if (!NFile::NIO::SetReparseData(_diskFilePath, _item.IsDir, )) { res = SendMessageError_with_LastError(kCantCreateSymLink, _diskFilePath); } */ } #endif } #endif return res; } void CArchiveExtractCallback::SetAttrib() { #ifndef _WIN32 // Linux now doesn't support permissions for symlinks if (_isSymLinkCreated) return; #endif if (_itemFailure || _diskFilePath.IsEmpty() || _stdOutMode || !_extractMode) return; #ifndef _WIN32 if (_fi.Owner.Id_Defined && _fi.Group.Id_Defined) { if (my_chown(_diskFilePath, _fi.Owner.Id, _fi.Group.Id) != 0) { SendMessageError_with_LastError("Cannot set owner", _diskFilePath); } } #endif if (_fi.Attrib_Defined) { // const AString s = GetAnsiString(_diskFilePath); // printf("\nSetFileAttrib_PosixHighDetect: %s: hex:%x\n", s.Ptr(), _fi.Attrib); bool res = SetFileAttrib_PosixHighDetect(_diskFilePath, _fi.Attrib); if (!res) { // do we need error message here in Windows and in posix? SendMessageError_with_LastError("Cannot set file attribute", _diskFilePath); } } } Z7_COM7F_IMF(CArchiveExtractCallback::SetOperationResult(Int32 opRes)) { COM_TRY_BEGIN // printf("\nCArchiveExtractCallback::SetOperationResult: %d %s\n", opRes, GetAnsiString(_diskFilePath)); #ifndef Z7_SFX if (ExtractToStreamCallback) { GetUnpackSize(); return ExtractToStreamCallback->SetOperationResult8(opRes, BoolToInt(_encrypted), _curSize); } #endif #ifndef Z7_SFX if (_hashStreamWasUsed) { _hashStreamSpec->_hash->Final(_item.IsDir, #ifdef SUPPORT_ALT_STREAMS _item.IsAltStream #else false #endif , _item.Path); _curSize = _hashStreamSpec->GetSize(); _curSize_Defined = true; _hashStreamSpec->ReleaseStream(); _hashStreamWasUsed = false; } #endif // Z7_SFX RINOK(CloseReparseAndFile()) #ifdef Z7_USE_SECURITY_CODE if (!_stdOutMode && _extractMode && _ntOptions.NtSecurity.Val && _arc->GetRawProps) { const void *data; UInt32 dataSize; UInt32 propType; _arc->GetRawProps->GetRawProp(_index, kpidNtSecure, &data, &dataSize, &propType); if (dataSize != 0) { if (propType != NPropDataType::kRaw) return E_FAIL; if (CheckNtSecure((const Byte *)data, dataSize)) { SECURITY_INFORMATION securInfo = DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION; if (_saclEnabled) securInfo |= SACL_SECURITY_INFORMATION; ::SetFileSecurityW(fs2us(_diskFilePath), securInfo, (PSECURITY_DESCRIPTOR)(void *)(const Byte *)(data)); } } } #endif // Z7_USE_SECURITY_CODE if (!_curSize_Defined) GetUnpackSize(); if (_curSize_Defined) { #ifdef SUPPORT_ALT_STREAMS if (_item.IsAltStream) AltStreams_UnpackSize += _curSize; else #endif UnpackSize += _curSize; } if (_item.IsDir) NumFolders++; #ifdef SUPPORT_ALT_STREAMS else if (_item.IsAltStream) NumAltStreams++; #endif else NumFiles++; if (_needSetAttrib) SetAttrib(); RINOK(_extractCallback2->SetOperationResult(opRes, BoolToInt(_encrypted))) return S_OK; COM_TRY_END } Z7_COM7F_IMF(CArchiveExtractCallback::ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)) { if (_folderArchiveExtractCallback2) { bool isEncrypted = false; UString s; if (indexType == NArchive::NEventIndexType::kInArcIndex && index != (UInt32)(Int32)-1) { CReadArcItem item; RINOK(_arc->GetItem(index, item)) s = item.Path; RINOK(Archive_GetItemBoolProp(_arc->Archive, index, kpidEncrypted, isEncrypted)) } else { s = '#'; s.Add_UInt32(index); // if (indexType == NArchive::NEventIndexType::kBlockIndex) {} } return _folderArchiveExtractCallback2->ReportExtractResult(opRes, isEncrypted, s); } return S_OK; } Z7_COM7F_IMF(CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)) { COM_TRY_BEGIN if (!_cryptoGetTextPassword) { RINOK(_extractCallback2.QueryInterface(IID_ICryptoGetTextPassword, &_cryptoGetTextPassword)) } return _cryptoGetTextPassword->CryptoGetTextPassword(password); COM_TRY_END } // ---------- HASH functions ---------- FString CArchiveExtractCallback::Hash_GetFullFilePath() { // this function changes _item.PathParts. CorrectPathParts(); const UStringVector &pathParts = _item.PathParts; const UString processedPath (MakePathFromParts(pathParts)); FString fullProcessedPath (us2fs(processedPath)); if (_pathMode != NExtract::NPathMode::kAbsPaths || !NName::IsAbsolutePath(processedPath)) { fullProcessedPath = MakePath_from_2_Parts( DirPathPrefix_for_HashFiles, // _dirPathPrefix, fullProcessedPath); } return fullProcessedPath; } Z7_COM7F_IMF(CArchiveExtractCallback::GetDiskProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; if (propID == kpidSize) { RINOK(GetItem(index)) const FString fullProcessedPath = Hash_GetFullFilePath(); NFile::NFind::CFileInfo fi; if (fi.Find_FollowLink(fullProcessedPath)) if (!fi.IsDir()) prop = (UInt64)fi.Size; } prop.Detach(value); return S_OK; COM_TRY_END } Z7_COM7F_IMF(CArchiveExtractCallback::GetStream2(UInt32 index, ISequentialInStream **inStream, UInt32 mode)) { COM_TRY_BEGIN *inStream = NULL; // if (index != _index) return E_FAIL; if (mode != NUpdateNotifyOp::kHashRead) return E_FAIL; RINOK(GetItem(index)) const FString fullProcessedPath = Hash_GetFullFilePath(); CInFileStream *inStreamSpec = new CInFileStream; CMyComPtr inStreamRef = inStreamSpec; inStreamSpec->Set_PreserveATime(_ntOptions.PreserveATime); if (!inStreamSpec->OpenShared(fullProcessedPath, _ntOptions.OpenShareForWrite)) { RINOK(SendMessageError_with_LastError(kCantOpenInFile, fullProcessedPath)) return S_OK; } *inStream = inStreamRef.Detach(); return S_OK; COM_TRY_END } Z7_COM7F_IMF(CArchiveExtractCallback::ReportOperation( UInt32 /* indexType */, UInt32 /* index */, UInt32 /* op */)) { // COM_TRY_BEGIN return S_OK; // COM_TRY_END } // ------------ After Extracting functions ------------ void CDirPathSortPair::SetNumSlashes(const FChar *s) { for (unsigned numSlashes = 0;;) { FChar c = *s++; if (c == 0) { Len = numSlashes; return; } if (IS_PATH_SEPAR(c)) numSlashes++; } } bool CDirPathTime::SetDirTime() const { return NDir::SetDirTime(Path, CTime_Defined ? &CTime : NULL, ATime_Defined ? &ATime : NULL, MTime_Defined ? &MTime : NULL); } HRESULT CArchiveExtractCallback::SetDirsTimes() { if (!_arc) return S_OK; CRecordVector pairs; pairs.ClearAndSetSize(_extractedFolders.Size()); unsigned i; for (i = 0; i < _extractedFolders.Size(); i++) { CDirPathSortPair &pair = pairs[i]; pair.Index = i; pair.SetNumSlashes(_extractedFolders[i].Path); } pairs.Sort2(); HRESULT res = S_OK; for (i = 0; i < pairs.Size(); i++) { const CDirPathTime &dpt = _extractedFolders[pairs[i].Index]; if (!dpt.SetDirTime()) { // result = E_FAIL; // do we need error message here in Windows and in posix? // SendMessageError_with_LastError("Cannot set directory time", dpt.Path); } } /* #ifndef _WIN32 for (i = 0; i < _delayedSymLinks.Size(); i++) { const CDelayedSymLink &link = _delayedSymLinks[i]; if (!link.Create()) { if (res == S_OK) res = GetLastError_noZero_HRESULT(); // res = E_FAIL; // do we need error message here in Windows and in posix? SendMessageError_with_LastError("Cannot create Symbolic Link", link._source); } } #endif // _WIN32 */ ClearExtractedDirsInfo(); return res; } HRESULT CArchiveExtractCallback::CloseArc() { HRESULT res = CloseReparseAndFile(); const HRESULT res2 = SetDirsTimes(); if (res == S_OK) res = res2; _arc = NULL; return res; }