From 4cdd2e6e1af05d9cd4b614c7608c289e7c9f0785 Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Tue, 22 Aug 2023 13:25:51 -0700 Subject: [PATCH 1/8] Bump version to 1.1.2 and FLEVEL to 182 --- CMakeLists.txt | 2 +- Jenkinsfile | 2 +- NEWS.md | 4 ++++ libclamav/bytecode_api.h | 1 + libclamav/others.h | 2 +- win32/res/common.rc | 4 ++-- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b8e148e88..b19e7d76b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ string(TIMESTAMP TODAY "%Y%m%d") set(VERSION_SUFFIX "") project( ClamAV - VERSION "1.1.1" + VERSION "1.1.2" DESCRIPTION "ClamAV open source email, web, and end-point anti-virus toolkit." ) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) diff --git a/Jenkinsfile b/Jenkinsfile index 004c3779eb..f16961a40c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,7 @@ properties( parameters( [ string(name: 'VERSION', - defaultValue: '1.1.1', + defaultValue: '1.1.2', description: 'ClamAV version string'), string(name: 'FRAMEWORK_BRANCH', defaultValue: '1.1', diff --git a/NEWS.md b/NEWS.md index 333ec15e20..2d7836766e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,10 @@ Note: This file refers to the official packages. Things described here may differ slightly from third-party binary packages. +## 1.1.2 + +ClamAV 1.1.2 is a critical patch release with the following fixes: + ## 1.1.1 ClamAV 1.1.1 is a critical patch release with the following fixes: diff --git a/libclamav/bytecode_api.h b/libclamav/bytecode_api.h index 97c8107e1a..dbcbd294af 100644 --- a/libclamav/bytecode_api.h +++ b/libclamav/bytecode_api.h @@ -170,6 +170,7 @@ enum FunctionalityLevels { FUNC_LEVEL_1_1 = 180, /**< LibClamAV release 1.1.0 */ FUNC_LEVEL_1_1_1 = 181, /**< LibClamAV release 1.1.1 */ + FUNC_LEVEL_1_1_2 = 182, /**< LibClamAV release 1.1.2 */ }; /** diff --git a/libclamav/others.h b/libclamav/others.h index 551414dea6..5826719766 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -73,7 +73,7 @@ * in re-enabling affected modules. */ -#define CL_FLEVEL 181 +#define CL_FLEVEL 182 #define CL_FLEVEL_DCONF CL_FLEVEL #define CL_FLEVEL_SIGTOOL CL_FLEVEL diff --git a/win32/res/common.rc b/win32/res/common.rc index a172ba1693..609142bf9a 100644 --- a/win32/res/common.rc +++ b/win32/res/common.rc @@ -6,8 +6,8 @@ #define REPO_VERSION VERSION #endif -#define RES_VER_Q 1,1,1,0 -#define RES_VER_S "ClamAV 1.1.1" +#define RES_VER_Q 1,1,2,0 +#define RES_VER_S "ClamAV 1.1.2" VS_VERSION_INFO VERSIONINFO FILEVERSION RES_VER_Q From 8781fa357d870fcff36ecd07900c5a59bdd14480 Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Tue, 22 Aug 2023 10:26:55 -0700 Subject: [PATCH 2/8] Update to UnRAR v6.2.10 --- libclamunrar/UnRARDll.vcxproj | 12 +- libclamunrar/archive.cpp | 12 +- libclamunrar/archive.hpp | 11 +- libclamunrar/arcread.cpp | 136 +++++---- libclamunrar/array.hpp | 34 +-- libclamunrar/blake2s.hpp | 9 +- libclamunrar/cmddata.cpp | 25 +- libclamunrar/cmddata.hpp | 6 +- libclamunrar/cmdfilter.cpp | 4 +- libclamunrar/cmdmix.cpp | 7 + libclamunrar/compress.hpp | 1 + libclamunrar/crc.cpp | 161 +++++++++++ libclamunrar/crc.hpp | 4 + libclamunrar/crypt.cpp | 14 +- libclamunrar/crypt.hpp | 72 ++++- libclamunrar/crypt3.cpp | 5 +- libclamunrar/crypt5.cpp | 12 +- libclamunrar/dll.cpp | 2 +- libclamunrar/dll.rc | 10 +- libclamunrar/errhnd.cpp | 5 +- libclamunrar/extinfo.cpp | 99 ++++++- libclamunrar/extinfo.hpp | 3 +- libclamunrar/extract.cpp | 517 +++++++++++++++++++++++++++------- libclamunrar/extract.hpp | 34 ++- libclamunrar/filcreat.cpp | 2 +- libclamunrar/filcreat.hpp | 2 +- libclamunrar/file.cpp | 67 +++-- libclamunrar/file.hpp | 10 +- libclamunrar/filefn.cpp | 4 +- libclamunrar/filefn.hpp | 2 - libclamunrar/find.cpp | 18 +- libclamunrar/getbits.cpp | 8 +- libclamunrar/getbits.hpp | 14 +- libclamunrar/hardlinks.cpp | 2 - libclamunrar/hash.cpp | 2 +- libclamunrar/hash.hpp | 10 +- libclamunrar/headers.cpp | 10 +- libclamunrar/headers.hpp | 29 +- libclamunrar/headers5.hpp | 7 + libclamunrar/isnt.cpp | 2 +- libclamunrar/list.cpp | 11 + libclamunrar/loclang.hpp | 7 +- libclamunrar/makefile | 11 +- libclamunrar/model.cpp | 6 +- libclamunrar/options.cpp | 8 - libclamunrar/options.hpp | 12 +- libclamunrar/os.hpp | 13 +- libclamunrar/pathfn.cpp | 93 +++--- libclamunrar/pathfn.hpp | 1 - libclamunrar/qopen.cpp | 2 +- libclamunrar/rar.hpp | 4 +- libclamunrar/rardefs.hpp | 10 +- libclamunrar/rawint.hpp | 6 +- libclamunrar/rdwrfn.cpp | 4 +- libclamunrar/recvol.cpp | 4 +- libclamunrar/recvol.hpp | 22 +- libclamunrar/recvol3.cpp | 17 +- libclamunrar/recvol5.cpp | 8 +- libclamunrar/rijndael.cpp | 98 ++++++- libclamunrar/rijndael.hpp | 10 + libclamunrar/scantree.cpp | 19 +- libclamunrar/secpassword.cpp | 30 +- libclamunrar/secpassword.hpp | 9 +- libclamunrar/strfn.cpp | 26 ++ libclamunrar/strfn.hpp | 1 + libclamunrar/system.cpp | 6 +- libclamunrar/threadmisc.cpp | 2 + libclamunrar/timefn.hpp | 11 + libclamunrar/ui.hpp | 37 +-- libclamunrar/uicommon.cpp | 2 +- libclamunrar/uiconsole.cpp | 5 +- libclamunrar/uisilent.cpp | 3 +- libclamunrar/ulinks.cpp | 8 +- libclamunrar/unicode.cpp | 7 +- libclamunrar/unicode.hpp | 2 +- libclamunrar/unpack.cpp | 12 +- libclamunrar/unpack.hpp | 6 +- libclamunrar/unpack30.cpp | 2 +- libclamunrar/unpack50.cpp | 4 +- libclamunrar/unpack50mt.cpp | 14 +- libclamunrar/uowners.cpp | 48 +--- libclamunrar/version.hpp | 8 +- libclamunrar/volume.cpp | 10 +- libclamunrar/volume.hpp | 3 - libclamunrar/win32lnk.cpp | 2 +- libclamunrar/win32stm.cpp | 16 +- 86 files changed, 1436 insertions(+), 588 deletions(-) diff --git a/libclamunrar/UnRARDll.vcxproj b/libclamunrar/UnRARDll.vcxproj index ec5c17b00e..72cecd8dba 100644 --- a/libclamunrar/UnRARDll.vcxproj +++ b/libclamunrar/UnRARDll.vcxproj @@ -138,7 +138,7 @@ Sync EnableFastChecks MultiThreadedDebug - 4Bytes + Default false Use rar.hpp @@ -168,7 +168,7 @@ Sync EnableFastChecks MultiThreadedDebug - 4Bytes + Default false Use rar.hpp @@ -198,7 +198,7 @@ false Sync MultiThreaded - 4Bytes + Default true true NoExtensions @@ -239,7 +239,7 @@ false Sync MultiThreaded - 4Bytes + Default true true false @@ -274,7 +274,7 @@ false Sync MultiThreaded - 4Bytes + Default true true NoExtensions @@ -315,7 +315,7 @@ false Sync MultiThreaded - 4Bytes + Default true true false diff --git a/libclamunrar/archive.cpp b/libclamunrar/archive.cpp index 0f5de946f7..25f0c3b79d 100644 --- a/libclamunrar/archive.cpp +++ b/libclamunrar/archive.cpp @@ -3,15 +3,15 @@ #include "arccmt.cpp" -Archive::Archive(RAROptions *InitCmd) +Archive::Archive(CommandData *InitCmd) { Cmd=NULL; // Just in case we'll have an exception in 'new' below. DummyCmd=(InitCmd==NULL); - Cmd=DummyCmd ? (new RAROptions):InitCmd; + Cmd=DummyCmd ? (new CommandData):InitCmd; OpenShared=Cmd->OpenShared; - Format=RARFMT15; + Format=RARFMT_NONE; Solid=false; Volume=false; MainComment=false; @@ -31,9 +31,9 @@ Archive::Archive(RAROptions *InitCmd) NextBlockPos=0; - memset(&MainHead,0,sizeof(MainHead)); - memset(&CryptHead,0,sizeof(CryptHead)); - memset(&EndArcHead,0,sizeof(EndArcHead)); + MainHead.Reset(); + CryptHead={}; + EndArcHead.Reset(); VolNumber=0; VolWrite=0; diff --git a/libclamunrar/archive.hpp b/libclamunrar/archive.hpp index d9518f1dc4..c0019aef57 100644 --- a/libclamunrar/archive.hpp +++ b/libclamunrar/archive.hpp @@ -32,8 +32,8 @@ class Archive:public File size_t ReadHeader14(); size_t ReadHeader15(); size_t ReadHeader50(); - void ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb); - void RequestArcPassword(); + void ProcessExtra50(RawRead *Raw,size_t ExtraSize,const BaseBlock *bb); + void RequestArcPassword(RarCheckPassword *SelPwd); void UnexpEndArcMsg(); void BrokenHeaderMsg(); void UnkEncVerMsg(const wchar *Name,const wchar *Info); @@ -45,7 +45,7 @@ class Archive:public File #endif ComprDataIO SubDataIO; bool DummyCmd; - RAROptions *Cmd; + CommandData *Cmd; RarTime LatestTime; @@ -58,7 +58,7 @@ class Archive:public File bool ProhibitQOpen; #endif public: - Archive(RAROptions *InitCmd=NULL); + Archive(CommandData *InitCmd=NULL); ~Archive(); static RARFORMAT IsSignature(const byte *D,size_t Size); bool IsArchive(bool EnableBroken); @@ -83,7 +83,7 @@ class Archive:public File const wchar *Name,uint Flags); bool ReadSubData(Array *UnpData,File *DestFile,bool TestMode); HEADER_TYPE GetHeaderType() {return CurHeaderType;} - RAROptions* GetRAROptions() {return Cmd;} + CommandData* GetCommandData() {return Cmd;} void SetSilentOpen(bool Mode) {SilentOpen=Mode;} #if 0 void GetRecoveryInfo(bool Required,int64 *Size,int *Percent); @@ -107,7 +107,6 @@ class Archive:public File FileHeader SubHead; CommentHeader CommHead; ProtectHeader ProtectHead; - UnixOwnersHeader UOHead; EAHeader EAHead; StreamHeader StreamHead; diff --git a/libclamunrar/arcread.cpp b/libclamunrar/arcread.cpp index 1a401f48d3..f5ef9aa517 100644 --- a/libclamunrar/arcread.cpp +++ b/libclamunrar/arcread.cpp @@ -100,6 +100,9 @@ void Archive::UnexpEndArcMsg() // If block positions are equal to file size, this is not an error. // It can happen when we reached the end of older RAR 1.5 archive, // which did not have the end of archive block. + // We can't replace this check by checking that read size is exactly 0 + // in the beginning of file header, because in this case the read position + // still can be beyond the end of archive. if (CurBlockPos!=ArcSize || NextBlockPos!=ArcSize) { uiMsg(UIERROR_UNEXPEOF,FileName); @@ -145,7 +148,7 @@ size_t Archive::ReadHeader15() #ifdef RAR_NOCRYPT // For rarext.dll and unrar_nocrypt.dll. return 0; #else - RequestArcPassword(); + RequestArcPassword(NULL); byte Salt[SIZE_SALT30]; if (Read(Salt,SIZE_SALT30)!=SIZE_SALT30) @@ -251,7 +254,11 @@ size_t Archive::ReadHeader15() hd->SplitAfter=(hd->Flags & LHD_SPLIT_AFTER)!=0; hd->Encrypted=(hd->Flags & LHD_PASSWORD)!=0; hd->SaltSet=(hd->Flags & LHD_SALT)!=0; + + // RAR versions earlier than 2.0 do not set the solid flag + // in file header. They use only a global solid archive flag. hd->Solid=FileBlock && (hd->Flags & LHD_SOLID)!=0; + hd->SubBlock=!FileBlock && (hd->Flags & LHD_SOLID)!=0; hd->Dir=(hd->Flags & LHD_WINDOWMASK)==LHD_DIRECTORY; hd->WinSize=hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5); @@ -469,19 +476,6 @@ size_t Archive::ReadHeader15() SubBlockHead.Level=Raw.Get1(); switch(SubBlockHead.SubType) { - case UO_HEAD: - *(SubBlockHeader *)&UOHead=SubBlockHead; - UOHead.OwnerNameSize=Raw.Get2(); - UOHead.GroupNameSize=Raw.Get2(); - if (UOHead.OwnerNameSize>=ASIZE(UOHead.OwnerName)) - UOHead.OwnerNameSize=ASIZE(UOHead.OwnerName)-1; - if (UOHead.GroupNameSize>=ASIZE(UOHead.GroupName)) - UOHead.GroupNameSize=ASIZE(UOHead.GroupName)-1; - Raw.GetB(UOHead.OwnerName,UOHead.OwnerNameSize); - Raw.GetB(UOHead.GroupName,UOHead.GroupNameSize); - UOHead.OwnerName[UOHead.OwnerNameSize]=0; - UOHead.GroupName[UOHead.GroupNameSize]=0; - break; case NTACL_HEAD: *(SubBlockHeader *)&EAHead=SubBlockHead; EAHead.UnpSize=Raw.Get4(); @@ -513,8 +507,12 @@ size_t Archive::ReadHeader15() ushort HeaderCRC=Raw.GetCRC15(false); // Old AV header does not have header CRC properly set. + // Old Unix owners header didn't include string fields into header size, + // but included them into CRC, so it couldn't be verified with generic + // approach here. if (ShortBlock.HeadCRC!=HeaderCRC && ShortBlock.HeaderType!=HEAD3_SIGN && - ShortBlock.HeaderType!=HEAD3_AV) + ShortBlock.HeaderType!=HEAD3_AV && + (ShortBlock.HeaderType!=HEAD3_OLDSERVICE || SubBlockHead.SubType!=UO_HEAD)) { bool Recovered=false; if (ShortBlock.HeaderType==HEAD_ENDARC && EndArcHead.RevSpace) @@ -577,14 +575,20 @@ size_t Archive::ReadHeader50() // in -p to not stop batch processing for encrypted archives. bool GlobalPassword=Cmd->Password.IsSet() || uiIsGlobalPasswordSet(); + RarCheckPassword CheckPwd; + if (CryptHead.UsePswCheck && !BrokenHeader) + CheckPwd.Set(CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,CryptHead.PswCheck); + while (true) // Repeat the password prompt for wrong passwords. { - RequestArcPassword(); + RequestArcPassword(CheckPwd.IsSet() ? &CheckPwd:NULL); byte PswCheck[SIZE_PSWCHECK]; HeadersCrypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,NULL,PswCheck); - // Verify password validity. - if (CryptHead.UsePswCheck && memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0) + // Verify password validity. If header is damaged, we cannot rely on + // password check value, because it can be damaged too. + if (CryptHead.UsePswCheck && !BrokenHeader && + memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0) { if (GlobalPassword) // For -p or Ctrl+P. { @@ -850,8 +854,6 @@ size_t Archive::ReadHeader50() hd->Dir=(hd->FileFlags & FHFL_DIRECTORY)!=0; hd->WinSize=hd->Dir ? 0:size_t(0x20000)<<((CompInfo>>10)&0xf); - hd->CryptMethod=hd->Encrypted ? CRYPT_RAR50:CRYPT_NONE; - char FileName[NM*4]; size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); Raw.GetB((byte *)FileName,ReadNameSize); @@ -875,25 +877,6 @@ size_t Archive::ReadHeader50() if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_CMT)) MainComment=true; -#if 0 - // For RAR5 format we read the user specified recovery percent here. - // It would be useful to do it for shell extension too, so we display - // the correct recovery record size in archive properties. But then - // we would need to include the entire recovery record processing - // code to shell extension, which is not done now. - if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_RR) && hd->SubData.Size()>0) - { - // It is stored as a single byte up to RAR 6.02 and as vint since - // 6.10, where we extended the maximum RR size from 99% to 1000%. - RawRead RawPercent; - RawPercent.Read(&hd->SubData[0],hd->SubData.Size()); - RecoveryPercent=(int)RawPercent.GetV(); - - RSBlockHeader Header; - GetRRInfo(this,&Header); - RecoverySize=Header.RecSectionSize*Header.RecCount; - } -#endif if (BadCRC) // Add the file name to broken header message displayed above. uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName); @@ -916,7 +899,7 @@ size_t Archive::ReadHeader50() #if !defined(RAR_NOCRYPT) -void Archive::RequestArcPassword() +void Archive::RequestArcPassword(RarCheckPassword *CheckPwd) { if (!Cmd->Password.IsSet()) { @@ -946,7 +929,7 @@ void Archive::RequestArcPassword() ErrHandler.Exit(RARX_USERBREAK); } #else - if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password)) + if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password,CheckPwd)) { Close(); uiMsg(UIERROR_INCERRCOUNT); // Prevent archive deleting if delete after extraction is on. @@ -959,7 +942,7 @@ void Archive::RequestArcPassword() #endif -void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) +void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,const BaseBlock *bb) { // Read extra data from the end of block skipping any fields before it. size_t ExtraStart=Raw->Size()-ExtraSize; @@ -982,22 +965,57 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) if (bb->HeaderType==HEAD_MAIN) { MainHeader *hd=(MainHeader *)bb; - if (FieldType==MHEXTRA_LOCATOR) + switch(FieldType) { - hd->Locator=true; - uint Flags=(uint)Raw->GetV(); - if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0) - { - uint64 Offset=Raw->GetV(); - if (Offset!=0) // 0 means that reserved space was not enough to write the offset. - hd->QOpenOffset=Offset+CurBlockPos; - } - if ((Flags & MHEXTRA_LOCATOR_RR)!=0) - { - uint64 Offset=Raw->GetV(); - if (Offset!=0) // 0 means that reserved space was not enough to write the offset. - hd->RROffset=Offset+CurBlockPos; - } + case MHEXTRA_LOCATOR: + { + hd->Locator=true; + uint Flags=(uint)Raw->GetV(); + if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->QOpenOffset=Offset+CurBlockPos; + } + if ((Flags & MHEXTRA_LOCATOR_RR)!=0) + { + uint64 Offset=Raw->GetV(); + if (Offset!=0) // 0 means that reserved space was not enough to write the offset. + hd->RROffset=Offset+CurBlockPos; + } + } + break; + case MHEXTRA_METADATA: + { + uint Flags=(uint)Raw->GetV(); + if ((Flags & MHEXTRA_METADATA_NAME)!=0) + { + uint64 NameSize=Raw->GetV(); + if (NameSize<0x10000) // Prevent excessive allocation. + { + std::vector NameU((size_t)NameSize); // UTF-8 name. + Raw->GetB(&NameU[0],(size_t)NameSize); + // If starts from 0, the name was longer than reserved space + // when saving this extra field. + if (NameU[0]!=0) + { + NameU.push_back(0); + std::vector NameW(NameU.size()*4); + UtfToWide(&NameU[0],&NameW[0],NameW.size()); + hd->OrigName.assign(&NameW[0]); + } + } + } + if ((Flags & MHEXTRA_METADATA_CTIME)!=0) + if ((Flags & MHEXTRA_METADATA_UNIXTIME)!=0) + if ((Flags & MHEXTRA_METADATA_UNIX_NS)!=0) + hd->OrigTime.SetUnixNS(Raw->Get8()); + else + hd->OrigTime.SetUnix((time_t)Raw->Get4()); + else + hd->OrigTime.SetWin(Raw->Get8()); + } + break; } } @@ -1453,7 +1471,9 @@ bool Archive::ReadSubData(Array *UnpData,File *DestFile,bool TestMode) { if (SubHead.UnpSize>0x1000000) { - // So huge allocation must never happen in valid archives. + // Prevent the excessive allocation. When reading to memory, normally + // this function operates with reasonably small blocks, such as + // the archive comment, NTFS ACL or "Zone.Identifier" NTFS stream. uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); return false; } diff --git a/libclamunrar/array.hpp b/libclamunrar/array.hpp index 20d258d5b8..ac786f7109 100644 --- a/libclamunrar/array.hpp +++ b/libclamunrar/array.hpp @@ -10,7 +10,6 @@ template class Array size_t BufSize; size_t AllocSize; size_t MaxSize; - bool Secure; // Clean memory if true. public: Array(); Array(size_t Size); @@ -24,14 +23,13 @@ template class Array void Alloc(size_t Items); void Reset(); void SoftReset(); - void operator = (Array &Src); + Array& operator = (const Array &Src); void Push(T Item); void Append(T *Item,size_t Count); T* Addr(size_t Item) {return Buffer+Item;} void SetMaxSize(size_t Size) {MaxSize=Size;} T* Begin() {return Buffer;} T* End() {return Buffer==NULL ? NULL:Buffer+BufSize;} - void SetSecure() {Secure=true;} }; @@ -41,7 +39,6 @@ template void Array::CleanData() BufSize=0; AllocSize=0; MaxSize=0; - Secure=false; } @@ -71,11 +68,7 @@ template Array::Array(const Array &Src) template Array::~Array() { if (Buffer!=NULL) - { - if (Secure) - cleandata(Buffer,AllocSize*sizeof(T)); free(Buffer); - } } @@ -111,25 +104,9 @@ template void Array::Add(size_t Items) size_t Suggested=AllocSize+AllocSize/4+32; size_t NewSize=Max(BufSize,Suggested); - T *NewBuffer; - if (Secure) - { - NewBuffer=(T *)malloc(NewSize*sizeof(T)); - if (NewBuffer==NULL) - ErrHandler.MemoryError(); - if (Buffer!=NULL) - { - memcpy(NewBuffer,Buffer,AllocSize*sizeof(T)); - cleandata(Buffer,AllocSize*sizeof(T)); - free(Buffer); - } - } - else - { - NewBuffer=(T *)realloc(Buffer,NewSize*sizeof(T)); - if (NewBuffer==NULL) - ErrHandler.MemoryError(); - } + T *NewBuffer=(T *)realloc(Buffer,NewSize*sizeof(T)); + if (NewBuffer==NULL) + ErrHandler.MemoryError(); Buffer=NewBuffer; AllocSize=NewSize; } @@ -165,12 +142,13 @@ template void Array::SoftReset() } -template void Array::operator =(Array &Src) +template Array& Array::operator =(const Array &Src) { Reset(); Alloc(Src.BufSize); if (Src.BufSize!=0) memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); + return *this; } diff --git a/libclamunrar/blake2s.hpp b/libclamunrar/blake2s.hpp index f88ef37802..06e396a77b 100644 --- a/libclamunrar/blake2s.hpp +++ b/libclamunrar/blake2s.hpp @@ -20,10 +20,15 @@ enum blake2s_constant // 'new' operator. struct blake2s_state { - enum { BLAKE_ALIGNMENT = 64 }; + // Use constexpr instead of enums, because otherwise clang -std=c++20 + // issues a warning about "arithmetic between different enumeration types" + // in ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT] declaration. + static constexpr size_t BLAKE_ALIGNMENT = 64; // buffer and uint32 h[8], t[2], f[2]; - enum { BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES }; + // 2 * BLAKE2S_BLOCKBYTES is the buf size in blake2_code_20140114.zip. + // It might differ in later versions. + static constexpr size_t BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES; byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT]; diff --git a/libclamunrar/cmddata.cpp b/libclamunrar/cmddata.cpp index 82d24f44f0..ffc5ff90db 100644 --- a/libclamunrar/cmddata.cpp +++ b/libclamunrar/cmddata.cpp @@ -26,9 +26,10 @@ void CommandData::Init() FileArgs.Reset(); ExclArgs.Reset(); InclArgs.Reset(); - StoreArgs.Reset(); ArcNames.Reset(); - NextVolSizes.Reset(); + StoreArgs.Reset(); + Password.Clean(); + NextVolSizes.clear(); } @@ -314,6 +315,21 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'I': IgnoreGeneralAttr=true; break; + case 'M': + switch(toupperw(Switch[2])) + { + case 0: + case 'S': + ArcMetadata=ARCMETA_SAVE; + break; + case 'R': + ArcMetadata=ARCMETA_RESTORE; + break; + default: + BadSwitch(Switch); + break; + } + break; case 'N': // Reserved for archive name. break; case 'O': @@ -415,7 +431,7 @@ void CommandData::ProcessSwitch(const wchar *Switch) else if (!Password.IsSet()) { - uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password,NULL); eprintf(L"\n"); } break; @@ -685,7 +701,7 @@ void CommandData::ProcessSwitch(const wchar *Switch) case 'P': if (Switch[1]==0) { - uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password,NULL); eprintf(L"\n"); } else @@ -927,6 +943,7 @@ void CommandData::ProcessSwitch(const wchar *Switch) void CommandData::BadSwitch(const wchar *Switch) { mprintf(St(MUnknownOption),Switch); + mprintf(L"\n"); ErrHandler.Exit(RARX_USERERROR); } #endif diff --git a/libclamunrar/cmddata.hpp b/libclamunrar/cmddata.hpp index 2a21922006..0feb404b3e 100644 --- a/libclamunrar/cmddata.hpp +++ b/libclamunrar/cmddata.hpp @@ -2,7 +2,7 @@ #define _RAR_CMDDATA_ -#define DefaultStoreList L"7z;ace;arj;bz2;cab;gz;jpeg;jpg;lha;lz;lzh;mp3;rar;taz;tgz;xz;z;zip;zipx" +#define DefaultStoreList L"7z;ace;arj;bz2;cab;gz;jpeg;jpg;lha;lz;lzh;mp3;rar;taz;tbz;tbz2;tgz;txz;xz;z;zip;zipx;zst;tzst" enum RAR_CMD_LIST_MODE {RCLM_AUTO,RCLM_REJECT_LISTS,RCLM_ACCEPT_LISTS}; @@ -65,6 +65,10 @@ class CommandData:public RAROptions StringList InclArgs; StringList ArcNames; StringList StoreArgs; + + SecPassword Password; + + std::vector NextVolSizes; }; #endif diff --git a/libclamunrar/cmdfilter.cpp b/libclamunrar/cmdfilter.cpp index d098d1088c..e0add14bfa 100644 --- a/libclamunrar/cmdfilter.cpp +++ b/libclamunrar/cmdfilter.cpp @@ -289,8 +289,8 @@ int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchTy return 0; if ((FileHead.FileAttr & ExclFileAttr)!=0 || FileHead.Dir && ExclDir) return 0; - if (InclAttrSet && (!FileHead.Dir && (FileHead.FileAttr & InclFileAttr)==0 || - FileHead.Dir && !InclDir)) + if (InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0 && + (!FileHead.Dir || !InclDir)) return 0; if (!Dir && SizeCheck(FileHead.UnpSize)) return 0; diff --git a/libclamunrar/cmdmix.cpp b/libclamunrar/cmdmix.cpp index 1fc4b8caff..6bd1e1ac0f 100644 --- a/libclamunrar/cmdmix.cpp +++ b/libclamunrar/cmdmix.cpp @@ -92,6 +92,13 @@ void CommandData::OutHelp(RAR_EXIT ExitCode) if (Found) continue; #endif +#ifdef _UNIX + if (CmpMSGID(Help[I],MRARTitle2)) + { + mprintf(St(MFwrSlTitle2)); + continue; + } +#endif #if !defined(_UNIX) && !defined(_WIN_ALL) if (CmpMSGID(Help[I],MCHelpSwOW)) continue; diff --git a/libclamunrar/compress.hpp b/libclamunrar/compress.hpp index 73f7ee41ae..4ef8570f4e 100644 --- a/libclamunrar/compress.hpp +++ b/libclamunrar/compress.hpp @@ -17,6 +17,7 @@ class PackDef static const uint MAX_INC_LZ_MATCH = MAX_LZ_MATCH + 3; static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + static const uint MAX3_INC_LZ_MATCH = MAX3_LZ_MATCH + 3; static const uint LOW_DIST_REP_COUNT = 16; static const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ diff --git a/libclamunrar/crc.cpp b/libclamunrar/crc.cpp index 0e5e13206f..0c6aef1607 100644 --- a/libclamunrar/crc.cpp +++ b/libclamunrar/crc.cpp @@ -110,3 +110,164 @@ ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size) #endif +#if 0 +static uint64 crc64_tables[8][256]; // Tables for Slicing-by-8 for CRC64. + +void InitCRC64(uint64 *CRCTab) +{ + const uint64 poly=INT32TO64(0xC96C5795, 0xD7870F42); // 0xC96C5795D7870F42; + for (uint I=0;I<256;I++) + { + uint64 C=I; + for (uint J=0;J<8;J++) + C=(C & 1) ? (C>>1)^poly: (C>>1); + CRCTab[I]=C; + } +} + + +static void InitTables64() +{ + InitCRC64(crc64_tables[0]); + + for (uint I=0;I<256;I++) // Build additional lookup tables. + { + uint64 C=crc64_tables[0][I]; + for (uint J=1;J<8;J++) + { + C=crc64_tables[0][(byte)C]^(C>>8); + crc64_tables[J][I]=C; + } + } +} + + +// We cannot place the intialization to CRC64(), because we use this function +// in multithreaded mode and it conflicts with multithreading. +struct CallInitCRC64 {CallInitCRC64() {InitTables64();}} static CallInit64; + +uint64 CRC64(uint64 StartCRC,const void *Addr,size_t Size) +{ + byte *Data=(byte *)Addr; + + // Align Data to 8 for better performance. + for (;Size>0 && ((size_t)Data & 7)!=0;Size--,Data++) + StartCRC=crc64_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + for (byte *DataEnd=Data+Size/8*8; Data> 8 ) ] ^ + crc64_tables[ 5 ] [ ( byte ) (Index >> 16 ) ] ^ + crc64_tables[ 4 ] [ ( byte ) (Index >> 24 ) ] ^ + crc64_tables[ 3 ] [ ( byte ) (Index >> 32 ) ] ^ + crc64_tables[ 2 ] [ ( byte ) (Index >> 40 ) ] ^ + crc64_tables[ 1 ] [ ( byte ) (Index >> 48 ) ] ^ + crc64_tables[ 0 ] [ ( byte ) (Index >> 56 ) ] ; + } + + for (Size%=8;Size>0;Size--,Data++) // Process left data. + StartCRC=crc64_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + return StartCRC; +} + + +#if 0 +static void TestCRC(); +struct TestCRCStruct {TestCRCStruct() {TestCRC();exit(0);}} GlobalTesCRC; + +void TestCRC() +{ + const uint FirstSize=300; + byte b[FirstSize]; + + if ((CRC32(0xffffffff,(byte*)"testtesttest",12)^0xffffffff)==0x44608e84) + mprintf(L"\nCRC32 test1 OK"); + else + mprintf(L"\nCRC32 test1 FAILED"); + + if (CRC32(0,(byte*)"te\x80st",5)==0xB2E5C5AE) + mprintf(L"\nCRC32 test2 OK"); + else + mprintf(L"\nCRC32 test2 FAILED"); + + for (uint I=0;I<14;I++) // Check for possible int sign extension. + b[I]=(byte)0x7f+I; + if ((CRC32(0xffffffff,b,14)^0xffffffff)==0x1DFA75DA) + mprintf(L"\nCRC32 test3 OK"); + else + mprintf(L"\nCRC32 test3 FAILED"); + + for (uint I=0;IIsSet() || Method==CRYPT_NONE) + if (Method==CRYPT_NONE || !Password->IsSet()) return false; CryptData::Method=Method; wchar PwdW[MAXPASSWORD]; Password->Get(PwdW,ASIZE(PwdW)); + PwdW[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives. + char PwdA[MAXPASSWORD]; WideToChar(PwdW,PwdA,ASIZE(PwdA)); + PwdA[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives. switch(Method) { diff --git a/libclamunrar/crypt.hpp b/libclamunrar/crypt.hpp index f6382ef504..9dc0215998 100644 --- a/libclamunrar/crypt.hpp +++ b/libclamunrar/crypt.hpp @@ -30,6 +30,18 @@ class CryptData uint Lg2Count; // Log2 of PBKDF2 repetition count. byte PswCheckValue[SHA256_DIGEST_SIZE]; byte HashKeyValue[SHA256_DIGEST_SIZE]; + + KDF5CacheItem() {Clean();} + ~KDF5CacheItem() {Clean();} + + void Clean() + { + cleandata(Salt,sizeof(Salt)); + cleandata(Key,sizeof(Key)); + cleandata(&Lg2Count,sizeof(Lg2Count)); + cleandata(PswCheckValue,sizeof(PswCheckValue)); + cleandata(HashKeyValue,sizeof(HashKeyValue)); + } }; struct KDF3CacheItem @@ -39,6 +51,17 @@ class CryptData byte Key[16]; byte Init[16]; bool SaltPresent; + + KDF3CacheItem() {Clean();} + ~KDF3CacheItem() {Clean();} + + void Clean() + { + cleandata(Salt,sizeof(Salt)); + cleandata(Key,sizeof(Key)); + cleandata(Init,sizeof(Init)); + cleandata(&SaltPresent,sizeof(SaltPresent)); + } }; @@ -77,7 +100,6 @@ class CryptData ushort Key15[4]; public: CryptData(); - ~CryptData(); bool SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password, const byte *Salt,const byte *InitV,uint Lg2Cnt, byte *HashKey,byte *PswCheck); @@ -88,6 +110,54 @@ class CryptData static void SetSalt(byte *Salt,size_t SaltSize); }; + +class CheckPassword +{ + public: + enum CONFIDENCE {CONFIDENCE_HIGH,CONFIDENCE_MEDIUM,CONFIDENCE_LOW}; + virtual CONFIDENCE GetConfidence()=0; + virtual bool Check(SecPassword *Password)=0; +}; + +class RarCheckPassword:public CheckPassword +{ + private: + CryptData *Crypt; + uint Lg2Count; + byte Salt[SIZE_SALT50]; + byte InitV[SIZE_INITV]; + byte PswCheck[SIZE_PSWCHECK]; + public: + RarCheckPassword() + { + Crypt=NULL; + } + ~RarCheckPassword() + { + delete Crypt; + } + void Set(byte *Salt,byte *InitV,uint Lg2Count,byte *PswCheck) + { + if (Crypt==NULL) + Crypt=new CryptData; + memcpy(this->Salt,Salt,sizeof(this->Salt)); + memcpy(this->InitV,InitV,sizeof(this->InitV)); + this->Lg2Count=Lg2Count; + memcpy(this->PswCheck,PswCheck,sizeof(this->PswCheck)); + } + bool IsSet() {return Crypt!=NULL;} + + // RAR5 provides the higly reliable 64 bit password verification value. + CONFIDENCE GetConfidence() {return CONFIDENCE_HIGH;} + + bool Check(SecPassword *Password) + { + byte PswCheck[SIZE_PSWCHECK]; + Crypt->SetCryptKeys(false,CRYPT_RAR50,Password,Salt,InitV,Lg2Count,NULL,PswCheck); + return memcmp(PswCheck,this->PswCheck,sizeof(this->PswCheck))==0; + } +}; + void GetRnd(byte *RndBuf,size_t BufSize); void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, diff --git a/libclamunrar/crypt3.cpp b/libclamunrar/crypt3.cpp index fe3bf97b80..e6e3a82cbb 100644 --- a/libclamunrar/crypt3.cpp +++ b/libclamunrar/crypt3.cpp @@ -18,8 +18,9 @@ void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,co if (!Cached) { byte RawPsw[2*MAXPASSWORD+SIZE_SALT30]; - WideToRaw(PwdW,RawPsw,ASIZE(RawPsw)); - size_t RawLength=2*wcslen(PwdW); + size_t PswLength=wcslen(PwdW); + size_t RawLength=2*PswLength; + WideToRaw(PwdW,PswLength,RawPsw,RawLength); if (Salt!=NULL) { memcpy(RawPsw+RawLength,Salt,SIZE_SALT30); diff --git a/libclamunrar/crypt5.cpp b/libclamunrar/crypt5.cpp index 7562469f33..5ed65af811 100644 --- a/libclamunrar/crypt5.cpp +++ b/libclamunrar/crypt5.cpp @@ -21,7 +21,7 @@ static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, sha256_context ICtx; if (ICtxOpt!=NULL && *SetIOpt) - ICtx=*ICtxOpt; // Use already calculated first block context. + ICtx=*ICtxOpt; // Use already calculated the first block context. else { // This calculation is the same for all iterations with same password. @@ -90,10 +90,10 @@ void pbkdf2(const byte *Pwd, size_t PwdLength, byte SaltData[MaxSalt+4]; memcpy(SaltData, Salt, Min(SaltLength,MaxSalt)); - SaltData[SaltLength + 0] = 0; // Salt concatenated to 1. - SaltData[SaltLength + 1] = 0; - SaltData[SaltLength + 2] = 0; - SaltData[SaltLength + 3] = 1; + SaltData[SaltLength + 0] = 0; // Block index appened to salt. + SaltData[SaltLength + 1] = 0; // + SaltData[SaltLength + 2] = 0; // Since we do not request the key width + SaltData[SaltLength + 3] = 1; // exceeding HMAC width, it is always 1. // First iteration: HMAC of password, salt and block index (1). byte U1[SHA256_DIGEST_SIZE]; @@ -140,7 +140,7 @@ void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, for (uint I=0;ILg2Count==Lg2Cnt && Item->Pwd==*Password && + if (Item->Pwd==*Password && Item->Lg2Count==Lg2Cnt && memcmp(Item->Salt,Salt,SIZE_SALT50)==0) { memcpy(Key,Item->Key,sizeof(Key)); diff --git a/libclamunrar/dll.cpp b/libclamunrar/dll.cpp index 0464c9ace6..31818e4980 100644 --- a/libclamunrar/dll.cpp +++ b/libclamunrar/dll.cpp @@ -329,7 +329,7 @@ int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestNa { Data->Cmd.DllError=0; if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT || - Operation==RAR_SKIP) // && !Data->Arc.Solid) + Operation==RAR_SKIP && !Data->Arc.Solid) { if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE && Data->Arc.FileHead.SplitAfter) diff --git a/libclamunrar/dll.rc b/libclamunrar/dll.rc index 7c399f23a4..3ca50eb1f7 100644 --- a/libclamunrar/dll.rc +++ b/libclamunrar/dll.rc @@ -2,8 +2,8 @@ #include VS_VERSION_INFO VERSIONINFO -FILEVERSION 6, 12, 100, 489 -PRODUCTVERSION 6, 12, 100, 489 +FILEVERSION 6, 23, 100, 944 +PRODUCTVERSION 6, 23, 100, 944 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP { @@ -14,9 +14,9 @@ FILETYPE VFT_APP VALUE "CompanyName", "Alexander Roshal\0" VALUE "ProductName", "RAR decompression library\0" VALUE "FileDescription", "RAR decompression library\0" - VALUE "FileVersion", "6.12.0\0" - VALUE "ProductVersion", "6.12.0\0" - VALUE "LegalCopyright", "Copyright � Alexander Roshal 1993-2022\0" + VALUE "FileVersion", "6.23.0\0" + VALUE "ProductVersion", "6.23.0\0" + VALUE "LegalCopyright", "Copyright � Alexander Roshal 1993-2023\0" VALUE "OriginalFilename", "Unrar.dll\0" } } diff --git a/libclamunrar/errhnd.cpp b/libclamunrar/errhnd.cpp index d473b1f898..97193e5ac2 100644 --- a/libclamunrar/errhnd.cpp +++ b/libclamunrar/errhnd.cpp @@ -169,10 +169,13 @@ void ErrorHandler::OpenErrorMsg(const wchar *FileName) void ErrorHandler::OpenErrorMsg(const wchar *ArcName,const wchar *FileName) { - Wait(); // Keep GUI responsive if many files cannot be opened when archiving. uiMsg(UIERROR_FILEOPEN,ArcName,FileName); SysErrMsg(); SetErrorCode(RARX_OPEN); + + // Keep GUI responsive if many files cannot be opened when archiving. + // Call after SysErrMsg to avoid modifying the error code and SysErrMsg text. + Wait(); } diff --git a/libclamunrar/extinfo.cpp b/libclamunrar/extinfo.cpp index 5cb90a4080..4bf8046a74 100644 --- a/libclamunrar/extinfo.cpp +++ b/libclamunrar/extinfo.cpp @@ -21,17 +21,11 @@ #ifndef SFX_MODULE void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) { +#ifdef _WIN_ALL if (Cmd->Test) return; switch(Arc.SubBlockHead.SubType) { -#ifdef _UNIX - case UO_HEAD: - if (Cmd->ProcessOwners) - ExtractUnixOwner20(Arc,Name); - break; -#endif -#ifdef _WIN_ALL case NTACL_HEAD: if (Cmd->ProcessOwners) ExtractACL20(Arc,Name); @@ -39,8 +33,8 @@ void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) case STREAM_HEAD: ExtractStreams20(Arc,Name); break; -#endif } +#endif } #endif @@ -112,6 +106,68 @@ static bool LinkInPath(const wchar *Name) } +// Delete symbolic links in file path, if any, and replace them by directories. +// Prevents extracting files outside of destination folder with symlink chains. +bool LinksToDirs(const wchar *SrcName,const wchar *SkipPart,std::wstring &LastChecked) +{ + // Unlike Unix, Windows doesn't expand lnk1 in symlink targets like + // "lnk1/../dir", but converts the path to "dir". In Unix we need to call + // this function to prevent placing unpacked files outside of destination + // folder if previously we unpacked "dir/lnk1" -> "..", + // "dir/lnk2" -> "lnk1/.." and "dir/lnk2/anypath/poc.txt". + // We may still need this function to prevent abusing symlink chains + // in link source path if we remove detection of such chains + // in IsRelativeSymlinkSafe. This function seems to make other symlink + // related safety checks redundant, but for now we prefer to keep them too. + // + // 2022.12.01: the performance impact is minimized after adding the check + // against the previous path and enabling this verification only after + // extracting a symlink with ".." in target. So we enabled it for Windows + // as well for extra safety. +//#ifdef _UNIX + wchar Path[NM]; + if (wcslen(SrcName)>=ASIZE(Path)) + return false; // It should not be that long, skip. + wcsncpyz(Path,SrcName,ASIZE(Path)); + + size_t SkipLength=wcslen(SkipPart); + + if (SkipLength>0 && wcsncmp(Path,SkipPart,SkipLength)!=0) + SkipLength=0; // Parameter validation, not really needed now. + + // Do not check parts already checked in previous path to improve performance. + for (uint I=0;Path[I]!=0 && ISkipLength) + SkipLength=I; + + wchar *Name=Path; + if (SkipLength>0) + { + // Avoid converting symlinks in destination path part specified by user. + Name+=SkipLength; + while (IsPathDiv(*Name)) + Name++; + } + + for (wchar *s=Path+wcslen(Path)-1;s>Name;s--) + if (IsPathDiv(*s)) + { + *s=0; + FindData FD; + if (FindFile::FastFind(Path,&FD,true) && FD.IsLink) +#ifdef _WIN_ALL + if (!DelDir(Path)) +#else + if (!DelFile(Path)) +#endif + return false; // Couldn't delete the symlink to replace it with directory. + } + LastChecked=SrcName; +//#endif + return true; +} + + bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName) { // Catch root dir based /path/file paths also as stuff like \\?\. @@ -131,10 +187,14 @@ bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *Pr UpLevels++; TargetName++; } - // If link target includes "..", it must not have another links - // in the path, because they can bypass our safety check. For example, + // If link target includes "..", it must not have another links in its + // source path, because they can bypass our safety check. For example, // suppose we extracted "lnk1" -> "." first and "lnk1/lnk2" -> ".." next - // or "dir/lnk1" -> ".." first and "dir/lnk1/lnk2" -> ".." next. + // or "dir/lnk1" -> ".." first, "dir/lnk1/lnk2" -> ".." next and + // file "dir/lnk1/lnk2/poc.txt" last. + // Do not confuse with link chains in target, this is in link source path. + // It is important for Windows too, though this check can be omitted + // if LinksToDirs is invoked in Windows as well. if (UpLevels>0 && LinkInPath(PrepSrcName)) return false; @@ -160,15 +220,26 @@ bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *Pr } -bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName,bool &UpLink) { + // Returning true in Uplink indicates that link target might include ".." + // and enables additional checks. It is ok to falsely return true here, + // as it implies only the minor performance penalty. But we shall always + // return true for links with ".." in target for security reason. + + UpLink=true; // Assume the target might include potentially unsafe "..". +#if defined(SAVE_LINKS) && defined(_UNIX) || defined(_WIN_ALL) + if (Arc.Format==RARFMT50) // For RAR5 archives we can check RedirName for both Unix and Windows. + UpLink=wcsstr(Arc.FileHead.RedirName,L"..")!=NULL; +#endif + #if defined(SAVE_LINKS) && defined(_UNIX) // For RAR 3.x archives we process links even in test mode to skip link data. if (Arc.Format==RARFMT15) - return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName); + return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName,UpLink); if (Arc.Format==RARFMT50) return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead); -#elif defined _WIN_ALL +#elif defined(_WIN_ALL) // RAR 5.0 archives store link information in file header, so there is // no need to additionally test it if we do not create a file. if (Arc.Format==RARFMT50) diff --git a/libclamunrar/extinfo.hpp b/libclamunrar/extinfo.hpp index f3c7511b48..d8551d463d 100644 --- a/libclamunrar/extinfo.hpp +++ b/libclamunrar/extinfo.hpp @@ -1,8 +1,9 @@ #ifndef _RAR_EXTINFO_ #define _RAR_EXTINFO_ +bool LinksToDirs(const wchar *SrcName,const wchar *SkipPart,std::wstring &LastChecked); bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName); -bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName); +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName,bool &UpLink); #ifdef _UNIX void SetUnixOwner(Archive &Arc,const wchar *FileName); #endif diff --git a/libclamunrar/extract.cpp b/libclamunrar/extract.cpp index dc109b9d90..f5e8fa445d 100644 --- a/libclamunrar/extract.cpp +++ b/libclamunrar/extract.cpp @@ -5,10 +5,30 @@ CmdExtract::CmdExtract(CommandData *Cmd) CmdExtract::Cmd=Cmd; *ArcName=0; - *DestFileName=0; + ArcAnalyzed=false; + Analyze=new AnalyzeData; + memset(Analyze,0,sizeof(*Analyze)); + TotalFileCount=0; + + // Common for all archives involved. Set here instead of DoExtract() + // to use in unrar.dll too. Allows to avoid LinksToDirs() calls + // and save CPU time in no symlinks including ".." in target were extracted. +#if defined(_WIN_ALL) + // We can't expand symlink path components in another symlink target + // in Windows. We can't create symlinks in Android now. Even though we do not + // really need LinksToDirs() calls in these systems, we still call it + // for extra safety, but only if symlink with ".." in target was extracted. + ConvertSymlinkPaths=false; +#else + // We enable it by default in Unix to care about the case when several + // archives are unpacked to same directory with several independent RAR runs. + // Worst case performance penalty for a lot of small files seems to be ~3%. + ConvertSymlinkPaths=true; +#endif + Unp=new Unpack(&DataIO); #ifdef RAR_SMP Unp->SetThreads(Cmd->Threads); @@ -18,7 +38,26 @@ CmdExtract::CmdExtract(CommandData *Cmd) CmdExtract::~CmdExtract() { + FreeAnalyzeData(); delete Unp; + delete Analyze; +} + + +void CmdExtract::FreeAnalyzeData() +{ + for (size_t I=0;ICommand[0]); - FindData FD; - while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) - if (FindFile::FastFind(ArcName,&FD)) - DataIO.TotalArcSize+=FD.Size; + if (*Cmd->UseStdin==0) + { + FindData FD; + while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + if (FindFile::FastFind(ArcName,&FD)) + DataIO.TotalArcSize+=FD.Size; + } Cmd->ArcNames.Rewind(); while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) @@ -97,7 +139,11 @@ void CmdExtract::ExtractArchiveInit(Archive &Arc) AllMatchesExact=true; AnySolidDataUnpackedWell=false; + ArcAnalyzed=false; + StartTime.SetCurrentTime(); + + LastCheckedSymlink.clear(); } @@ -168,20 +214,33 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() } #endif + Arc.ViewComment(); // Must be before possible EXTRACT_ARC_REPEAT. + int64 VolumeSetSize=0; // Total size of volumes after the current volume. +#ifndef SFX_MODULE + if (!ArcAnalyzed && *Cmd->UseStdin==0) + { + AnalyzeArchive(Arc.FileName,Arc.Volume,Arc.NewNumbering); + ArcAnalyzed=true; // Avoid repeated analysis on EXTRACT_ARC_REPEAT. + } +#endif + if (Arc.Volume) { #ifndef SFX_MODULE // Try to speed up extraction for independent solid volumes by starting // extraction from non-first volume if we can. - if (!UseExactVolName && Arc.Solid && DetectStartVolume(Arc.FileName,Arc.NewNumbering)) + if (*Analyze->StartName!=0) { + wcsncpyz(ArcName,Analyze->StartName,ASIZE(ArcName)); + *Analyze->StartName=0; + UseExactVolName=true; return EXTRACT_ARC_REPEAT; } #endif - + // Calculate the total size of all accessible volumes. // This size is necessary to display the correct total progress indicator. @@ -216,7 +275,13 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() else uiStartArchiveExtract(!Cmd->Test,ArcName); - Arc.ViewComment(); +#ifndef SFX_MODULE + if (Analyze->StartPos!=0) + { + Arc.Seek(Analyze->StartPos,SEEK_SET); + Analyze->StartPos=0; + } +#endif while (1) @@ -272,7 +337,14 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return false; HEADER_TYPE HeaderType=Arc.GetHeaderType(); - if (HeaderType!=HEAD_FILE) + if (HeaderType==HEAD_FILE) + { + // Unlike Arc.FileName, ArcName might store an old volume name here. + if (Analyze->EndPos!=0 && Analyze->EndPos==Arc.CurBlockPos && + (*Analyze->EndName==0 || wcscmp(Analyze->EndName,Arc.FileName)==0)) + return false; + } + else { #ifndef SFX_MODULE if (Arc.Format==RARFMT15 && HeaderType==HEAD3_OLDSERVICE && PrevProcessed) @@ -315,6 +387,9 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (Arc.FileHead.UnpSize<0) Arc.FileHead.UnpSize=0; + // 2022.03.20: We might remove this check in the future. + // It duplicates Analyze->EndPos and Analyze->EndName in all cases except + // volumes on removable media. if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) return false; @@ -413,13 +488,39 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) FirstFile=false; #endif + bool RefTarget=false; + if (!MatchFound) + for (size_t I=0;ITest) // While harmless, it is useless for 't'. + { + // If reference source isn't selected, but target is selected, + // we unpack the source under the temporary name and then rename + // or copy it to target name. We do not unpack it under the target + // name immediately, because the same source can be used by multiple + // targets and it is possible that first target isn't unpacked + // for some reason. Also targets might have associated service blocks + // like ACLs. All this would complicate processing a lot. + wcsncpyz(DestFileName,*Cmd->TempPath!=0 ? Cmd->TempPath:Cmd->ExtrPath,ASIZE(DestFileName)); + AddEndSlash(DestFileName,ASIZE(DestFileName)); + wcsncatz(DestFileName,L"__tmp_reference_source_",ASIZE(DestFileName)); + MkTemp(DestFileName,ASIZE(DestFileName)); + MatchedRef->TmpName=wcsdup(DestFileName); + } + RefTarget=true; // Need it even for 't' to test the reference source. + break; + } + if (Arc.FileHead.Encrypted && Cmd->SkipEncrypted) if (Arc.Solid) return false; // Abort the entire extraction for solid archive. else MatchFound=false; // Skip only the current file for non-solid archive. - if (MatchFound || (SkipSolid=Arc.Solid)!=0) + if (MatchFound || RefTarget || (SkipSolid=Arc.Solid)!=0) { // First common call of uiStartFileExtract. It is done before overwrite // prompts, so if SkipSolid state is changed below, we'll need to make @@ -427,7 +528,8 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) return false; - ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); + if (!RefTarget) + ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); // DestFileName can be set empty in case of excessive -ap switch. ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; @@ -464,9 +566,13 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return !Arc.Solid; // Can try extracting next file only in non-solid archive. } - while (true) // Repeat the password prompt for wrong and empty passwords. + if (Arc.FileHead.Encrypted) { - if (Arc.FileHead.Encrypted) + RarCheckPassword CheckPwd; + if (Arc.Format==RARFMT50 && Arc.FileHead.UsePswCheck && !Arc.BrokenHeader) + CheckPwd.Set(Arc.FileHead.Salt,Arc.FileHead.InitV,Arc.FileHead.Lg2Count,Arc.FileHead.PswCheck); + + while (true) // Repeat the password prompt for wrong and empty passwords. { // Stop archive extracting if user cancelled a password prompt. #ifdef RARDLL @@ -476,77 +582,83 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) return false; } #else - if (!ExtrGetPassword(Arc,ArcFileName)) + if (!ExtrGetPassword(Arc,ArcFileName,CheckPwd.IsSet() ? &CheckPwd:NULL)) { PasswordCancelled=true; return false; } #endif - } - // Set a password before creating the file, so we can skip creating - // in case of wrong password. - SecPassword FilePassword=Cmd->Password; -#if defined(_WIN_ALL) && !defined(SFX_MODULE) - ConvertDosPassword(Arc,FilePassword); -#endif - - byte PswCheck[SIZE_PSWCHECK]; - DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, - Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, - Arc.FileHead.InitV,Arc.FileHead.Lg2Count, - Arc.FileHead.HashKey,PswCheck); - - // If header is damaged, we cannot rely on password check value, - // because it can be damaged too. - if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && - memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && - !Arc.BrokenHeader) - { - if (GlobalPassword) // For -p or Ctrl+P to avoid the infinite loop. + // Set a password before creating the file, so we can skip creating + // in case of wrong password. + SecPassword FilePassword=Cmd->Password; + #if defined(_WIN_ALL) && !defined(SFX_MODULE) + ConvertDosPassword(Arc,FilePassword); + #endif + + byte PswCheck[SIZE_PSWCHECK]; + DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, + Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, + Arc.FileHead.InitV,Arc.FileHead.Lg2Count, + Arc.FileHead.HashKey,PswCheck); + + // If header is damaged, we cannot rely on password check value, + // because it can be damaged too. + if (Arc.FileHead.UsePswCheck && !Arc.BrokenHeader && + memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0) { - // This message is used by Android GUI to reset cached passwords. - // Update appropriate code if changed. - uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); - } - else // For passwords entered manually. - { - // This message is used by Android GUI and Windows GUI and SFX to - // reset cached passwords. Update appropriate code if changed. - uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); - Cmd->Password.Clean(); - - // Avoid new requests for unrar.dll to prevent the infinite loop - // if app always returns the same password. -#ifndef RARDLL - continue; // Request a password again. -#endif + if (GlobalPassword) // For -p or Ctrl+P to avoid the infinite loop. + { + // This message is used by Android GUI to reset cached passwords. + // Update appropriate code if changed. + uiMsg(UIERROR_BADPSW,Arc.FileName,ArcFileName); + } + else // For passwords entered manually. + { + // This message is used by Android GUI and Windows GUI and SFX to + // reset cached passwords. Update appropriate code if changed. + uiMsg(UIWAIT_BADPSW,Arc.FileName,ArcFileName); + Cmd->Password.Clean(); + + // Avoid new requests for unrar.dll to prevent the infinite loop + // if app always returns the same password. + #ifndef RARDLL + continue; // Request a password again. + #endif + } + #ifdef RARDLL + // If we already have ERAR_EOPEN as result of missing volume, + // we should not replace it with less precise ERAR_BAD_PASSWORD. + if (Cmd->DllError!=ERAR_EOPEN) + Cmd->DllError=ERAR_BAD_PASSWORD; + #endif + ErrHandler.SetErrorCode(RARX_BADPWD); + ExtrFile=false; } -#ifdef RARDLL - // If we already have ERAR_EOPEN as result of missing volume, - // we should not replace it with less precise ERAR_BAD_PASSWORD. - if (Cmd->DllError!=ERAR_EOPEN) - Cmd->DllError=ERAR_BAD_PASSWORD; -#endif - ErrHandler.SetErrorCode(RARX_BADPWD); - ExtrFile=false; + break; } - break; } + else + DataIO.SetEncryption(false,CRYPT_NONE,NULL,NULL,NULL,0,NULL,NULL); #ifdef RARDLL if (*Cmd->DllDestName!=0) wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); #endif + if (ExtrFile && Command!='P' && !Cmd->Test && !Cmd->AbsoluteLinks && + ConvertSymlinkPaths) + ExtrFile=LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); + File CurFile; bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; - if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) + if (LinkEntry && (Arc.FileHead.RedirType!=FSREDIR_FILECOPY)) { if (ExtrFile && Command!='P' && !Cmd->Test) { - // Overwrite prompt for symbolic and hard links. + // Overwrite prompt for symbolic and hard links and when we move + // a temporary file to the file reference instead of copying it. bool UserReject=false; if (FileExist(DestFileName) && !UserReject) FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); @@ -667,7 +779,17 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) { wchar RedirName[NM]; - ConvertPath(Arc.FileHead.RedirName,RedirName,ASIZE(RedirName)); + + // 2022.11.15: Might be needed when unpacking WinRAR 5.0 links with + // Unix RAR. WinRAR 5.0 used \ path separators here, when beginning + // from 5.10 even Windows version uses / internally and converts + // them to \ when reading FHEXTRA_REDIR. + // We must perform this conversion before ConvertPath call, + // so paths mixing different slashes like \dir1/dir2\file are + // processed correctly. + SlashToNative(Arc.FileHead.RedirName,RedirName,ASIZE(RedirName)); + + ConvertPath(RedirName,RedirName,ASIZE(RedirName)); wchar NameExisting[NM]; ExtrPrepareName(Arc,RedirName,NameExisting,ASIZE(NameExisting)); @@ -675,17 +797,37 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (Type==FSREDIR_HARDLINK) LinkSuccess=ExtractHardlink(Cmd,DestFileName,NameExisting,ASIZE(NameExisting)); else - LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); + LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,RedirName,DestFileName,NameExisting,ASIZE(NameExisting),Arc.FileHead.UnpSize); } else if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) { if (FileCreateMode) - LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); + { + bool UpLink; + LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName,UpLink); + + // Unix symlink can have its own owner data. + if (LinkSuccess) + SetFileHeaderExtra(Cmd,Arc,DestFileName); + + ConvertSymlinkPaths|=LinkSuccess && UpLink; + + // We do not actually need to reset the cache here if we cache + // only the single last checked path, because at this point + // it will always contain the link own path and link can't + // overwrite its parent folder. But if we ever decide to cache + // several already checked paths, we'll need to reset them here. + // Otherwise if no files were created in one of such paths, + // let's say because of file create error, it might be possible + // to overwrite the path with link and avoid checks. We keep this + // code here as a reminder in case of possible modifications. + LastCheckedSymlink.clear(); // Reset cache for safety reason. + } } else { - uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,DestFileName); + uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,ArcFileName); LinkSuccess=false; } @@ -709,6 +851,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); Unp->SetDestSize(Arc.FileHead.UnpSize); #ifndef SFX_MODULE + // RAR 1.3 - 1.5 archives do not set per file solid flag. if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) Unp->DoUnpack(15,FileCount>1 && Arc.Solid); else @@ -866,22 +1009,64 @@ void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize) } -bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,const wchar *RedirName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize,int64 UnpSize) { - SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. - File Existing; - if (!Existing.WOpen(NameExisting)) + if (!Existing.Open(NameExisting)) { - uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); - uiMsg(UIERROR_FILECOPYHINT,ArcName); + bool OpenFailed=true; + // If we couldn't find the existing file, check if match is present + // in temporary reference sources list. + for (size_t I=0;IDllError=ERAR_EREFERENCE; + Cmd->DllError=ERAR_EREFERENCE; #endif - return false; + return false; + } } - Array Buffer(0x100000); + Array Buffer(0x100000); int64 CopySize=0; while (true) @@ -890,6 +1075,10 @@ bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar * int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); if (ReadSize==0) break; + // Update only the current file progress in WinRAR, set the total to 0 + // to keep it as is. It looks better for WinRAR. + uiExtractProgress(CopySize,UnpSize,0,0); + New.Write(&Buffer[0],ReadSize); CopySize+=ReadSize; } @@ -900,6 +1089,16 @@ bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar * void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) { + if (Cmd->Test) + { + // Destination name conversion isn't needed for simple archive test. + // This check also allows to avoid issuing "Attempting to correct... + // Renaming..." messages in MakeNameCompatible() below for problematic + // names like aux.txt when testing an archive. + wcsncpyz(DestName,ArcFileName,DestSize); + return; + } + wcsncpyz(DestName,Cmd->ExtrPath,DestSize); if (*Cmd->ExtrPath!=0) @@ -1033,11 +1232,11 @@ bool CmdExtract::ExtrDllGetPassword() #ifndef RARDLL -bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) +bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName,RarCheckPassword *CheckPwd) { if (!Cmd->Password.IsSet()) { - if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)/* || !Cmd->Password.IsSet()*/) + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)/* || !Cmd->Password.IsSet()*/) { // Suppress "test is ok" message if user cancelled the password prompt. uiMsg(UIERROR_INCERRCOUNT); @@ -1055,7 +1254,7 @@ bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) case -1: ErrHandler.Exit(RARX_USERBREAK); case 2: - if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password,CheckPwd)) return false; break; case 3: @@ -1131,6 +1330,8 @@ void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) DirExist=FileExist(DestFileName) && IsDir(GetFileAttr(DestFileName)); if (!DirExist) { + if (!Cmd->AbsoluteLinks && ConvertSymlinkPaths) + LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); CreatePath(DestFileName,true,Cmd->DisableNames); MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); } @@ -1212,6 +1413,8 @@ bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) MakeNameUsable(DestFileName,true); + if (!Cmd->AbsoluteLinks && ConvertSymlinkPaths) + LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); CreatePath(DestFileName,true,Cmd->DisableNames); if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) { @@ -1258,31 +1461,59 @@ bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) #ifndef SFX_MODULE -// To speed up solid volumes extraction, try to find a non-first start volume, -// which still allows to unpack all files. It is possible for independent -// solid volumes with solid statistics reset in the beginning. -bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) +// Find non-matched reference sources in solid and non-solid archives. +// Detect the optimal start position for semi-solid archives +// and optimal start volume for independent solid volumes. +// +// Alternatively we could collect references while extracting an archive +// and perform the second extraction pass for references only. +// But it would be slower for solid archives than scaning headers +// in first pass and extracting everything in second, as implemented now. +// +void CmdExtract::AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumbering) { + FreeAnalyzeData(); // If processing non-first archive in multiple archives set. + wchar *ArgName=Cmd->FileArgs.GetString(); Cmd->FileArgs.Rewind(); if (ArgName!=NULL && (wcscmp(ArgName,L"*")==0 || wcscmp(ArgName,L"*.*")==0)) - return false; // No need to check further for * and *.* masks. + return; // No need to check further for * and *.* masks. - wchar StartName[NM]; - *StartName=0; - // Start search from first volume if all volumes preceding current are available. wchar NextName[NM]; - GetFirstVolIfFullSet(VolName,NewNumbering,NextName,ASIZE(NextName)); + if (Volume) + GetFirstVolIfFullSet(ArcName,NewNumbering,NextName,ASIZE(NextName)); + else + wcsncpyz(NextName,ArcName,ASIZE(NextName)); + + bool MatchFound=false; + bool PrevMatched=false; + bool OpenNext=false; - bool Matched=false; - while (!Matched) + bool FirstVolume=true; + + // We shall set FirstFile once for all volumes and not for each volume. + // So we do not reuse the outdated Analyze->StartPos from previous volume + // if extracted file resides completely in the beginning of current one. + bool FirstFile=true; + + while (true) { Archive Arc(Cmd); - if (!Arc.Open(NextName) || !Arc.IsArchive(false) || !Arc.Volume) + if (!Arc.Open(NextName) || !Arc.IsArchive(false)) + { + if (OpenNext) + { + // If we couldn't open trailing volumes, we can't set early exit + // parameters. It is possible that some volume are on removable media + // and will be provided by user when extracting. + *Analyze->EndName=0; + Analyze->EndPos=0; + } break; + } - bool OpenNext=false; + OpenNext=false; while (Arc.ReadHeader()>0) { Wait(); @@ -1295,17 +1526,88 @@ bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) } if (HeaderType==HEAD_FILE) { + if ((Arc.Format==RARFMT14 || Arc.Format==RARFMT15) && Arc.FileHead.UnpVer<=15) + { + // RAR versions earlier than 2.0 do not set per file solid flag. + // They have only the global archive solid flag, so we can't + // reliably analyze them here. + OpenNext=false; + break; + } + if (!Arc.FileHead.SplitBefore) { - if (!Arc.FileHead.Solid) // Can start extraction from here. - wcsncpyz(StartName,NextName,ASIZE(StartName)); + if (!MatchFound && !Arc.FileHead.Solid) // Can start extraction from here. + { + // We would gain nothing and unnecessarily complicate extraction + // if we set StartName for first volume or StartPos for first + // archived file. + if (!FirstVolume) + wcsncpyz(Analyze->StartName,NextName,ASIZE(Analyze->StartName)); + + // We shall set FirstFile once for all volumes for this code + // to work properly. Alternatively we could append + // "|| Analyze->StartPos!=0" to the condition, so we do not reuse + // the outdated Analyze->StartPos value from previous volume. + if (!FirstFile) + Analyze->StartPos=Arc.CurBlockPos; + } if (Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL,0)!=0) { - Matched=true; // First matched file found, must stop further scan. - break; + MatchFound = true; + PrevMatched = true; + + // Reset the previously set early exit position, if any, because + // we found a new matched file. + Analyze->EndPos=0; + + // Matched file reference pointing at maybe non-matched source file. + // Even though we know RedirName, we can't check if source file + // is certainly non-matched, because it can be filtered out by + // date or attributes, which we do not know here. + if (Arc.FileHead.RedirType==FSREDIR_FILECOPY) + { + bool AlreadyAdded=false; + for (size_t I=0;IEndName,NextName,ASIZE(Analyze->EndName)); + Analyze->EndPos=Arc.CurBlockPos; + } + PrevMatched=false; } } + + FirstFile=false; if (Arc.FileHead.SplitAfter) { OpenNext=true; // Allow open next volume. @@ -1316,16 +1618,25 @@ bool CmdExtract::DetectStartVolume(const wchar *VolName,bool NewNumbering) } Arc.Close(); - if (!OpenNext) - break; + if (Volume && OpenNext) + { + NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); + FirstVolume=false; - NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); + // Needed for multivolume archives. Added in case some 'break' + // will quit early from loop above, so we do not set it in the loop. + // Now it can happen for hypothetical archive without file records + // and with HEAD_ENDARC record. + FirstFile=false; + } + else + break; } - bool NewStartFound=wcscmp(VolName,StartName)!=0; - if (NewStartFound) // Found a new volume to start extraction. - wcsncpyz(ArcName,StartName,ASIZE(ArcName)); - - return NewStartFound; + + // If file references are present, we can't reliably skip in semi-solid + // archives, because reference source can be present in skipped data. + if (RefList.Size()!=0) + memset(Analyze,0,sizeof(*Analyze)); } #endif diff --git a/libclamunrar/extract.hpp b/libclamunrar/extract.hpp index 159759b563..18396c5b93 100644 --- a/libclamunrar/extract.hpp +++ b/libclamunrar/extract.hpp @@ -6,13 +6,32 @@ enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT}; class CmdExtract { private: + struct ExtractRef + { + wchar *RefName; + wchar *TmpName; + uint64 RefCount; + }; + Array RefList; + + struct AnalyzeData + { + wchar StartName[NM]; + uint64 StartPos; + wchar EndName[NM]; + uint64 EndPos; + } *Analyze; + + bool ArcAnalyzed; + + void FreeAnalyzeData(); EXTRACT_ARC_CODE ExtractArchive(); - bool ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); + bool ExtractFileCopy(File &New,wchar *ArcName,const wchar *RedirName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize,int64 UnpSize); void ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize); #ifdef RARDLL bool ExtrDllGetPassword(); #else - bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName); + bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName,RarCheckPassword *CheckPwd); #endif #if defined(_WIN_ALL) && !defined(SFX_MODULE) void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd); @@ -21,7 +40,7 @@ class CmdExtract bool ExtrCreateFile(Archive &Arc,File &CurFile); bool CheckUnpVer(Archive &Arc,const wchar *ArcFileName); #ifndef SFX_MODULE - bool DetectStartVolume(const wchar *VolName,bool NewNumbering); + void AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumbering); void GetFirstVolIfFullSet(const wchar *SrcName,bool NewNumbering,wchar *DestName,size_t DestSize); #endif @@ -52,6 +71,15 @@ class CmdExtract bool PrevProcessed; // If previous file was successfully extracted or tested. wchar DestFileName[NM]; bool PasswordCancelled; + + // In Windows it is set to true if at least one symlink with ".." + // in target was extracted. + bool ConvertSymlinkPaths; + + // Last path checked for symlinks. We use it to improve the performance, + // so we do not check recently checked folders again. + std::wstring LastCheckedSymlink; + #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) bool Fat32,NotFat32; #endif diff --git a/libclamunrar/filcreat.cpp b/libclamunrar/filcreat.cpp index 8e7562cb6f..d58e4f6fe9 100644 --- a/libclamunrar/filcreat.cpp +++ b/libclamunrar/filcreat.cpp @@ -3,7 +3,7 @@ // If NewFile==NULL, we delete created file after user confirmation. // It is useful if we need to overwrite an existing folder or file, // but need user confirmation for that. -bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, +bool FileCreate(CommandData *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly) { if (UserReject!=NULL) diff --git a/libclamunrar/filcreat.hpp b/libclamunrar/filcreat.hpp index 44f801d4ee..456a4a4a13 100644 --- a/libclamunrar/filcreat.hpp +++ b/libclamunrar/filcreat.hpp @@ -1,7 +1,7 @@ #ifndef _RAR_FILECREATE_ #define _RAR_FILECREATE_ -bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, +bool FileCreate(CommandData *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, bool *UserReject,int64 FileSize=INT64NDF, RarTime *FileTime=NULL,bool WriteOnly=false); diff --git a/libclamunrar/file.cpp b/libclamunrar/file.cpp index e7b584da6b..7bf60fd4ef 100644 --- a/libclamunrar/file.cpp +++ b/libclamunrar/file.cpp @@ -522,29 +522,35 @@ bool File::RawSeek(int64 Offset,int Method) { if (hFile==FILE_BAD_HANDLE) return true; - if (!IsSeekable()) + if (!IsSeekable()) // To extract archives from stdin with -si. { - if (Method==SEEK_CUR) - { - Offset+=CurFilePos; - Method=SEEK_SET; - } - if (Method==SEEK_SET && Offset>=CurFilePos) // Reading for seek forward. + // We tried to dynamically allocate 32 KB buffer here, but it improved + // speed in Windows 10 by mere ~1.5%. + byte Buf[4096]; + if (Method==SEEK_CUR || Method==SEEK_SET && Offset>=CurFilePos) { - uint64 SkipSize=Offset-CurFilePos; - while (SkipSize>0) + uint64 SkipSize=Method==SEEK_CUR ? Offset:Offset-CurFilePos; + while (SkipSize>0) // Reading to emulate seek forward. { - byte Buf[4096]; int ReadSize=Read(Buf,(size_t)Min(SkipSize,ASIZE(Buf))); if (ReadSize<=0) return false; SkipSize-=ReadSize; + CurFilePos+=ReadSize; } - CurFilePos=Offset; + return true; + } + // May need it in FileLength() in Archive::UnexpEndArcMsg() when unpacking + // RAR 4.x archives without the end of archive block created with -en. + if (Method==SEEK_END) + { + int ReadSize; + while ((ReadSize=Read(Buf,ASIZE(Buf)))>0) + CurFilePos+=ReadSize; return true; } - return false; // Backward or end of file seek on unseekable file. + return false; // Backward seek on unseekable file. } if (Offset<0 && Method!=SEEK_SET) { @@ -732,17 +738,40 @@ void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) } -void File::GetOpenFileTime(RarTime *ft) +#ifdef _UNIX +void File::StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta) { -#ifdef _WIN_ALL - FILETIME FileTime; - GetFileTime(hFile,NULL,NULL,&FileTime); - ft->SetWinFT(&FileTime); +#ifdef UNIX_TIME_NS +#if defined(_APPLE) + if (ftm!=NULL) ftm->SetUnixNS(st.st_mtimespec.tv_sec*(uint64)1000000000+st.st_mtimespec.tv_nsec); + if (ftc!=NULL) ftc->SetUnixNS(st.st_ctimespec.tv_sec*(uint64)1000000000+st.st_ctimespec.tv_nsec); + if (fta!=NULL) fta->SetUnixNS(st.st_atimespec.tv_sec*(uint64)1000000000+st.st_atimespec.tv_nsec); +#else + if (ftm!=NULL) ftm->SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec); + if (ftc!=NULL) ftc->SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec); + if (fta!=NULL) fta->SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec); +#endif +#else + if (ftm!=NULL) ftm->SetUnix(st.st_mtime); + if (ftc!=NULL) ftc->SetUnix(st.st_ctime); + if (fta!=NULL) fta->SetUnix(st.st_atime); +#endif +} #endif -#if defined(_UNIX) || defined(_EMX) + + +void File::GetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#ifdef _WIN_ALL + FILETIME ctime,atime,mtime; + GetFileTime(hFile,&ctime,&atime,&mtime); + if (ftm!=NULL) ftm->SetWinFT(&mtime); + if (ftc!=NULL) ftc->SetWinFT(&ctime); + if (fta!=NULL) fta->SetWinFT(&atime); +#elif defined(_UNIX) struct stat st; fstat(GetFD(),&st); - ft->SetUnix(st.st_mtime); + StatToRarTime(st,ftm,ftc,fta); #endif } diff --git a/libclamunrar/file.hpp b/libclamunrar/file.hpp index 1de5fe6df3..5f55de960e 100644 --- a/libclamunrar/file.hpp +++ b/libclamunrar/file.hpp @@ -14,8 +14,6 @@ #define FILE_BAD_HANDLE NULL #endif -class RAROptions; - enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD}; enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR}; @@ -88,6 +86,9 @@ class File wchar FileName[NM]; FILE_ERRORTYPE ErrorType; + + byte *SeekBuf; // To read instead of seek for stdin files. + static const size_t SeekBufSize=0x10000; public: File(); virtual ~File(); @@ -118,7 +119,10 @@ class File void SetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL); void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL); static void SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta); - void GetOpenFileTime(RarTime *ft); +#ifdef _UNIX + static void StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta); +#endif + void GetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL); virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;} // 'virtual' for MultiFile class. int64 FileLength(); void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;} diff --git a/libclamunrar/filefn.cpp b/libclamunrar/filefn.cpp index 883c26e2bc..aaef305b8e 100644 --- a/libclamunrar/filefn.cpp +++ b/libclamunrar/filefn.cpp @@ -320,7 +320,6 @@ bool SetFileAttr(const wchar *Name,uint Attr) } -#if 0 wchar *MkTemp(wchar *Name,size_t MaxSize) { size_t Length=wcslen(Name); @@ -354,7 +353,6 @@ wchar *MkTemp(wchar *Name,size_t MaxSize) } return Name; } -#endif #if !defined(SFX_MODULE) @@ -399,7 +397,7 @@ void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size, if ((Flags & CALCFSUM_SHOWPROGRESS)!=0) { // Update only the current file progress in WinRAR, set the total to 0 - // to keep it as is. It looks better for WinRAR, + // to keep it as is. It looks better for WinRAR. uiExtractProgress(TotalRead,FileLength,0,0); } else diff --git a/libclamunrar/filefn.hpp b/libclamunrar/filefn.hpp index 36dc703953..53d86653fe 100644 --- a/libclamunrar/filefn.hpp +++ b/libclamunrar/filefn.hpp @@ -27,9 +27,7 @@ bool IsDeleteAllowed(uint FileAttr); void PrepareToDelete(const wchar *Name); uint GetFileAttr(const wchar *Name); bool SetFileAttr(const wchar *Name,uint Attr); -#if 0 wchar* MkTemp(wchar *Name,size_t MaxSize); -#endif enum CALCFSUM_FLAGS {CALCFSUM_SHOWTEXT=1,CALCFSUM_SHOWPERCENT=2,CALCFSUM_SHOWPROGRESS=4,CALCFSUM_CURPOS=8}; diff --git a/libclamunrar/find.cpp b/libclamunrar/find.cpp index 04702ab87e..c9f2c5768c 100644 --- a/libclamunrar/find.cpp +++ b/libclamunrar/find.cpp @@ -117,7 +117,7 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) if (hFind==INVALID_HANDLE_VALUE) return false; FindClose(hFind); -#else +#elif defined(_UNIX) char FindMaskA[NM]; WideToChar(FindMask,FindMaskA,ASIZE(FindMaskA)); @@ -143,21 +143,7 @@ bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) fd->FileAttr=st.st_mode; fd->Size=st.st_size; -#ifdef UNIX_TIME_NS -#if defined(_APPLE) - fd->mtime.SetUnixNS(st.st_mtimespec.tv_sec*(uint64)1000000000+st.st_mtimespec.tv_nsec); - fd->atime.SetUnixNS(st.st_atimespec.tv_sec*(uint64)1000000000+st.st_atimespec.tv_nsec); - fd->ctime.SetUnixNS(st.st_ctimespec.tv_sec*(uint64)1000000000+st.st_ctimespec.tv_nsec); -#else - fd->mtime.SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec); - fd->atime.SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec); - fd->ctime.SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec); -#endif -#else - fd->mtime.SetUnix(st.st_mtime); - fd->atime.SetUnix(st.st_atime); - fd->ctime.SetUnix(st.st_ctime); -#endif + File::StatToRarTime(st,&fd->mtime,&fd->ctime,&fd->atime); wcsncpyz(fd->Name,FindMask,ASIZE(fd->Name)); #endif diff --git a/libclamunrar/getbits.cpp b/libclamunrar/getbits.cpp index e4db2695fb..5d5ad2bb2c 100644 --- a/libclamunrar/getbits.cpp +++ b/libclamunrar/getbits.cpp @@ -5,11 +5,11 @@ BitInput::BitInput(bool AllocBuffer) ExternalBuffer=false; if (AllocBuffer) { - // getbits32 attempts to read data from InAddr, ... InAddr+3 positions. - // So let's allocate 3 additional bytes for situation, when we need to + // getbits*() attempt to read data from InAddr, ... InAddr+4 positions. + // So let's allocate 4 additional bytes for situation, when we need to // read only 1 byte from the last position of buffer and avoid a crash - // from access to next 3 bytes, which contents we do not need. - size_t BufSize=MAX_SIZE+3; + // from access to next 4 bytes, which contents we do not need. + size_t BufSize=MAX_SIZE+4; InBuf=new byte[BufSize]; // Ensure that we get predictable results when accessing bytes in area diff --git a/libclamunrar/getbits.hpp b/libclamunrar/getbits.hpp index 2e151da9a9..00acbea945 100644 --- a/libclamunrar/getbits.hpp +++ b/libclamunrar/getbits.hpp @@ -28,26 +28,38 @@ class BitInput InAddr+=Bits>>3; InBit=Bits&7; } - + // Return 16 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. uint getbits() { +#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) + uint32 BitField=*(uint32*)(InBuf+InAddr); + BitField=ByteSwap32(BitField); + BitField >>= (16-InBit); +#else uint BitField=(uint)InBuf[InAddr] << 16; BitField|=(uint)InBuf[InAddr+1] << 8; BitField|=(uint)InBuf[InAddr+2]; BitField >>= (8-InBit); +#endif return BitField & 0xffff; } + // Return 32 bits from current position in the buffer. // Bit at (InAddr,InBit) has the highest position in returning data. uint getbits32() { +#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) + uint32 BitField=*(uint32*)(InBuf+InAddr); + BitField=ByteSwap32(BitField); +#else uint BitField=(uint)InBuf[InAddr] << 24; BitField|=(uint)InBuf[InAddr+1] << 16; BitField|=(uint)InBuf[InAddr+2] << 8; BitField|=(uint)InBuf[InAddr+3]; +#endif BitField <<= InBit; BitField|=(uint)InBuf[InAddr+4] >> (8-InBit); return BitField & 0xffffffff; diff --git a/libclamunrar/hardlinks.cpp b/libclamunrar/hardlinks.cpp index 40cc0aa495..171b5fa087 100644 --- a/libclamunrar/hardlinks.cpp +++ b/libclamunrar/hardlinks.cpp @@ -1,7 +1,5 @@ bool ExtractHardlink(CommandData *Cmd,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) { - SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. - if (!FileExist(NameExisting)) { uiMsg(UIERROR_HLINKCREATE,NameNew); diff --git a/libclamunrar/hash.cpp b/libclamunrar/hash.cpp index a4559e05c3..106cc60833 100644 --- a/libclamunrar/hash.cpp +++ b/libclamunrar/hash.cpp @@ -26,7 +26,7 @@ void HashValue::Init(HASH_TYPE Type) } -bool HashValue::operator == (const HashValue &cmp) +bool HashValue::operator == (const HashValue &cmp) const { if (Type==HASH_NONE || cmp.Type==HASH_NONE) return true; diff --git a/libclamunrar/hash.hpp b/libclamunrar/hash.hpp index b7d879f66a..6315680e78 100644 --- a/libclamunrar/hash.hpp +++ b/libclamunrar/hash.hpp @@ -6,8 +6,14 @@ enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2}; struct HashValue { void Init(HASH_TYPE Type); - bool operator == (const HashValue &cmp); - bool operator != (const HashValue &cmp) {return !(*this==cmp);} + + // Use the const member, so types on both sides of "==" match. + // Otherwise clang -std=c++20 issues "ambiguity is between a regular call + // to this operator and a call with the argument order reversed" warning. + bool operator == (const HashValue &cmp) const; + + // Not actually used now. Const member for same reason as operator == above. + bool operator != (const HashValue &cmp) const {return !(*this==cmp);} HASH_TYPE Type; union diff --git a/libclamunrar/headers.cpp b/libclamunrar/headers.cpp index b042dc3932..b441376c56 100644 --- a/libclamunrar/headers.cpp +++ b/libclamunrar/headers.cpp @@ -49,13 +49,5 @@ FileHeader& FileHeader::operator = (FileHeader &hd) void MainHeader::Reset() { - HighPosAV=0; - PosAV=0; - CommentInHeader=false; - PackComment=false; - Locator=false; - QOpenOffset=0; - QOpenMaxSize=0; - RROffset=0; - RRMaxSize=0; + *this={}; } diff --git a/libclamunrar/headers.hpp b/libclamunrar/headers.hpp index 6af453a9d1..5984f99629 100644 --- a/libclamunrar/headers.hpp +++ b/libclamunrar/headers.hpp @@ -6,12 +6,11 @@ #define SIZEOF_MAINHEAD3 13 // Size of RAR 4.x main archive header. #define SIZEOF_FILEHEAD14 21 // Size of RAR 1.4 file header. #define SIZEOF_FILEHEAD3 32 // Size of RAR 3.0 file header. -#define SIZEOF_SHORTBLOCKHEAD 7 +#define SIZEOF_SHORTBLOCKHEAD 7 // Smallest RAR 4.x block size. #define SIZEOF_LONGBLOCKHEAD 11 #define SIZEOF_SUBBLOCKHEAD 14 #define SIZEOF_COMMHEAD 13 #define SIZEOF_PROTECTHEAD 26 -#define SIZEOF_UOHEAD 18 #define SIZEOF_STREAMHEAD 26 #define VER_PACK 29U @@ -162,12 +161,16 @@ struct MainHeader:BaseBlock ushort HighPosAV; uint PosAV; bool CommentInHeader; - bool PackComment; // For RAR 1.4 archive format only. + bool PackComment; // For RAR 1.4 archive format only. bool Locator; - uint64 QOpenOffset; // Offset of quick list record. - uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field. - uint64 RROffset; // Offset of recovery record. - uint64 RRMaxSize; // Maximum size of RR offset in locator extra field. + uint64 QOpenOffset; // Offset of quick list record. + uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field. + uint64 RROffset; // Offset of recovery record. + uint64 RRMaxSize; // Maximum size of RR offset in locator extra field. + size_t MetaNameMaxSize; // Maximum size of archive name in metadata extra field. + std::wstring OrigName; // Original archive name. + RarTime OrigTime; // Original archive time. + void Reset(); }; @@ -230,7 +233,7 @@ struct FileHeader:BlockHeader bool LargeFile; // 'true' for HEAD_SERVICE block, which is a child of preceding file block. - // RAR 4.x uses 'solid' flag to indicate child subheader blocks in archives. + // RAR 4.x uses 'solid' flag to indicate children subheader blocks in archives. bool SubBlock; HOST_SYSTEM_TYPE HSType; @@ -321,16 +324,6 @@ struct ProtectHeader:BlockHeader }; -struct UnixOwnersHeader:SubBlockHeader -{ - ushort OwnerNameSize; - ushort GroupNameSize; -/* dummy */ - char OwnerName[256]; - char GroupName[256]; -}; - - struct EAHeader:SubBlockHeader { uint UnpSize; diff --git a/libclamunrar/headers5.hpp b/libclamunrar/headers5.hpp index 9ea8d979ae..50f5955d1d 100644 --- a/libclamunrar/headers5.hpp +++ b/libclamunrar/headers5.hpp @@ -59,11 +59,18 @@ // Main header extra field values. #define MHEXTRA_LOCATOR 0x01 // Position of quick list and other blocks. +#define MHEXTRA_METADATA 0x02 // Archive metadata. // Flags for MHEXTRA_LOCATOR. #define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present. #define MHEXTRA_LOCATOR_RR 0x02 // Recovery record offset is present. +// Flags for MHEXTRA_METADATA. +#define MHEXTRA_METADATA_NAME 0x01 // Archive name is present. +#define MHEXTRA_METADATA_CTIME 0x02 // Archive creation time is present. +#define MHEXTRA_METADATA_UNIXTIME 0x04 // Use Unix nanosecond time format. +#define MHEXTRA_METADATA_UNIX_NS 0x08 // Unix format with nanosecond precision. + // File and service header extra field values. #define FHEXTRA_CRYPT 0x01 // Encryption parameters. #define FHEXTRA_HASH 0x02 // File hash. diff --git a/libclamunrar/isnt.cpp b/libclamunrar/isnt.cpp index f85472c350..3cc876b990 100644 --- a/libclamunrar/isnt.cpp +++ b/libclamunrar/isnt.cpp @@ -40,7 +40,7 @@ static bool WMI_IsWindows10() IWbemServices *pSvc = NULL; - hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,0,NULL,0,0,&pSvc); + hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,NULL,NULL,0,0,&pSvc); if (FAILED(hres)) { diff --git a/libclamunrar/list.cpp b/libclamunrar/list.cpp index e5052c0177..e4444e13ab 100644 --- a/libclamunrar/list.cpp +++ b/libclamunrar/list.cpp @@ -36,6 +36,7 @@ void ListArchive(CommandData *Cmd) { Arc.ViewComment(); mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName); + mprintf(L"\n%s: ",St(MListDetails)); uint SetCount=0; const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 4":L"RAR 5"); @@ -61,6 +62,16 @@ void ListArchive(CommandData *Cmd) mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListLock)); if (Arc.Encrypted) mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListEncHead)); + + if (!Arc.MainHead.OrigName.empty()) + mprintf(L"\n%s: %s",St(MOrigName),Arc.MainHead.OrigName.c_str()); + if (Arc.MainHead.OrigTime.IsSet()) + { + wchar DateStr[50]; + Arc.MainHead.OrigTime.GetText(DateStr,ASIZE(DateStr),Technical); + mprintf(L"\n%s: %s",St(MOriginalTime),DateStr); + } + mprintf(L"\n"); } diff --git a/libclamunrar/loclang.hpp b/libclamunrar/loclang.hpp index ad7d0c9249..1e23ece76b 100644 --- a/libclamunrar/loclang.hpp +++ b/libclamunrar/loclang.hpp @@ -28,6 +28,7 @@ #define MRARTitle1 L"\nUsage: rar - - " #define MUNRARTitle1 L"\nUsage: unrar - - " #define MRARTitle2 L"\n <@listfiles...> " +#define MFwrSlTitle2 L"\n <@listfiles...> " #define MCHelpCmd L"\n\n" #define MCHelpCmdA L"\n a Add files to archive" #define MCHelpCmdC L"\n c Add archive comment" @@ -44,7 +45,7 @@ #define MCHelpCmdR L"\n r Repair archive" #define MCHelpCmdRC L"\n rc Reconstruct missing volumes" #define MCHelpCmdRN L"\n rn Rename archived files" -#define MCHelpCmdRR L"\n rr[N] Add data recovery record" +#define MCHelpCmdRR L"\n rr[N] Add the data recovery record" #define MCHelpCmdRV L"\n rv[N] Create recovery volumes" #define MCHelpCmdS L"\n s[name|-] Convert archive to or from SFX" #define MCHelpCmdT L"\n t Test archive files" @@ -58,6 +59,7 @@ #define MCHelpSwAD L"\n ad[1,2] Alternate destination path" #define MCHelpSwAG L"\n ag[format] Generate archive name using the current date" #define MCHelpSwAI L"\n ai Ignore file attributes" +#define MCHelpSwAM L"\n am[s,r] Archive name and time [save, restore]" #define MCHelpSwAO L"\n ao Add files with Archive attribute set" #define MCHelpSwAP L"\n ap Set path inside archive" #define MCHelpSwAS L"\n as Synchronize archive contents" @@ -394,3 +396,6 @@ #define MAdjustValue L"\nAdjusting %s value to %s." #define MOpFailed L"\nOperation failed" #define MSkipEncArc L"\nSkipping the encrypted archive %s" +#define MOrigName L"Original name" +#define MOriginalTime L"Original time" +#define MFileRenamed L"\n%s is renamed to %s" diff --git a/libclamunrar/makefile b/libclamunrar/makefile index ace156c7c6..55af49befe 100644 --- a/libclamunrar/makefile +++ b/libclamunrar/makefile @@ -126,7 +126,7 @@ OBJECTS=rar.o strlist.o strfn.o pathfn.o smallfn.o global.o file.o filefn.o filc archive.o arcread.o unicode.o system.o crypt.o crc.o rawread.o encname.o \ resource.o match.o timefn.o rdwrfn.o consio.o options.o errhnd.o rarvm.o secpassword.o \ rijndael.o getbits.o sha1.o sha256.o blake2s.o hash.o extinfo.o extract.o volume.o \ - list.o find.o unpack.o headers.o threadpool.o rs16.o cmddata.o ui.o + list.o find.o unpack.o headers.o threadpool.o rs16.o cmddata.o ui.o .cpp.o: $(COMPILE) -D$(WHAT) -c $< @@ -142,20 +142,23 @@ clean: @rm -f $(OBJECTS) $(UNRAR_OBJ) $(LIB_OBJ) @rm -f unrar libunrar.* -unrar: clean $(OBJECTS) $(UNRAR_OBJ) +# We removed 'clean' from dependencies, because it prevented parallel +# 'make -Jn' builds. + +unrar: $(OBJECTS) $(UNRAR_OBJ) @rm -f unrar $(LINK) -o unrar $(LDFLAGS) $(OBJECTS) $(UNRAR_OBJ) $(LIBS) $(STRIP) unrar sfx: WHAT=SFX_MODULE -sfx: clean $(OBJECTS) +sfx: $(OBJECTS) @rm -f default.sfx $(LINK) -o default.sfx $(LDFLAGS) $(OBJECTS) $(STRIP) default.sfx lib: WHAT=RARDLL lib: CXXFLAGS+=$(LIBFLAGS) -lib: clean $(OBJECTS) $(LIB_OBJ) +lib: $(OBJECTS) $(LIB_OBJ) @rm -f libunrar.* $(LINK) -shared -o libunrar.so $(LDFLAGS) $(OBJECTS) $(LIB_OBJ) $(AR) rcs libunrar.a $(OBJECTS) $(LIB_OBJ) diff --git a/libclamunrar/model.cpp b/libclamunrar/model.cpp index 83391c5a45..e4f9e3c537 100644 --- a/libclamunrar/model.cpp +++ b/libclamunrar/model.cpp @@ -532,13 +532,15 @@ inline bool RARPPM_CONTEXT::decodeSymbol2(ModelPPM *Model) Model->Coder.SubRange.LowCount=HiCnt; Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale; i=NumStats-Model->NumMasked; - pps--; + + // 2022.12.02: we removed pps-- here and changed the code below to avoid + // "array subscript -1 is outside array bounds" warning in some compilers. do { - pps++; if (pps>=ps+ASIZE(ps)) // Extra safety check. return false; Model->CharMask[(*pps)->Symbol]=Model->EscCount; + pps++; } while ( --i ); psee2c->Summ += Model->Coder.SubRange.scale; Model->NumMasked = NumStats; diff --git a/libclamunrar/options.cpp b/libclamunrar/options.cpp index 40323be828..22ae27ce21 100644 --- a/libclamunrar/options.cpp +++ b/libclamunrar/options.cpp @@ -6,14 +6,6 @@ RAROptions::RAROptions() } -RAROptions::~RAROptions() -{ - // It is important for security reasons, so we do not have the unnecessary - // password data left in memory. - memset(this,0,sizeof(RAROptions)); -} - - void RAROptions::Init() { memset(this,0,sizeof(RAROptions)); diff --git a/libclamunrar/options.hpp b/libclamunrar/options.hpp index 113195828b..e249eb599d 100644 --- a/libclamunrar/options.hpp +++ b/libclamunrar/options.hpp @@ -45,6 +45,12 @@ enum OVERWRITE_MODE OVERWRITE_FORCE_ASK }; +enum ARC_METADATA +{ + ARCMETA_NONE=0, + ARCMETA_SAVE, // -ams + ARCMETA_RESTORE // -amr +}; enum QOPEN_MODE { QOPEN_NONE, QOPEN_AUTO, QOPEN_ALWAYS }; @@ -84,11 +90,12 @@ struct FilterMode #define MAX_GENERATE_MASK 128 +// Here we store simple data types, which we can clear and move all together +// quickly. Rest of data types goes to CommandData. class RAROptions { public: RAROptions(); - ~RAROptions(); void Init(); uint ExclFileAttr; @@ -118,7 +125,6 @@ class RAROptions wchar ArcPath[NM]; // For -ap. wchar ExclArcPath[NM]; // For -ep4 switch. - SecPassword Password; bool EncryptHeaders; bool SkipEncrypted; @@ -132,6 +138,7 @@ class RAROptions HASH_TYPE HashType; int Recovery; int RecVolNumber; + ARC_METADATA ArcMetadata; bool DisablePercentage; bool DisableCopyright; bool DisableDone; @@ -147,7 +154,6 @@ class RAROptions PATH_EXCL_MODE ExclPath; RECURSE_MODE Recurse; int64 VolSize; - Array NextVolSizes; uint CurVolNum; bool AllYes; bool VerboseOutput; // -iv, display verbose output, used only in "WinRAR t" now. diff --git a/libclamunrar/os.hpp b/libclamunrar/os.hpp index 7df3c6c0da..4b21e49d76 100644 --- a/libclamunrar/os.hpp +++ b/libclamunrar/os.hpp @@ -13,6 +13,8 @@ #endif #include +#include +#include #if defined(_WIN_ALL) || defined(_EMX) @@ -39,8 +41,15 @@ #define _UNICODE // Set _T() macro to convert from narrow to wide strings. #endif +#if 0 +// 2021.09.05: Allow newer Vista+ APIs like IFileOpenDialog for WinRAR, +// but still keep SFX modules XP compatible. +#define WINVER _WIN32_WINNT_VISTA +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#else #define WINVER _WIN32_WINNT_WINXP #define _WIN32_WINNT _WIN32_WINNT_WINXP +#endif #if !defined(ZIPSFX) #define RAR_SMP @@ -72,9 +81,6 @@ #include #endif #ifdef _MSC_VER - #if _MSC_VER<1500 - #define for if (0) ; else for - #endif #include #include @@ -98,7 +104,6 @@ #include #include - #define SAVE_LINKS #define ENABLE_ACCESS diff --git a/libclamunrar/pathfn.cpp b/libclamunrar/pathfn.cpp index 983bd74379..e959e9d65b 100644 --- a/libclamunrar/pathfn.cpp +++ b/libclamunrar/pathfn.cpp @@ -31,11 +31,17 @@ wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath,size_t DestSize) const wchar *s=DestPtr; if (s[0]!=0 && IsDriveDiv(s[1])) s+=2; - if (s[0]=='\\' && s[1]=='\\') + + // Skip UNC Windows \\server\share\ or Unix //server/share/ + if (IsPathDiv(s[0]) && IsPathDiv(s[1])) { - const wchar *Slash=wcschr(s+2,'\\'); - if (Slash!=NULL && (Slash=wcschr(Slash+1,'\\'))!=NULL) - s=Slash+1; + uint SlashCount=0; + for (const wchar *t=s+2;*t!=0;t++) + if (IsPathDiv(*t) && ++SlashCount==2) + { + s=t+1; // Found two more path separators after leading two. + break; + } } for (const wchar *t=s;*t!=0;t++) if (IsPathDiv(*t)) @@ -422,50 +428,39 @@ void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering) bool IsNameUsable(const wchar *Name) { -#ifndef _UNIX - if (Name[0] && Name[1] && wcschr(Name+2,':')!=NULL) + // We were asked to apply Windows-like conversion in Linux in case + // files are unpacked to Windows share. This code is invoked only + // if file failed to be created, so it doesn't affect extraction + // of Unix compatible names to native Unix drives. +#ifdef _UNIX + // Windows shares in Unix do not allow the drive letter, + // so unlike Windows version, we check all characters here. + if (wcschr(Name,':')!=NULL) return false; +#else + if (Name[0]!=0 && Name[1]!=0 && wcschr(Name+2,':')!=NULL) + return false; +#endif for (const wchar *s=Name;*s!=0;s++) { if ((uint)*s<32) return false; + + // It is for Windows shares in Unix. We can create such names in Windows. +#ifdef _UNIX + // No spaces or dots before the path separator are allowed in Windows + // shares. But they are allowed and automtically removed at the end of + // file or folder name, so it is useless to replace them here. + // Since such files or folders are created successfully, a supposed + // conversion here would never be invoked. if ((*s==' ' || *s=='.') && IsPathDiv(s[1])) return false; - } #endif + } return *Name!=0 && wcspbrk(Name,L"?*<>|\"")==NULL; } -void MakeNameUsable(char *Name,bool Extended) -{ -#ifdef _WIN_ALL - // In Windows we also need to convert characters not defined in current - // code page. This double conversion changes them to '?', which is - // catched by code below. - size_t NameLength=strlen(Name); - wchar NameW[NM]; - CharToWide(Name,NameW,ASIZE(NameW)); - WideToChar(NameW,Name,NameLength+1); - Name[NameLength]=0; -#endif - for (char *s=Name;*s!=0;s=charnext(s)) - { - if (strchr(Extended ? "?*<>|\"":"?*",*s)!=NULL || Extended && (byte)*s<32) - *s='_'; -#ifdef _EMX - if (*s=='=') - *s='_'; -#endif -#ifndef _UNIX - if (s-Name>1 && *s==':') - *s='_'; - // Remove ' ' and '.' before path separator, but allow .\ and ..\. - if ((*s==' ' || *s=='.' && s>Name && !IsPathDiv(s[-1]) && s[-1]!='.') && IsPathDiv(s[1])) - *s='_'; -#endif - } -} void MakeNameUsable(wchar *Name,bool Extended) @@ -474,7 +469,27 @@ void MakeNameUsable(wchar *Name,bool Extended) { if (wcschr(Extended ? L"?*<>|\"":L"?*",*s)!=NULL || Extended && (uint)*s<32) *s='_'; -#ifndef _UNIX +#ifdef _UNIX + // We were asked to apply Windows-like conversion in Linux in case + // files are unpacked to Windows share. This code is invoked only + // if file failed to be created, so it doesn't affect extraction + // of Unix compatible names to native Unix drives. + if (Extended) + { + // Windows shares in Unix do not allow the drive letter, + // so unlike Windows version, we check all characters here. + if (*s==':') + *s='_'; + + // No spaces or dots before the path separator are allowed on Windows + // shares. But they are allowed and automatically removed at the end of + // file or folder name, so it is useless to replace them here. + // Since such files or folders are created successfully, a supposed + // conversion here would never be invoked. + if ((*s==' ' || *s=='.') && IsPathDiv(s[1])) + *s='_'; + } +#else if (s-Name>1 && *s==':') *s='_'; #if 0 // We already can create such files. @@ -731,7 +746,7 @@ static void GenArcName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,u // Here we ensure that we have enough 'N' characters to fit all digits // of archive number. We'll replace them by actual number later // in this function. - if (NCount=4) CurWeek++; - char Field[10][6]; + char Field[10][11]; sprintf(Field[0],"%04u",rlt.Year); sprintf(Field[1],"%02u",rlt.Month); diff --git a/libclamunrar/pathfn.hpp b/libclamunrar/pathfn.hpp index efb283301d..62cae0ad2c 100644 --- a/libclamunrar/pathfn.hpp +++ b/libclamunrar/pathfn.hpp @@ -29,7 +29,6 @@ void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckEx wchar* GetVolNumPart(const wchar *ArcName); void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering); bool IsNameUsable(const wchar *Name); -void MakeNameUsable(char *Name,bool Extended); void MakeNameUsable(wchar *Name,bool Extended); void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength); diff --git a/libclamunrar/qopen.cpp b/libclamunrar/qopen.cpp index 43346b061a..d906d06bd3 100644 --- a/libclamunrar/qopen.cpp +++ b/libclamunrar/qopen.cpp @@ -97,7 +97,7 @@ void QuickOpen::Load(uint64 BlockPos) if (Arc->SubHead.Encrypted) { - RAROptions *Cmd=Arc->GetRAROptions(); + CommandData *Cmd=Arc->GetCommandData(); #ifndef RAR_NOCRYPT if (Cmd->Password.IsSet()) Crypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,Arc->SubHead.Salt, diff --git a/libclamunrar/rar.hpp b/libclamunrar/rar.hpp index 20e32fb238..67edb6735d 100644 --- a/libclamunrar/rar.hpp +++ b/libclamunrar/rar.hpp @@ -12,6 +12,7 @@ #include "version.hpp" #include "rardefs.hpp" #include "rarlang.hpp" +#include "rawint.hpp" #include "unicode.hpp" #include "errhnd.hpp" #include "secpassword.hpp" @@ -34,7 +35,6 @@ #endif #include "file.hpp" #include "crc.hpp" -#include "ui.hpp" #include "filefn.hpp" #include "filestr.hpp" #include "find.hpp" @@ -47,11 +47,11 @@ #include "archive.hpp" #include "match.hpp" #include "cmddata.hpp" +#include "ui.hpp" #include "filcreat.hpp" #include "consio.hpp" #include "system.hpp" #include "log.hpp" -#include "rawint.hpp" #include "rawread.hpp" #include "encname.hpp" #include "resource.hpp" diff --git a/libclamunrar/rardefs.hpp b/libclamunrar/rardefs.hpp index 095792a036..6858d39c13 100644 --- a/libclamunrar/rardefs.hpp +++ b/libclamunrar/rardefs.hpp @@ -9,9 +9,13 @@ #define ASIZE(x) (sizeof(x)/sizeof(x[0])) -// MAXPASSWORD is expected to be multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE (16) -// for CryptProtectMemory in SecPassword. -#define MAXPASSWORD 128 +// MAXPASSWORD and MAXPASSWORD_RAR are expected to be multiple of +// CRYPTPROTECTMEMORY_BLOCK_SIZE (16) for CryptProtectMemory in SecPassword. +// We allow a larger MAXPASSWORD to unpack archives with lengthy passwords +// in non-RAR formats in GUI versions. For RAR format we set MAXPASSWORD_RAR +// to 128 for compatibility and because it is enough for AES-256. +#define MAXPASSWORD 512 +#define MAXPASSWORD_RAR 128 #define MAXSFXSIZE 0x200000 diff --git a/libclamunrar/rawint.hpp b/libclamunrar/rawint.hpp index 303798886b..c8cd86fc7a 100644 --- a/libclamunrar/rawint.hpp +++ b/libclamunrar/rawint.hpp @@ -84,7 +84,7 @@ inline uint32 RawGetBE4(const byte *m) { #if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) return _byteswap_ulong(*(uint32 *)m); -#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) return __builtin_bswap32(*(uint32 *)m); #else return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; @@ -97,7 +97,7 @@ inline void RawPutBE4(uint32 i,byte *mem) { #if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) *(uint32*)mem = _byteswap_ulong(i); -#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +#elif defined(USE_MEM_BYTESWAP) && (defined(__clang__) || defined(__GNUC__)) *(uint32*)mem = __builtin_bswap32(i); #else mem[0]=byte(i>>24); @@ -112,7 +112,7 @@ inline uint32 ByteSwap32(uint32 i) { #ifdef _MSC_VER return _byteswap_ulong(i); -#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +#elif defined(__clang__) || defined(__GNUC__) return __builtin_bswap32(i); #else return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); diff --git a/libclamunrar/rdwrfn.cpp b/libclamunrar/rdwrfn.cpp index 5e13ae6547..fa71376018 100644 --- a/libclamunrar/rdwrfn.cpp +++ b/libclamunrar/rdwrfn.cpp @@ -155,7 +155,7 @@ void ComprDataIO::UnpWrite(byte *Addr,size_t Count) { #ifdef RARDLL - RAROptions *Cmd=((Archive *)SrcFile)->GetRAROptions(); + CommandData *Cmd=((Archive *)SrcFile)->GetCommandData(); if (Cmd->DllOpMode!=RAR_SKIP) { if (Cmd->Callback!=NULL && @@ -204,7 +204,7 @@ void ComprDataIO::ShowUnpRead(int64 ArcPos,int64 ArcSize) ArcPos+=ProcessedArcSize; Archive *SrcArc=(Archive *)SrcFile; - RAROptions *Cmd=SrcArc->GetRAROptions(); + CommandData *Cmd=SrcArc->GetCommandData(); int CurPercent=ToPercent(ArcPos,ArcSize); if (!Cmd->DisablePercentage && CurPercent!=LastPercent) diff --git a/libclamunrar/recvol.cpp b/libclamunrar/recvol.cpp index adf5840443..b178207119 100644 --- a/libclamunrar/recvol.cpp +++ b/libclamunrar/recvol.cpp @@ -5,7 +5,7 @@ -bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent) +bool RecVolumesRestore(CommandData *Cmd,const wchar *Name,bool Silent) { Archive Arc(Cmd); if (!Arc.Open(Name)) @@ -42,7 +42,7 @@ bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent) } -void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name) +void RecVolumesTest(CommandData *Cmd,Archive *Arc,const wchar *Name) { wchar RevName[NM]; *RevName=0; diff --git a/libclamunrar/recvol.hpp b/libclamunrar/recvol.hpp index 06510a211b..4a6d663ffe 100644 --- a/libclamunrar/recvol.hpp +++ b/libclamunrar/recvol.hpp @@ -14,11 +14,11 @@ class RecVolumes3 ThreadPool *RSThreadPool; #endif public: - RecVolumes3(RAROptions *Cmd,bool TestOnly); + RecVolumes3(CommandData *Cmd,bool TestOnly); ~RecVolumes3(); - void Make(RAROptions *Cmd,wchar *ArcName); - bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent); - void Test(RAROptions *Cmd,const wchar *Name); + void Make(CommandData *Cmd,wchar *ArcName); + bool Restore(CommandData *Cmd,const wchar *Name,bool Silent); + void Test(CommandData *Cmd,const wchar *Name); }; @@ -48,8 +48,8 @@ struct RecRSThreadData class RecVolumes5 { private: - void ProcessRS(RAROptions *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode); - void ProcessRS(RAROptions *Cmd,uint MaxRead,bool Encode); + void ProcessRS(CommandData *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode); + void ProcessRS(CommandData *Cmd,uint MaxRead,bool Encode); uint ReadHeader(File *RecFile,bool FirstRev); Array RecItems; @@ -76,13 +76,13 @@ class RecVolumes5 public: // 'public' only because called from thread functions. void ProcessAreaRS(RecRSThreadData *td); public: - RecVolumes5(RAROptions *Cmd,bool TestOnly); + RecVolumes5(CommandData *Cmd,bool TestOnly); ~RecVolumes5(); - bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent); - void Test(RAROptions *Cmd,const wchar *Name); + bool Restore(CommandData *Cmd,const wchar *Name,bool Silent); + void Test(CommandData *Cmd,const wchar *Name); }; -bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent); -void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name); +bool RecVolumesRestore(CommandData *Cmd,const wchar *Name,bool Silent); +void RecVolumesTest(CommandData *Cmd,Archive *Arc,const wchar *Name); #endif diff --git a/libclamunrar/recvol3.cpp b/libclamunrar/recvol3.cpp index 9fb846a28a..0138d0f3a9 100644 --- a/libclamunrar/recvol3.cpp +++ b/libclamunrar/recvol3.cpp @@ -36,7 +36,7 @@ THREAD_PROC(RSDecodeThread) } #endif -RecVolumes3::RecVolumes3(RAROptions *Cmd,bool TestOnly) +RecVolumes3::RecVolumes3(CommandData *Cmd,bool TestOnly) { memset(SrcFile,0,sizeof(SrcFile)); if (TestOnly) @@ -99,7 +99,7 @@ static bool IsNewStyleRev(const wchar *Name) } -bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) +bool RecVolumes3::Restore(CommandData *Cmd,const wchar *Name,bool Silent) { wchar ArcName[NM]; wcsncpyz(ArcName,Name,ASIZE(ArcName)); @@ -226,7 +226,7 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) if (WrongParam) continue; } - if (P[1]+P[2]>255) + if (P[0]<=0 || P[1]<=0 || P[2]<=0 || P[1]+P[2]>255 || P[0]+P[2]-1>255) continue; if (RecVolNumber!=0 && RecVolNumber!=P[1] || FileNumber!=0 && FileNumber!=P[2]) { @@ -238,7 +238,14 @@ bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) wcsncpyz(PrevName,CurName,ASIZE(PrevName)); File *NewFile=new File; NewFile->TOpen(CurName); - SrcFile[FileNumber+P[0]-1]=NewFile; + + // This check is redundant taking into account P[I]>255 and P[0]+P[2]-1>255 + // checks above. Still we keep it here for better clarity and security. + int SrcPos=FileNumber+P[0]-1; + if (SrcPos<0 || SrcPos>=ASIZE(SrcFile)) + continue; + SrcFile[SrcPos]=NewFile; + FoundRecVolumes++; if (RecFileSize==0) @@ -497,7 +504,7 @@ void RSEncode::DecodeBuf() } -void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name) +void RecVolumes3::Test(CommandData *Cmd,const wchar *Name) { if (!IsNewStyleRev(Name)) // RAR 3.0 name#_#_#.rev do not include CRC32. { diff --git a/libclamunrar/recvol5.cpp b/libclamunrar/recvol5.cpp index 596ea06f10..2d9c947110 100644 --- a/libclamunrar/recvol5.cpp +++ b/libclamunrar/recvol5.cpp @@ -4,7 +4,7 @@ static const uint MaxVolumes=65535; // rev files by mistake. #define MAX_REV_TO_DATA_RATIO 10 // 1000% of rev files. -RecVolumes5::RecVolumes5(RAROptions *Cmd,bool TestOnly) +RecVolumes5::RecVolumes5(CommandData *Cmd,bool TestOnly) { RealBuf=NULL; RealReadBuffer=NULL; @@ -70,7 +70,7 @@ THREAD_PROC(RecThreadRS) #endif -void RecVolumes5::ProcessRS(RAROptions *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode) +void RecVolumes5::ProcessRS(CommandData *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode) { /* RSCoder16 RS; @@ -141,7 +141,7 @@ void RecVolumes5::ProcessAreaRS(RecRSThreadData *td) -bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent) +bool RecVolumes5::Restore(CommandData *Cmd,const wchar *Name,bool Silent) { wchar ArcName[NM]; wcsncpyz(ArcName,Name,ASIZE(ArcName)); @@ -494,7 +494,7 @@ uint RecVolumes5::ReadHeader(File *RecFile,bool FirstRev) } -void RecVolumes5::Test(RAROptions *Cmd,const wchar *Name) +void RecVolumes5::Test(CommandData *Cmd,const wchar *Name) { wchar VolName[NM]; wcsncpyz(VolName,Name,ASIZE(VolName)); diff --git a/libclamunrar/rijndael.cpp b/libclamunrar/rijndael.cpp index 48ac1723bc..02c4d147b2 100644 --- a/libclamunrar/rijndael.cpp +++ b/libclamunrar/rijndael.cpp @@ -90,18 +90,20 @@ Rijndael::Rijndael() void Rijndael::Init(bool Encrypt,const byte *key,uint keyLen,const byte * initVector) { -#ifdef USE_SSE - // Check SSE here instead of constructor, so if object is a part of some - // structure memset'ed before use, this variable is not lost. + // Check SIMD here instead of constructor, so if object is a part of some + // structure memset'ed before use, these variables are not lost. +#if defined(USE_SSE) int CPUInfo[4]; - __cpuid(CPUInfo, 0x80000000); // Get the maximum supported cpuid function. - if ((CPUInfo[0] & 0x7fffffff)>=1) + __cpuid(CPUInfo, 0); + if (CPUInfo[0]>=1) // Check the maximum supported cpuid function. { __cpuid(CPUInfo, 1); AES_NI=(CPUInfo[2] & 0x2000000)!=0; } else AES_NI=false; +#elif defined(USE_NEON) + AES_Neon=(getauxval(AT_HWCAP) & HWCAP_AES)!=0; #endif // Other developers asked us to initialize it to suppress "may be used @@ -141,18 +143,25 @@ void Rijndael::Init(bool Encrypt,const byte *key,uint keyLen,const byte * initVe keyEncToDec(); } + void Rijndael::blockEncrypt(const byte *input,size_t inputLen,byte *outBuffer) { if (inputLen <= 0) return; size_t numBlocks = inputLen/16; -#ifdef USE_SSE +#if defined(USE_SSE) if (AES_NI) { blockEncryptSSE(input,numBlocks,outBuffer); return; } +#elif defined(USE_NEON) + if (AES_Neon) + { + blockEncryptNeon(input,numBlocks,outBuffer); + return; + } #endif byte *prevBlock = m_initVector; @@ -239,6 +248,40 @@ void Rijndael::blockEncryptSSE(const byte *input,size_t numBlocks,byte *outBuffe } #endif + +#ifdef USE_NEON +void Rijndael::blockEncryptNeon(const byte *input,size_t numBlocks,byte *outBuffer) +{ + byte *prevBlock = m_initVector; + while (numBlocks > 0) + { + byte block[16]; + if (CBCMode) + vst1q_u8(block, veorq_u8(vld1q_u8(prevBlock), vld1q_u8(input))); + else + vst1q_u8(block, vld1q_u8(input)); + + uint8x16_t data = vld1q_u8(block); + for (uint i = 0; i < m_uRounds-1; i++) + { + data = vaeseq_u8(data, vld1q_u8((byte *)m_expandedKey[i])); + data = vaesmcq_u8(data); + } + data = vaeseq_u8(data, vld1q_u8((byte *)(m_expandedKey[m_uRounds-1]))); + data = veorq_u8(data, vld1q_u8((byte *)(m_expandedKey[m_uRounds]))); + vst1q_u8(outBuffer, data); + + prevBlock=outBuffer; + + outBuffer += 16; + input += 16; + numBlocks--; + } + vst1q_u8(m_initVector, vld1q_u8(prevBlock)); + return; +} +#endif + void Rijndael::blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer) { @@ -246,12 +289,18 @@ void Rijndael::blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer) return; size_t numBlocks=inputLen/16; -#ifdef USE_SSE +#if defined(USE_SSE) if (AES_NI) { blockDecryptSSE(input,numBlocks,outBuffer); return; } +#elif defined(USE_NEON) + if (AES_Neon) + { + blockDecryptNeon(input,numBlocks,outBuffer); + return; + } #endif byte block[16], iv[4][4]; @@ -343,6 +392,41 @@ void Rijndael::blockDecryptSSE(const byte *input, size_t numBlocks, byte *outBuf #endif +#ifdef USE_NEON +void Rijndael::blockDecryptNeon(const byte *input, size_t numBlocks, byte *outBuffer) +{ + byte iv[16]; + memcpy(iv,m_initVector,16); + + while (numBlocks > 0) + { + uint8x16_t data = vld1q_u8(input); + + for (int i=m_uRounds-1; i>0; i--) + { + data = vaesdq_u8(data, vld1q_u8((byte *)m_expandedKey[i+1])); + data = vaesimcq_u8(data); + } + + data = vaesdq_u8(data, vld1q_u8((byte *)m_expandedKey[1])); + data = veorq_u8(data, vld1q_u8((byte *)m_expandedKey[0])); + + if (CBCMode) + data = veorq_u8(data, vld1q_u8(iv)); + + vst1q_u8(iv, vld1q_u8(input)); + vst1q_u8(outBuffer, data); + + input += 16; + outBuffer += 16; + numBlocks--; + } + + memcpy(m_initVector,iv,16); +} +#endif + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ALGORITHM ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/libclamunrar/rijndael.hpp b/libclamunrar/rijndael.hpp index 797899ffb7..96e1d0d14c 100644 --- a/libclamunrar/rijndael.hpp +++ b/libclamunrar/rijndael.hpp @@ -18,6 +18,16 @@ class Rijndael bool AES_NI; #endif +#ifdef USE_NEON + // Set "crypto" attribute as replacement of -march=armv8-a+crypto switch. + __attribute__((target("crypto"))) + void blockEncryptNeon(const byte *input,size_t numBlocks,byte *outBuffer); + __attribute__((target("crypto"))) + void blockDecryptNeon(const byte *input, size_t numBlocks, byte *outBuffer); + + bool AES_Neon; +#endif + void keySched(byte key[_MAX_KEY_COLUMNS][4]); void keyEncToDec(); void GenerateTables(); diff --git a/libclamunrar/scantree.cpp b/libclamunrar/scantree.cpp index a13a3ebcef..dbaf1e4273 100644 --- a/libclamunrar/scantree.cpp +++ b/libclamunrar/scantree.cpp @@ -215,10 +215,21 @@ bool ScanTree::GetNextMask() UnixSlashToDos(CurMask,CurMask,ASIZE(CurMask)); #endif - // We wish to scan entire disk if mask like c:\ is specified - // regardless of recursion mode. Use c:\*.* mask when need to scan only - // the root directory. - ScanEntireDisk=IsDriveLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0; + // We prefer to scan entire disk if mask like \\server\share\ or c:\ + // is specified regardless of recursion mode. Use \\server\share\*.* + // or c:\*.* mask to scan only the root directory. + if (CurMask[0]=='\\' && CurMask[1]=='\\') + { + const wchar *Slash=wcschr(CurMask+2,'\\'); + if (Slash!=NULL) + { + Slash=wcschr(Slash+1,'\\'); + ScanEntireDisk=Slash!=NULL && *(Slash+1)==0; + } + } + else + ScanEntireDisk=IsDriveLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0; + wchar *Name=PointToName(CurMask); if (*Name==0) diff --git a/libclamunrar/secpassword.cpp b/libclamunrar/secpassword.cpp index b99e53af01..08da549721 100644 --- a/libclamunrar/secpassword.cpp +++ b/libclamunrar/secpassword.cpp @@ -56,7 +56,6 @@ static CryptLoader GlobalCryptLoader; SecPassword::SecPassword() { - CrossProcess=false; Set(L""); } @@ -70,7 +69,8 @@ SecPassword::~SecPassword() void SecPassword::Clean() { PasswordSet=false; - cleandata(Password,sizeof(Password)); + if (Password.size()>0) + cleandata(&Password[0],Password.size()*sizeof(Password[0])); } @@ -104,7 +104,7 @@ void SecPassword::Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstS // Source string can be shorter than destination as in case when we process // -p parameter, so we need to take into account both sizes. memcpy(Dst,Src,Min(SrcSize,DstSize)*sizeof(*Dst)); - SecHideData(Dst,DstSize*sizeof(*Dst),Encode,CrossProcess); + SecHideData(Dst,DstSize*sizeof(*Dst),Encode,false); } @@ -112,7 +112,7 @@ void SecPassword::Get(wchar *Psw,size_t MaxSize) { if (PasswordSet) { - Process(Password,ASIZE(Password),Psw,MaxSize,false); + Process(&Password[0],Password.size(),Psw,MaxSize,false); Psw[MaxSize-1]=0; } else @@ -124,15 +124,14 @@ void SecPassword::Get(wchar *Psw,size_t MaxSize) void SecPassword::Set(const wchar *Psw) { - if (*Psw==0) - { - PasswordSet=false; - memset(Password,0,sizeof(Password)); - } - else + // Eliminate any traces of previously stored password for security reason + // in case it was longer than new one. + Clean(); + + if (*Psw!=0) { PasswordSet=true; - Process(Psw,wcslen(Psw)+1,Password,ASIZE(Password),true); + Process(Psw,wcslen(Psw)+1,&Password[0],Password.size(),true); } } @@ -142,7 +141,7 @@ size_t SecPassword::Length() wchar Plain[MAXPASSWORD]; Get(Plain,ASIZE(Plain)); size_t Length=wcslen(Plain); - cleandata(Plain,ASIZE(Plain)); + cleandata(Plain,sizeof(Plain)); return Length; } @@ -157,12 +156,15 @@ bool SecPassword::operator == (SecPassword &psw) Get(Plain1,ASIZE(Plain1)); psw.Get(Plain2,ASIZE(Plain2)); bool Result=wcscmp(Plain1,Plain2)==0; - cleandata(Plain1,ASIZE(Plain1)); - cleandata(Plain2,ASIZE(Plain2)); + cleandata(Plain1,sizeof(Plain1)); + cleandata(Plain2,sizeof(Plain2)); return Result; } +// Set CrossProcess to true if we need to pass a password to another process. +// We use CrossProcess when transferring parameters to UAC elevated WinRAR +// and Windows GUI SFX modules. void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess) { // CryptProtectMemory is not available in UWP and CryptProtectData diff --git a/libclamunrar/secpassword.hpp b/libclamunrar/secpassword.hpp index 375d3887a4..5284bce124 100644 --- a/libclamunrar/secpassword.hpp +++ b/libclamunrar/secpassword.hpp @@ -8,10 +8,7 @@ class SecPassword private: void Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode); - wchar Password[MAXPASSWORD]; - - // It is important to have this 'bool' value, so if our object is cleaned - // with memset as a part of larger structure, it is handled correctly. + std::vector Password = std::vector(MAXPASSWORD); bool PasswordSet; public: SecPassword(); @@ -22,10 +19,6 @@ class SecPassword bool IsSet() {return PasswordSet;} size_t Length(); bool operator == (SecPassword &psw); - - // Set to true if we need to pass a password to another process. - // We use it when transferring parameters to UAC elevated WinRAR. - bool CrossProcess; }; diff --git a/libclamunrar/strfn.cpp b/libclamunrar/strfn.cpp index f53c826ace..7617f7a599 100644 --- a/libclamunrar/strfn.cpp +++ b/libclamunrar/strfn.cpp @@ -357,6 +357,32 @@ void itoa(int64 n,wchar *Str,size_t MaxSize) } +// Convert the number to string using thousand separators. +void fmtitoa(int64 n,wchar *Str,size_t MaxSize) +{ + static wchar ThSep=0; // Thousands separator. +#ifdef _WIN_ALL + wchar Info[10]; + if (!ThSep!=0 && GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STHOUSAND,Info,ASIZE(Info))>0) + ThSep=*Info; +#elif defined(_UNIX) + ThSep=*localeconv()->thousands_sep; +#endif + if (ThSep==0) // If failed to detect the actual separator value. + ThSep=' '; + wchar RawText[30]; // 20 characters are enough for largest unsigned 64 bit int. + itoa(n,RawText,ASIZE(RawText)); + uint S=0,D=0,L=wcslen(RawText)%3; + while (RawText[S]!=0 && D+1=7) { diff --git a/libclamunrar/threadmisc.cpp b/libclamunrar/threadmisc.cpp index 742eda41d9..7a6ec7822e 100644 --- a/libclamunrar/threadmisc.cpp +++ b/libclamunrar/threadmisc.cpp @@ -149,3 +149,5 @@ uint GetNumberOfThreads() return NumCPU; } + + diff --git a/libclamunrar/timefn.hpp b/libclamunrar/timefn.hpp index 5271361644..49b61e85d6 100644 --- a/libclamunrar/timefn.hpp +++ b/libclamunrar/timefn.hpp @@ -22,6 +22,17 @@ class RarTime // Internal time representation in 1/TICKS_PER_SECOND since 01.01.1601. // We use nanoseconds here to handle the high precision Unix time. + // It allows dates up to July 2185. + // + // If we'll ever need to extend the date range, we can define a lower + // precision Windows version of TICKS_PER_SECOND. But then Unix and Windows + // versions can differ in least significant digits of "lt" time output + // for Unix archives. + // Alternatively we can introduce 'bool HighPrecision' set to true + // in SetUnixNS() and TicksPerSecond() instead of constant above. + // It might be more reliable than defining TicksPerSecond variable, + // which wouldn't survive memset of any structure hosting RarTime. + // We would need to eliminate all such memsets in the entire code first. uint64 itime; public: // RarLocalTime::Reminder precision. Must be equal to TICKS_PER_SECOND. diff --git a/libclamunrar/ui.hpp b/libclamunrar/ui.hpp index 8fc76aa800..5def26df18 100644 --- a/libclamunrar/ui.hpp +++ b/libclamunrar/ui.hpp @@ -49,7 +49,7 @@ enum UIMESSAGE_CODE { UIMSG_CORRECTINGNAME, UIMSG_BADARCHIVE, UIMSG_CREATING, UIMSG_RENAMING, UIMSG_RECVOLCALCCHECKSUM, UIMSG_RECVOLFOUND, UIMSG_RECVOLMISSING, UIMSG_MISSINGVOL, UIMSG_RECONSTRUCTING, UIMSG_CHECKSUM, UIMSG_FAT32SIZE, - UIMSG_SKIPENCARC, + UIMSG_SKIPENCARC, UIMSG_FILERENAME, UIWAIT_FIRST, UIWAIT_DISKFULLNEXT, UIWAIT_FCREATEERROR, UIWAIT_BADPSW, @@ -77,7 +77,7 @@ enum UIASKREP_RESULT { }; UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags); -UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags); +UIASKREP_RESULT uiAskReplaceEx(CommandData *Cmd,wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags); void uiInit(SOUND_NOTIFY_MODE Sound); @@ -88,7 +88,7 @@ void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize); enum UIPASSWORD_TYPE {UIPASSWORD_GLOBAL,UIPASSWORD_FILE,UIPASSWORD_ARCHIVE}; -bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password); +bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password,CheckPassword *CheckPwd); bool uiIsGlobalPasswordSet(); enum UIALARM_TYPE {UIALARM_ERROR, UIALARM_INFO, UIALARM_QUESTION}; @@ -145,30 +145,31 @@ class uiMsgStore // Templates recognize usual NULL as integer, not wchar*. #define UINULL ((wchar *)NULL) -inline void uiMsg(UIMESSAGE_CODE Code) +inline void uiMsgBase(uiMsgStore &Store) { - uiMsgStore Store(Code); - Store.Msg(); + // Called last, when no parameters are left. } -template void uiMsg(UIMESSAGE_CODE Code,T1 a1) +template void uiMsgBase(uiMsgStore &Store,T1&& a1,TN&&... aN) { - uiMsgStore Store(Code); + // Process first parameter and pass the rest to same uiMsgBase. Store< void uiMsg(UIMESSAGE_CODE Code,T1 a1,T2 a2) -{ - uiMsgStore Store(Code); - Store< void uiMsg(UIMESSAGE_CODE code,T1 a1,T2 a2,T3 a3) +// Use variadic templates. +// +// We must pass variable parameters by reference, so no temporary copies are +// created for custom string objects like CStringBase in 7-Zip decompression +// code. Such temporary copies would be destroyed inside of recursive +// uiMsgBase calls, leaving us with Str[] items pointing at released memory. +// Since we pass integer values as well, we can't use & references +// and must resort to && rvalue references. +template void uiMsg(UIMESSAGE_CODE Code,TN&&... aN) { - uiMsgStore Store(code); - Store<Overwrite==OVERWRITE_NONE) return UIASKREP_R_SKIP; diff --git a/libclamunrar/uiconsole.cpp b/libclamunrar/uiconsole.cpp index d713fac0b6..b524c25ff4 100644 --- a/libclamunrar/uiconsole.cpp +++ b/libclamunrar/uiconsole.cpp @@ -183,6 +183,7 @@ void uiMsgStore::Msg() Log(NULL,St(MNeedAdmin)); break; case UIERROR_ARCBROKEN: + mprintf(L"\n"); // So it is not merged with preceding UIERROR_HEADERBROKEN. Log(Str[0],St(MErrBrokenArc)); break; case UIERROR_HEADERBROKEN: @@ -262,6 +263,7 @@ void uiMsgStore::Msg() break; case UIERROR_MISSINGVOL: Log(Str[0],St(MAbsNextVol),Str[0]); + mprintf(L" "); // For progress percent. break; #ifndef SFX_MODULE case UIERROR_NEEDPREVVOL: @@ -395,7 +397,8 @@ void uiMsgStore::Msg() } -bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName, + SecPassword *Password,CheckPassword *CheckPwd) { // Unlike GUI we cannot provide Cancel button here, so we use the empty // password to abort. Otherwise user not knowing a password would need to diff --git a/libclamunrar/uisilent.cpp b/libclamunrar/uisilent.cpp index 1df0975665..8155885746 100644 --- a/libclamunrar/uisilent.cpp +++ b/libclamunrar/uisilent.cpp @@ -33,7 +33,8 @@ void uiMsgStore::Msg() } -bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName, + SecPassword *Password,CheckPassword *CheckPwd) { return false; } diff --git a/libclamunrar/ulinks.cpp b/libclamunrar/ulinks.cpp index af6ef36ff3..141a97fecc 100644 --- a/libclamunrar/ulinks.cpp +++ b/libclamunrar/ulinks.cpp @@ -70,7 +70,8 @@ static bool SafeCharToWide(const char *Src,wchar *Dest,size_t DestSize) } -bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +static bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc, + const wchar *LinkName,bool &UpLink) { char Target[NM]; if (IsLink(Arc.FileHead.FileAttr)) @@ -100,13 +101,14 @@ bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const w if (!Cmd->AbsoluteLinks && (IsFullPath(TargetW) || !IsRelativeSymlinkSafe(Cmd,Arc.FileHead.FileName,LinkName,TargetW))) return false; + UpLink=strstr(Target,"..")!=NULL; return UnixSymlink(Cmd,Target,LinkName,&Arc.FileHead.mtime,&Arc.FileHead.atime); } return false; } -bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd) +static bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd) { char Target[NM]; WideToChar(hd->RedirName,Target,ASIZE(Target)); @@ -127,8 +129,6 @@ bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd) // Use hd->FileName instead of LinkName, since LinkName can include // the destination path as a prefix, which can confuse // IsRelativeSymlinkSafe algorithm. - // 2022.05.04: Use TargetW instead of previously used hd->RedirName - // for security reason. if (!Cmd->AbsoluteLinks && (IsFullPath(TargetW) || !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,TargetW))) return false; diff --git a/libclamunrar/unicode.cpp b/libclamunrar/unicode.cpp index 5421923ff3..73b09bb220 100644 --- a/libclamunrar/unicode.cpp +++ b/libclamunrar/unicode.cpp @@ -229,10 +229,11 @@ void CharToWideMap(const char *Src,wchar *Dest,size_t DestSize,bool &Success) #endif -// SrcSize is in wide characters, not in bytes. -byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize) +// SrcSize is source data size in wide characters, not in bytes. +// DestSize is the maximum allowed destination size. +byte* WideToRaw(const wchar *Src,size_t SrcSize,byte *Dest,size_t DestSize) { - for (size_t I=0;I>8); diff --git a/libclamunrar/unicode.hpp b/libclamunrar/unicode.hpp index 8d433c1b6b..9bfd9c5dcd 100644 --- a/libclamunrar/unicode.hpp +++ b/libclamunrar/unicode.hpp @@ -7,7 +7,7 @@ bool WideToChar(const wchar *Src,char *Dest,size_t DestSize); bool CharToWide(const char *Src,wchar *Dest,size_t DestSize); -byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize); +byte* WideToRaw(const wchar *Src,size_t SrcSize,byte *Dest,size_t DestSize); wchar* RawToWide(const byte *Src,wchar *Dest,size_t DestSize); void WideToUtf(const wchar *Src,char *Dest,size_t DestSize); size_t WideToUtfSize(const wchar *Src); diff --git a/libclamunrar/unpack.cpp b/libclamunrar/unpack.cpp index 8dd2311901..9236e748bc 100644 --- a/libclamunrar/unpack.cpp +++ b/libclamunrar/unpack.cpp @@ -91,12 +91,6 @@ void Unpack::Init(size_t WinSize,bool Solid) if ((WinSize>>16)>0x10000) // Window size must not exceed 4 GB. return; - // Unrar does not support window size greather than 1GB at this time. - // Any request for a window larger than 1GB should be ignored. - const size_t MaxAllocSize=0x40000000; - if (WinSize>MaxAllocSize) - WinSize=MaxAllocSize; - // Archiving code guarantees that window size does not grow in the same // solid stream. So if we are here, we are either creating a new window // or increasing the size of non-solid window. So we could safely reject @@ -271,7 +265,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) Dec->DecodeLen[I]=(uint)LeftAligned; // Every item of this array contains the sum of all preceding items. - // So it contains the start position in code list for every bit length. + // So it contains the start position in code list for every bit length. Dec->DecodePos[I]=Dec->DecodePos[I-1]+LengthCount[I-1]; } @@ -315,7 +309,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) Dec->QuickBits=MAX_QUICK_DECODE_BITS; break; default: - Dec->QuickBits=MAX_QUICK_DECODE_BITS-3; + Dec->QuickBits=MAX_QUICK_DECODE_BITS>3 ? MAX_QUICK_DECODE_BITS-3 : 0; break; } @@ -334,7 +328,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) uint BitField=Code<<(16-Dec->QuickBits); // Prepare the table for quick decoding of bit lengths. - + // Find the upper limit for current bit field and adjust the bit length // accordingly if necessary. while (CurBitLengthDecodeLen) && BitField>=Dec->DecodeLen[CurBitLength]) diff --git a/libclamunrar/unpack.hpp b/libclamunrar/unpack.hpp index 30a9a2ee77..737b31c2cc 100644 --- a/libclamunrar/unpack.hpp +++ b/libclamunrar/unpack.hpp @@ -93,17 +93,17 @@ struct UnpackBlockTables #ifdef RAR_SMP enum UNP_DEC_TYPE { - UNPDT_LITERAL,UNPDT_MATCH,UNPDT_FULLREP,UNPDT_REP,UNPDT_FILTER + UNPDT_LITERAL=0,UNPDT_MATCH,UNPDT_FULLREP,UNPDT_REP,UNPDT_FILTER }; struct UnpackDecodedItem { - UNP_DEC_TYPE Type; + byte Type; // 'byte' instead of enum type to reduce memory use. ushort Length; union { uint Distance; - byte Literal[4]; + byte Literal[8]; // Store up to 8 chars here to speed up extraction. }; }; diff --git a/libclamunrar/unpack30.cpp b/libclamunrar/unpack30.cpp index 6a8efa23bc..7c2adfab28 100644 --- a/libclamunrar/unpack30.cpp +++ b/libclamunrar/unpack30.cpp @@ -55,7 +55,7 @@ void Unpack::Unpack29(bool Solid) if (!UnpReadBuf30()) break; } - if (((WrPtr-UnpPtr) & MaxWinMask)<260 && WrPtr!=UnpPtr) + if (((WrPtr-UnpPtr) & MaxWinMask)<=MAX3_INC_LZ_MATCH && WrPtr!=UnpPtr) { UnpWriteBuf30(); if (WrittenFileSize>DestUnpSize) diff --git a/libclamunrar/unpack50.cpp b/libclamunrar/unpack50.cpp index 99119507f9..e040907c27 100644 --- a/libclamunrar/unpack50.cpp +++ b/libclamunrar/unpack50.cpp @@ -42,7 +42,7 @@ void Unpack::Unpack5(bool Solid) break; } - if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) @@ -93,7 +93,7 @@ void Unpack::Unpack5(bool Solid) } else { - Distance+=Inp.getbits32()>>(32-DBits); + Distance+=Inp.getbits()>>(16-DBits); Inp.addbits(DBits); } } diff --git a/libclamunrar/unpack50mt.cpp b/libclamunrar/unpack50mt.cpp index 691ac8e993..82c9c4a8ce 100644 --- a/libclamunrar/unpack50mt.cpp +++ b/libclamunrar/unpack50mt.cpp @@ -345,7 +345,7 @@ void Unpack::UnpackDecode(UnpackThreadData &D) if (D.DecodedSize>1) { UnpackDecodedItem *PrevItem=CurItem-1; - if (PrevItem->Type==UNPDT_LITERAL && PrevItem->Length<3) + if (PrevItem->Type==UNPDT_LITERAL && PrevItem->LengthLiteral)-1) { PrevItem->Length++; PrevItem->Literal[PrevItem->Length]=(byte)MainSlot; @@ -388,7 +388,7 @@ void Unpack::UnpackDecode(UnpackThreadData &D) } else { - Distance+=D.Inp.getbits32()>>(32-DBits); + Distance+=D.Inp.getbits()>>(16-DBits); D.Inp.addbits(DBits); } } @@ -451,7 +451,7 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) while (ItemDestUnpSize) @@ -461,10 +461,10 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D) if (Item->Type==UNPDT_LITERAL) { #if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) - if (Item->Length==3 && UnpPtrLength==7 && UnpPtrLiteral; - UnpPtr+=4; + *(uint64 *)(Window+UnpPtr)=*(uint64 *)(Item->Literal); + UnpPtr+=8; } else #endif @@ -559,7 +559,7 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D) break; } } - if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) diff --git a/libclamunrar/uowners.cpp b/libclamunrar/uowners.cpp index 9f46308583..5eb1279027 100644 --- a/libclamunrar/uowners.cpp +++ b/libclamunrar/uowners.cpp @@ -1,56 +1,12 @@ -void ExtractUnixOwner20(Archive &Arc,const wchar *FileName) +void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) { char NameA[NM]; WideToChar(FileName,NameA,ASIZE(NameA)); - if (Arc.BrokenHeader) - { - uiMsg(UIERROR_UOWNERBROKEN,Arc.FileName,FileName); - ErrHandler.SetErrorCode(RARX_CRC); + if (memchr(&Arc.SubHead.SubData[0],0,Arc.SubHead.SubData.Size())==NULL) return; - } - - struct passwd *pw; - errno=0; // Required by getpwnam specification if we need to check errno. - if ((pw=getpwnam(Arc.UOHead.OwnerName))==NULL) - { - uiMsg(UIERROR_UOWNERGETOWNERID,Arc.FileName,GetWide(Arc.UOHead.OwnerName)); - ErrHandler.SysErrMsg(); - ErrHandler.SetErrorCode(RARX_WARNING); - return; - } - uid_t OwnerID=pw->pw_uid; - - struct group *gr; - errno=0; // Required by getgrnam specification if we need to check errno. - if ((gr=getgrnam(Arc.UOHead.GroupName))==NULL) - { - uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(Arc.UOHead.GroupName)); - ErrHandler.SysErrMsg(); - ErrHandler.SetErrorCode(RARX_CRC); - return; - } - uint Attr=GetFileAttr(FileName); - gid_t GroupID=gr->gr_gid; -#if defined(SAVE_LINKS) && !defined(_APPLE) - if (lchown(NameA,OwnerID,GroupID)!=0) -#else - if (chown(NameA,OwnerID,GroupID)!=0) -#endif - { - uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName); - ErrHandler.SetErrorCode(RARX_CREATE); - } - SetFileAttr(FileName,Attr); -} - - -void ExtractUnixOwner30(Archive &Arc,const wchar *FileName) -{ - char NameA[NM]; - WideToChar(FileName,NameA,ASIZE(NameA)); char *OwnerName=(char *)&Arc.SubHead.SubData[0]; int OwnerSize=strlen(OwnerName)+1; diff --git a/libclamunrar/version.hpp b/libclamunrar/version.hpp index 4807c9cb40..1f45c3a189 100644 --- a/libclamunrar/version.hpp +++ b/libclamunrar/version.hpp @@ -1,6 +1,6 @@ #define RARVER_MAJOR 6 -#define RARVER_MINOR 12 +#define RARVER_MINOR 23 #define RARVER_BETA 0 -#define RARVER_DAY 4 -#define RARVER_MONTH 5 -#define RARVER_YEAR 2022 +#define RARVER_DAY 1 +#define RARVER_MONTH 8 +#define RARVER_YEAR 2023 diff --git a/libclamunrar/volume.cpp b/libclamunrar/volume.cpp index 917851de40..4924d8d0ea 100644 --- a/libclamunrar/volume.cpp +++ b/libclamunrar/volume.cpp @@ -1,15 +1,15 @@ #include "rar.hpp" #ifdef RARDLL -static bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize); -static bool DllVolNotify(RAROptions *Cmd,wchar *NextName); +static bool DllVolChange(CommandData *Cmd,wchar *NextName,size_t NameSize); +static bool DllVolNotify(CommandData *Cmd,wchar *NextName); #endif bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Command) { - RAROptions *Cmd=Arc.GetRAROptions(); + CommandData *Cmd=Arc.GetCommandData(); HEADER_TYPE HeaderType=Arc.GetHeaderType(); FileHeader *hd=HeaderType==HEAD_SERVICE ? &Arc.SubHead:&Arc.FileHead; @@ -190,7 +190,7 @@ bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Comma #ifdef RARDLL -bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize) +bool DllVolChange(CommandData *Cmd,wchar *NextName,size_t NameSize) { bool DllVolChanged=false,DllVolAborted=false; @@ -246,7 +246,7 @@ bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize) #ifdef RARDLL -bool DllVolNotify(RAROptions *Cmd,wchar *NextName) +bool DllVolNotify(CommandData *Cmd,wchar *NextName) { char NextNameA[NM]; WideToChar(NextName,NextNameA,ASIZE(NextNameA)); diff --git a/libclamunrar/volume.hpp b/libclamunrar/volume.hpp index 2d6a6d5c1e..4ada10910b 100644 --- a/libclamunrar/volume.hpp +++ b/libclamunrar/volume.hpp @@ -1,10 +1,7 @@ #ifndef _RAR_VOLUME_ #define _RAR_VOLUME_ -void SplitArchive(Archive &Arc,FileHeader *fh,int64 *HeaderPos, - ComprDataIO *DataIO); bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName, wchar Command); -void SetVolWrite(Archive &Dest,int64 VolSize); #endif diff --git a/libclamunrar/win32lnk.cpp b/libclamunrar/win32lnk.cpp index 84ab63ff61..759c49003f 100644 --- a/libclamunrar/win32lnk.cpp +++ b/libclamunrar/win32lnk.cpp @@ -40,7 +40,7 @@ bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd) PrivSet=true; } - const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+2*NM+1024; + const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+2*NM*sizeof(wchar)+1024; Array Buf(BufSize); REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)&Buf[0]; diff --git a/libclamunrar/win32stm.cpp b/libclamunrar/win32stm.cpp index eaa43be2d9..9dab728bd4 100644 --- a/libclamunrar/win32stm.cpp +++ b/libclamunrar/win32stm.cpp @@ -39,6 +39,7 @@ void ExtractStreams20(Archive &Arc,const wchar *FileName) CharToWide(Arc.StreamHead.StreamName,StoredName,ASIZE(StoredName)); ConvertPath(StoredName+1,StoredName+1,ASIZE(StoredName)-1); + wcsncatz(StreamName,StoredName,ASIZE(StreamName)); FindData fd; @@ -111,16 +112,23 @@ void ExtractStreams(Archive &Arc,const wchar *FileName,bool TestMode) wcsncatz(FullName,StreamName,ASIZE(FullName)); + FindData fd; - bool Found=FindFile::FastFind(FileName,&fd); + bool HostFound=FindFile::FastFind(FileName,&fd); if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0) SetFileAttr(FileName,fd.FileAttr & ~FILE_ATTRIBUTE_READONLY); File CurFile; - if (CurFile.WCreate(FullName) && Arc.ReadSubData(NULL,&CurFile,false)) - CurFile.Close(); + + if (CurFile.WCreate(FullName)) + { + if (Arc.ReadSubData(NULL,&CurFile,false)) + CurFile.Close(); + } + + // Restoring original file timestamps. File HostFile; - if (Found && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) + if (HostFound && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) SetFileTime(HostFile.GetHandle(),&fd.ftCreationTime,&fd.ftLastAccessTime, &fd.ftLastWriteTime); From 6358fc20611bd667be2a87a1063364f1d87562aa Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Tue, 22 Aug 2023 15:22:46 -0700 Subject: [PATCH 3/8] Set UnRAR library to build using C++11 I'm seeing 'error: unknown type name 'constexpr' with the UnRAR update. It seems that C++11 must be enabled for 'constexpr' keyword to be recognized. --- libclamunrar/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libclamunrar/CMakeLists.txt b/libclamunrar/CMakeLists.txt index 1cc741b089..9e8c5e8816 100644 --- a/libclamunrar/CMakeLists.txt +++ b/libclamunrar/CMakeLists.txt @@ -70,7 +70,10 @@ if(ENABLE_SHARED_LIB) add_library( clamunrar SHARED ) set_target_properties(clamunrar PROPERTIES VERSION ${LIBCLAMAV_VERSION} - SOVERSION ${LIBCLAMAV_SOVERSION}) + SOVERSION ${LIBCLAMAV_SOVERSION} + CXX_STANDARD 11 + ) + target_sources( clamunrar PRIVATE ${UNRAR_SOURCES} @@ -112,6 +115,9 @@ endif() if(ENABLE_STATIC_LIB) # The clamunrar static library. add_library( clamunrar_static STATIC) + set_target_properties(clamunrar_static PROPERTIES + CXX_STANDARD 11 + ) target_sources( clamunrar_static PRIVATE ${UNRAR_SOURCES} From b516d5252317a13a530eed2070b877bbfd9179fd Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Fri, 25 Aug 2023 09:47:44 -0700 Subject: [PATCH 4/8] Set UnRAR interface library to build using C++11 Because it includes rar.hpp, the UnRAR iface also uses C++11 features. --- libclamunrar/CMakeLists.txt | 4 ++-- libclamunrar_iface/CMakeLists.txt | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libclamunrar/CMakeLists.txt b/libclamunrar/CMakeLists.txt index 9e8c5e8816..7d6f393f10 100644 --- a/libclamunrar/CMakeLists.txt +++ b/libclamunrar/CMakeLists.txt @@ -68,7 +68,7 @@ endif() if(ENABLE_SHARED_LIB) # The unrar shared library. add_library( clamunrar SHARED ) - set_target_properties(clamunrar PROPERTIES + set_target_properties( clamunrar PROPERTIES VERSION ${LIBCLAMAV_VERSION} SOVERSION ${LIBCLAMAV_SOVERSION} CXX_STANDARD 11 @@ -115,7 +115,7 @@ endif() if(ENABLE_STATIC_LIB) # The clamunrar static library. add_library( clamunrar_static STATIC) - set_target_properties(clamunrar_static PROPERTIES + set_target_properties( clamunrar_static PROPERTIES CXX_STANDARD 11 ) target_sources( clamunrar_static diff --git a/libclamunrar_iface/CMakeLists.txt b/libclamunrar_iface/CMakeLists.txt index 6d9795db12..e193adc5ae 100644 --- a/libclamunrar_iface/CMakeLists.txt +++ b/libclamunrar_iface/CMakeLists.txt @@ -34,9 +34,11 @@ if(ENABLE_UNRAR) if(ENABLE_SHARED_LIB) # The clamunrar_iface SHARED library. add_library( clamunrar_iface SHARED ) - set_target_properties(clamunrar_iface PROPERTIES + set_target_properties( clamunrar_iface PROPERTIES VERSION ${LIBCLAMAV_VERSION} - SOVERSION ${LIBCLAMAV_SOVERSION}) + SOVERSION ${LIBCLAMAV_SOVERSION} + CXX_STANDARD 11 + ) target_sources( clamunrar_iface PRIVATE @@ -86,6 +88,9 @@ if(ENABLE_UNRAR) if(ENABLE_STATIC_LIB) # The clamunrar_iface static library. add_library( clamunrar_iface_static STATIC) + set_target_properties( clamunrar_iface_static PROPERTIES + CXX_STANDARD 11 + ) target_sources( clamunrar_iface_static PRIVATE From 793edae939f7b96572c1cc3d23cbdc046c1facb3 Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Wed, 23 Aug 2023 10:06:59 -0700 Subject: [PATCH 5/8] Patch UnRAR: don't replace symlinks with directories UnRAR logic replaces directory symlinks found within archive file entry file paths with actual directories by deleting them after they're extracted. Unfortunately, this logic extends to deleting existing directories if you set the `DestName` instead of the `DestPath` in this API: rc = RARProcessFile(hArchive, RAR_EXTRACT, NULL, destFilePath); In the future UnRAR may change to disable the `LinksToDirs()` feature if using the `DestName` parameter. In the meantime, this commit completely disables it for our use case. --- libclamunrar/extract.cpp | 60 +++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/libclamunrar/extract.cpp b/libclamunrar/extract.cpp index f5e8fa445d..4b746fc020 100644 --- a/libclamunrar/extract.cpp +++ b/libclamunrar/extract.cpp @@ -82,7 +82,7 @@ void CmdExtract::DoExtract() { if (Cmd->ManualPassword) Cmd->Password.Clean(); // Clean user entered password before processing next archive. - + ReconstructDone=false; // Must be reset here, not in ExtractArchiveInit(). UseExactVolName=false; // Must be reset here, not in ExtractArchiveInit(). while (true) @@ -98,7 +98,7 @@ void CmdExtract::DoExtract() if (Cmd->ManualPassword) Cmd->Password.Clean(); - if (TotalFileCount==0 && Cmd->Command[0]!='I' && + if (TotalFileCount==0 && Cmd->Command[0]!='I' && ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password. { if (!PasswordCancelled) @@ -240,7 +240,7 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() return EXTRACT_ARC_REPEAT; } #endif - + // Calculate the total size of all accessible volumes. // This size is necessary to display the correct total progress indicator. @@ -249,7 +249,7 @@ EXTRACT_ARC_CODE CmdExtract::ExtractArchive() while (true) { - // First volume is already added to DataIO.TotalArcSize + // First volume is already added to DataIO.TotalArcSize // in initial TotalArcSize calculation in DoExtract. // So we skip it and start from second volume. NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); @@ -494,7 +494,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (wcscmp(ArcFileName,RefList[I].RefName)==0) { ExtractRef *MatchedRef=&RefList[I]; - + if (!Cmd->Test) // While harmless, it is useless for 't'. { // If reference source isn't selected, but target is selected, @@ -513,13 +513,13 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) RefTarget=true; // Need it even for 't' to test the reference source. break; } - + if (Arc.FileHead.Encrypted && Cmd->SkipEncrypted) if (Arc.Solid) return false; // Abort the entire extraction for solid archive. else MatchFound=false; // Skip only the current file for non-solid archive. - + if (MatchFound || RefTarget || (SkipSolid=Arc.Solid)!=0) { // First common call of uiStartFileExtract. It is done before overwrite @@ -541,10 +541,10 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) { if (FD.mtime >= Arc.FileHead.mtime) { - // If directory already exists and its modification time is newer - // than start of extraction, it is likely it was created - // when creating a path to one of already extracted items. - // In such case we'll better update its time even if archived + // If directory already exists and its modification time is newer + // than start of extraction, it is likely it was created + // when creating a path to one of already extracted items. + // In such case we'll better update its time even if archived // directory is older. if (!FD.IsDir || FD.mtimeDllDestName,ASIZE(DestFileName)); #endif - if (ExtrFile && Command!='P' && !Cmd->Test && !Cmd->AbsoluteLinks && - ConvertSymlinkPaths) - ExtrFile=LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink); - File CurFile; bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; @@ -688,7 +684,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) ExtrFile=true; // We changed SkipSolid, so we need to call uiStartFileExtract - // with "Skip" parameter to change the operation status + // with "Skip" parameter to change the operation status // from "extracting" to "skipping". For example, it can be necessary // if user answered "No" to overwrite prompt when unpacking // a solid archive. @@ -779,7 +775,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) { wchar RedirName[NM]; - + // 2022.11.15: Might be needed when unpacking WinRAR 5.0 links with // Unix RAR. WinRAR 5.0 used \ path separators here, when beginning // from 5.10 even Windows version uses / internally and converts @@ -810,7 +806,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) // Unix symlink can have its own owner data. if (LinkSuccess) SetFileHeaderExtra(Cmd,Arc,DestFileName); - + ConvertSymlinkPaths|=LinkSuccess && UpLink; // We do not actually need to reset the cache here if we cache @@ -830,7 +826,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) uiMsg(UIERROR_UNKNOWNEXTRA,Arc.FileName,ArcFileName); LinkSuccess=false; } - + if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode) { // RAR 5.x links have a valid data checksum even in case of @@ -878,9 +874,9 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) else if (Arc.FileHead.Method!=0 && Arc.FileHead.UnpSize>0 && ValidCRC) AnySolidDataUnpackedWell=true; - + bool BrokenFile=false; - + // Checksum is not calculated in skip solid mode for performance reason. if (!SkipSolid && ShowChecksum) { @@ -892,7 +888,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) } else { - if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || + if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || Arc.BrokenHeader) && !AnySolidDataUnpackedWell) uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName); else @@ -913,7 +909,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) // We check SkipSolid to remove percent for skipped solid files only. // We must not apply these \b to links with ShowChecksum==false // and their possible error messages. - if (SkipSolid) + if (SkipSolid) mprintf(L"\b\b\b\b\b "); } @@ -926,7 +922,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) bool SetAttrOnly=LinkEntry && Arc.FileHead.RedirType==FSREDIR_HARDLINK && LinkSuccess; if (!TestMode && (Command=='X' || Command=='E') && - (!LinkEntry || SetAttrOnly || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && + (!LinkEntry || SetAttrOnly || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && (!BrokenFile || Cmd->KeepBroken)) { // Below we use DestFileName instead of CurFile.FileName, @@ -953,7 +949,7 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); } - + #if defined(_WIN_ALL) && !defined(SFX_MODULE) if (Cmd->SetCompressedAttr && (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0) @@ -1098,7 +1094,7 @@ void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *De wcsncpyz(DestName,ArcFileName,DestSize); return; } - + wcsncpyz(DestName,Cmd->ExtrPath,DestSize); if (*Cmd->ExtrPath!=0) @@ -1145,7 +1141,7 @@ void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *De { size_t NameLength=wcslen(ArcFileName); if (NameLength>=ArcPathLength && wcsnicompc(ArcPath,ArcFileName,ArcPathLength)==0 && - (IsPathDiv(ArcPath[ArcPathLength-1]) || + (IsPathDiv(ArcPath[ArcPathLength-1]) || IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0)) { ArcFileName+=Min(ArcPathLength,NameLength); @@ -1464,12 +1460,12 @@ bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) // Find non-matched reference sources in solid and non-solid archives. // Detect the optimal start position for semi-solid archives // and optimal start volume for independent solid volumes. -// +// // Alternatively we could collect references while extracting an archive // and perform the second extraction pass for references only. // But it would be slower for solid archives than scaning headers // in first pass and extracting everything in second, as implemented now. -// +// void CmdExtract::AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumbering) { FreeAnalyzeData(); // If processing non-first archive in multiple archives set. @@ -1485,13 +1481,13 @@ void CmdExtract::AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumberi GetFirstVolIfFullSet(ArcName,NewNumbering,NextName,ASIZE(NextName)); else wcsncpyz(NextName,ArcName,ASIZE(NextName)); - + bool MatchFound=false; bool PrevMatched=false; bool OpenNext=false; bool FirstVolume=true; - + // We shall set FirstFile once for all volumes and not for each volume. // So we do not reuse the outdated Analyze->StartPos from previous volume // if extracted file resides completely in the beginning of current one. @@ -1546,7 +1542,7 @@ void CmdExtract::AnalyzeArchive(const wchar *ArcName,bool Volume,bool NewNumberi wcsncpyz(Analyze->StartName,NextName,ASIZE(Analyze->StartName)); // We shall set FirstFile once for all volumes for this code - // to work properly. Alternatively we could append + // to work properly. Alternatively we could append // "|| Analyze->StartPos!=0" to the condition, so we do not reuse // the outdated Analyze->StartPos value from previous volume. if (!FirstFile) From a612a7886bf840482dec986d8c09dfcf1f62814b Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Fri, 28 Sep 2018 14:30:42 -0400 Subject: [PATCH 6/8] Patch UnRAR: allow skipping files in solid archives This is a cherry-pick of commit 24f225c21ffa77dbd4b2a3c0377b9b3f86547ce6 Modification to unrar codebase allowing skipping of files within Solid archives when parsing in extraction mode, enabling us to skip encrypted files while still scanning metadata and potentially scanning unencrypted files later in the archive. --- libclamunrar/dll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libclamunrar/dll.cpp b/libclamunrar/dll.cpp index 31818e4980..0464c9ace6 100644 --- a/libclamunrar/dll.cpp +++ b/libclamunrar/dll.cpp @@ -329,7 +329,7 @@ int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestNa { Data->Cmd.DllError=0; if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT || - Operation==RAR_SKIP && !Data->Arc.Solid) + Operation==RAR_SKIP) // && !Data->Arc.Solid) { if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE && Data->Arc.FileHead.SplitAfter) From 5bbeaa5a282bdda471aa15b3c3ce0fa70618d0db Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Sun, 18 Jul 2021 18:26:23 -0700 Subject: [PATCH 7/8] Patch UnRAR: limit dict winsize to 1GB Prevent allocating more than 1GB regardless of what is requested. RAR dictionary sizes may not be larger than 1GB, at least in the current version. This is a cherry-pick of commit 9b444e7e02639d1030bbc38f9c95511bbe19e67b --- libclamunrar/unpack.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libclamunrar/unpack.cpp b/libclamunrar/unpack.cpp index 9236e748bc..f82bd4753b 100644 --- a/libclamunrar/unpack.cpp +++ b/libclamunrar/unpack.cpp @@ -91,6 +91,12 @@ void Unpack::Init(size_t WinSize,bool Solid) if ((WinSize>>16)>0x10000) // Window size must not exceed 4 GB. return; + // Unrar does not support window size greather than 1GB at this time. + // Any request for a window larger than 1GB should be ignored. + const size_t MaxAllocSize=0x40000000; + if (WinSize>MaxAllocSize) + WinSize=MaxAllocSize; + // Archiving code guarantees that window size does not grow in the same // solid stream. So if we are here, we are either creating a new window // or increasing the size of non-solid window. So we could safely reject @@ -265,7 +271,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) Dec->DecodeLen[I]=(uint)LeftAligned; // Every item of this array contains the sum of all preceding items. - // So it contains the start position in code list for every bit length. + // So it contains the start position in code list for every bit length. Dec->DecodePos[I]=Dec->DecodePos[I-1]+LengthCount[I-1]; } @@ -328,7 +334,7 @@ void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size) uint BitField=Code<<(16-Dec->QuickBits); // Prepare the table for quick decoding of bit lengths. - + // Find the upper limit for current bit field and adjust the bit length // accordingly if necessary. while (CurBitLengthDecodeLen) && BitField>=Dec->DecodeLen[CurBitLength]) From 37f322714a63a9d74fecf9ea8694862a8dddd503 Mon Sep 17 00:00:00 2001 From: Micah Snyder Date: Tue, 22 Aug 2023 13:26:44 -0700 Subject: [PATCH 8/8] News: Add notes for changes in 1.1.2 patch version --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 2d7836766e..fcea6fc725 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,9 @@ differ slightly from third-party binary packages. ClamAV 1.1.2 is a critical patch release with the following fixes: +- Upgrade the bundled UnRAR library (libclamunrar) to version 6.2.10. + - GitHub pull request: https://github.com/Cisco-Talos/clamav/pull/1011 + ## 1.1.1 ClamAV 1.1.1 is a critical patch release with the following fixes: