diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a69f3f2e..a15ea12d82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ string(TIMESTAMP TODAY "%Y%m%d") set(VERSION_SUFFIX "") project( ClamAV - VERSION "1.0.2" + VERSION "1.0.3" 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 34598cee04..a3996e84f5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,7 @@ properties( parameters( [ string(name: 'VERSION', - defaultValue: '1.0.2', + defaultValue: '1.0.3', description: 'ClamAV version string'), string(name: 'FRAMEWORK_BRANCH', defaultValue: '1.0', diff --git a/NEWS.md b/NEWS.md index f139701469..d11b535508 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,13 @@ Note: This file refers to the official packages. Things described here may differ slightly from third-party binary packages. +## 1.0.3 + +ClamAV 1.0.3 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/1010 + ## 1.0.2 ClamAV 1.0.2 is a critical patch release with the following fixes: diff --git a/libclamav/bytecode_api.h b/libclamav/bytecode_api.h index caee7f600c..ebb58df145 100644 --- a/libclamav/bytecode_api.h +++ b/libclamav/bytecode_api.h @@ -166,6 +166,7 @@ enum FunctionalityLevels { FUNC_LEVEL_1_0 = 160, /**< LibClamAV release 1.0.0 */ FUNC_LEVEL_1_0_1 = 161, /**< LibClamAV release 1.0.1 */ FUNC_LEVEL_1_0_2 = 162, /**< LibClamAV release 1.0.2 */ + FUNC_LEVEL_1_0_3 = 163, /**< LibClamAV release 1.0.3 */ }; /** diff --git a/libclamav/others.h b/libclamav/others.h index 761d2c17fa..3893a8254a 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -73,7 +73,7 @@ * in re-enabling affected modules. */ -#define CL_FLEVEL 161 +#define CL_FLEVEL 163 #define CL_FLEVEL_DCONF CL_FLEVEL #define CL_FLEVEL_SIGTOOL CL_FLEVEL diff --git a/libclamunrar/CMakeLists.txt b/libclamunrar/CMakeLists.txt index 6b924b466a..e54348f845 100644 --- a/libclamunrar/CMakeLists.txt +++ b/libclamunrar/CMakeLists.txt @@ -68,9 +68,12 @@ 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}) + SOVERSION ${LIBCLAMAV_SOVERSION} + CXX_STANDARD 11 + ) + target_sources( clamunrar PRIVATE ${UNRAR_SOURCES} @@ -110,6 +113,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} 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.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..4b746fc020 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))) { 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) @@ -56,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) @@ -97,7 +139,11 @@ void CmdExtract::ExtractArchiveInit(Archive &Arc) AllMatchesExact=true; AnySolidDataUnpackedWell=false; + ArcAnalyzed=false; + StartTime.SetCurrentTime(); + + LastCheckedSymlink.clear(); } @@ -168,15 +214,28 @@ 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; } @@ -190,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); @@ -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; @@ -439,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.mtimePassword; -#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. - { - // 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. + // 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 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) @@ -542,11 +649,12 @@ bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) 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); @@ -576,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. @@ -667,7 +775,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,20 +793,40 @@ 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; } - + if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode) { // RAR 5.x links have a valid data checksum even in case of @@ -709,6 +847,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 @@ -735,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) { @@ -749,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 @@ -770,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 "); } @@ -783,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, @@ -810,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) @@ -866,22 +1005,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 +1071,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 +1085,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) @@ -946,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); @@ -1033,11 +1228,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 +1250,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 +1326,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 +1409,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 +1457,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 Matched=false; - while (!Matched) + 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. + 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 +1522,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 +1614,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..f82bd4753b 100644 --- a/libclamunrar/unpack.cpp +++ b/libclamunrar/unpack.cpp @@ -315,7 +315,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; } 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); diff --git a/libclamunrar_iface/CMakeLists.txt b/libclamunrar_iface/CMakeLists.txt index 4918eb8a10..d7c4648e55 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 @@ -84,6 +86,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 diff --git a/win32/res/common.rc b/win32/res/common.rc index b5ee7de048..250ce57915 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,0,2,0 -#define RES_VER_S "ClamAV 1.0.2" +#define RES_VER_Q 1,0,3,0 +#define RES_VER_S "ClamAV 1.0.3" VS_VERSION_INFO VERSIONINFO FILEVERSION RES_VER_Q