Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package unrar for openSUSE:Factory:NonFree checked in at 2026-09-16 17:39:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory:NonFree/unrar (Old) and /work/SRC/openSUSE:Factory:NonFree/.unrar.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "unrar" Wed Sep 16 17:39:37 2026 rev:123 rq:1378006 version:7.3.1 Changes: -------- --- /work/SRC/openSUSE:Factory:NonFree/unrar/unrar.changes 2026-08-27 18:47:55.144365321 +0200 +++ /work/SRC/openSUSE:Factory:NonFree/.unrar.new.383539/unrar.changes 2026-09-16 17:39:38.526279547 +0200 @@ -1,0 +2,24 @@ +Sat Sep 12 14:20:23 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 7.3.1 (upstream tarball dated 2026-09-09): + * New -da switch deletes the archive after successful extraction. + * New -ed1 switch excludes only empty directories; -ed still + excludes all directories and -e+d adds directories only. + * The v and l commands accept the a modifier without t, so va + and la also list service blocks and NTFS stream names. + * Drop the weak time-based RNG fallback: key generation now fails + instead of silently using predictable bytes when no randomness + source is available. + * Harden parsing against crafted archives: out-of-range RAR5 + header and redirect types are clamped, PPM decode and ACL sizes + gained bounds checks, undersized REV volumes are skipped. + * Extracted files lose SUID/SGID and directories lose SUID for + non-root users, with a warning if permissions cannot be set. + * An existing empty folder can now be replaced by an extracted + file after confirmation. +- Rename libunrar7_2_7 to libunrar7_3_1: the soname carries the full + version (libunrar.so.7.3.1 via soname.patch, applies with no fuzz). + The library API version stays at 10 and no package in Factory + consumes libunrar, so there is no rebuild scope. + +------------------------------------------------------------------- Old: ---- unrarsrc-7.2.7.tar.gz New: ---- unrarsrc-7.3.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ unrar.spec ++++++ --- /var/tmp/diff_new_pack.joC8vm/_old 2026-09-16 17:39:40.939380375 +0200 +++ /var/tmp/diff_new_pack.joC8vm/_new 2026-09-16 17:39:40.940380417 +0200 @@ -18,9 +18,9 @@ # majorversion should match the major version number. %define majorversion 7 -%define libsuffix 7_2_7 +%define libsuffix 7_3_1 Name: unrar -Version: 7.2.7 +Version: 7.3.1 Release: 0 Summary: A program to extract, test, and view RAR archives License: NonFree ++++++ baselibs.conf ++++++ --- /var/tmp/diff_new_pack.joC8vm/_old 2026-09-16 17:39:41.037384470 +0200 +++ /var/tmp/diff_new_pack.joC8vm/_new 2026-09-16 17:39:41.054385181 +0200 @@ -1,2 +1,2 @@ -libunrar7_2_7 +libunrar7_3_1 ++++++ unrarsrc-7.2.7.tar.gz -> unrarsrc-7.3.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/arcread.cpp new/unrar/arcread.cpp --- old/unrar/arcread.cpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/arcread.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -673,7 +673,12 @@ uint HeaderCRC=Raw.GetCRC50(); - ShortBlock.HeaderType=(HEADER_TYPE)Raw.GetV(); + uint64 HeaderType=Raw.GetV(); + // Adjust values larger than maximum enum type, because they might not be + // able to fit to udnerlying enum data type and storing them is undefined + // behavior in C++. + ShortBlock.HeaderType=Min((HEADER_TYPE)HeaderType,HEAD_UNKNOWN); + ShortBlock.Flags=(uint)Raw.GetV(); ShortBlock.SkipIfUnknown=(ShortBlock.Flags & HFL_SKIPIFUNKNOWN)!=0; ShortBlock.HeadSize=HeaderSize; @@ -1171,14 +1176,15 @@ break; case FHEXTRA_REDIR: { - FILE_SYSTEM_REDIRECT RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV(); + uint64 RedirType=Raw->GetV(); uint Flags=(uint)Raw->GetV(); size_t NameSize=(size_t)Raw->GetV(); if (NameSize>0 && NameSize<MAXPATHSIZE) { std::string UtfName(NameSize,0); - hd->RedirType=RedirType; + hd->RedirType=RedirType<FSREDIR_UNKNOWN ? + (FILE_SYSTEM_REDIRECT)RedirType:FSREDIR_UNKNOWN; hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0; Raw->GetB(&UtfName[0],NameSize); UtfToWide(&UtfName[0],hd->RedirName); @@ -1530,7 +1536,11 @@ return false; SubDataIO.UnpHash.Init(SubHead.FileHash.Type,1); SubDataIO.SetPackedSizeToRead(SubHead.PackSize); + + // We would need to either reuse extraction DataIO or set + // SubDataIO.TotalArcSize to display progress. SubDataIO.EnableShowProgress(false); + SubDataIO.SetFiles(this,DestFile); SubDataIO.SetTestMode(TestMode); SubDataIO.UnpVolume=SubHead.SplitAfter; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/cmddata.cpp new/unrar/cmddata.cpp --- old/unrar/cmddata.cpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/cmddata.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -11,7 +11,11 @@ void CommandData::Init() { - RAROptions::Init(); + // "*this={}" in RAROptions constructor causes the constructor to call itself + // infinitely, so we do it in the derived class instead. + *(RAROptions*)this={}; // Initialize all fields to default values. + + RAROptions::Init(); // Set all non-default values. Command.clear(); ArcName.clear(); @@ -404,9 +408,16 @@ case 'H': OpenShared=true; break; - case 'F': - DeleteFiles=true; + case 'A': + DeleteArchive=true; + break; + +#ifdef _WIN_ALL // Need it in UnRAR too for -da switch. + case 'R': + DeleteToRecycleBin=true; break; +#endif + default: BadSwitch(Switch); break; @@ -442,11 +453,11 @@ default: if (Switch[1]=='+') { - InclFileAttr|=GetExclAttr(Switch+2,InclDir); + InclFileAttr|=GetExclAttr(Switch+2,false,DirMode); InclAttrSet=true; } else - ExclFileAttr|=GetExclAttr(Switch+1,ExclDir); + ExclFileAttr|=GetExclAttr(Switch+1,true,DirMode); break; } break; @@ -1208,7 +1219,7 @@ #ifndef SFX_MODULE -uint CommandData::GetExclAttr(const wchar *Str,bool &Dir) +uint CommandData::GetExclAttr(const wchar *Str,bool Exclude,DIR_FILTER_MODE &DirMode) { if (IsDigit(*Str)) return wcstol(Str,NULL,0); @@ -1219,7 +1230,10 @@ switch(toupperw(*Str)) { case 'D': - Dir=true; + if (Exclude) + DirMode=Str[1]=='1' ? DIRFM_EXCLUDE_EMPTY:DIRFM_EXCLUDE_ALL; + else + DirMode=DIRFM_DIR_ONLY; break; #ifdef _UNIX case 'V': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/cmddata.hpp new/unrar/cmddata.hpp --- old/unrar/cmddata.hpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/cmddata.hpp 2026-09-09 15:11:09.000000000 +0200 @@ -19,7 +19,7 @@ private: void ProcessSwitch(const wchar *Switch); void BadSwitch(const wchar *Switch); - uint GetExclAttr(const wchar *Str,bool &Dir); + uint GetExclAttr(const wchar *Str,bool Exclude,DIR_FILTER_MODE &DirMode); #if !defined(SFX_MODULE) void SetTimeFilters(const wchar *Mod,bool Before,bool Age); void SetStoreTimeMode(const wchar *S); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/cmdfilter.cpp new/unrar/cmdfilter.cpp --- old/unrar/cmdfilter.cpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/cmdfilter.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -308,10 +308,11 @@ #ifndef SFX_MODULE if (TimeCheck(FileHead.mtime,FileHead.ctime,FileHead.atime)) return 0; - if ((FileHead.FileAttr & ExclFileAttr)!=0 || FileHead.Dir && ExclDir) + if ((FileHead.FileAttr & ExclFileAttr)!=0 || + FileHead.Dir && DirMode==DIRFM_EXCLUDE_ALL) return 0; if (InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0 && - (!FileHead.Dir || !InclDir)) + (!FileHead.Dir || DirMode!=DIRFM_DIR_ONLY)) return 0; if (!Dir && SizeCheck(FileHead.UnpSize)) return 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/cmdmix.cpp new/unrar/cmdmix.cpp --- old/unrar/cmdmix.cpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/cmdmix.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -62,8 +62,8 @@ MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL, MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm, MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP, - MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,MCHelpSwDH,MCHelpSwEP, - MCHelpSwEP3,MCHelpSwEP4,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, + MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,MCHelpSwDA,MCHelpSwDH, + MCHelpSwEP,MCHelpSwEP3,MCHelpSwEP4,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwME,MCHelpSwMLP, MCHelpSwN,MCHelpSwNa,MCHelpSwNal,MCHelpSwO,MCHelpSwOC,MCHelpSwOL, MCHelpSwOM,MCHelpSwOP,MCHelpSwOR,MCHelpSwOW,MCHelpSwP,MCHelpSwR, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/consio.cpp new/unrar/consio.cpp --- old/unrar/consio.cpp 2026-06-27 13:35:30.000000000 +0200 +++ new/unrar/consio.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -398,6 +398,8 @@ void OutComment(const std::wstring &Comment) { + // After we added Esc filtering to mprintf, this check seems to be excessive. + // Still keeping it for now just in case. if (IsCommentUnsafe(Comment)) return; const size_t MaxOutSize=0x400; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/crypt.cpp new/unrar/crypt.cpp --- old/unrar/crypt.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/crypt.cpp 2026-09-09 15:11:09.000000000 +0200 @@ -95,21 +95,6 @@ } -// Use the current system time to additionally randomize data. -static void TimeRandomize(byte *RndBuf,size_t BufSize) -{ - static uint Count=0; - RarTime CurTime; - CurTime.SetCurrentTime(); - uint64 Random=CurTime.GetWin()+clock(); - for (size_t I=0;I<BufSize;I++) - { - byte RndByte = byte (Random >> ( (I & 7) * 8 )); - RndBuf[I]=byte( (RndByte ^ I) + Count++); - } -} - - // Fill buffer with random data. @@ -124,14 +109,21 @@ CryptReleaseContext(hProvider, 0); } #elif defined(_UNIX) - FILE *rndf = fopen("/dev/urandom", "r"); + FILE *rndf = fopen("/dev/urandom", "rb"); if (rndf!=NULL) { - Success=fread(RndBuf, BufSize, 1, rndf) == BufSize; + Success=fread(RndBuf, 1, BufSize, rndf) == BufSize; fclose(rndf); } #endif - // We use this code only as the last resort if code above failed. if (!Success) - TimeRandomize(RndBuf,BufSize); + { +#if defined(_WIN_ALL) + const wchar *ErrMsg=L"CryptGenRandom"; +#else + const wchar *ErrMsg=L"/dev/urandom"; +#endif + if (!Success) + ErrHandler.OpenError(ErrMsg); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/crypt.hpp new/unrar/crypt.hpp --- old/unrar/crypt.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/crypt.hpp 2026-09-09 15:11:09.000000000 +0200 @@ -153,7 +153,8 @@ bool Check(SecPassword *Password) { byte PswCheck[SIZE_PSWCHECK]; - Crypt->SetCryptKeys(false,CRYPT_RAR50,Password,Salt,InitV,Lg2Count,NULL,PswCheck); + if (!Crypt->SetCryptKeys(false,CRYPT_RAR50,Password,Salt,InitV,Lg2Count,NULL,PswCheck)) + return false; return memcmp(PswCheck,this->PswCheck,sizeof(this->PswCheck))==0; } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/dll.rc new/unrar/dll.rc --- old/unrar/dll.rc 2026-06-27 12:54:34.000000000 +0200 +++ new/unrar/dll.rc 2026-09-09 15:06:35.000000000 +0200 @@ -2,8 +2,8 @@ #include <commctrl.h> VS_VERSION_INFO VERSIONINFO -FILEVERSION 7, 23, 100, 2007 -PRODUCTVERSION 7, 23, 100, 2007 +FILEVERSION 7, 30, 1, 2082 +PRODUCTVERSION 7, 30, 1, 2082 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP { @@ -14,9 +14,9 @@ VALUE "CompanyName", "Alexander Roshal\0" VALUE "ProductName", "RAR decompression library\0" VALUE "FileDescription", "RAR decompression library\0" - VALUE "FileVersion", "7.23.0\0" - VALUE "ProductVersion", "7.23.0\0" - VALUE "LegalCopyright", "Copyright � Alexander Roshal 1993-2026\0" + VALUE "FileVersion", "7.30.1\0" + VALUE "ProductVersion", "7.30.1\0" + VALUE "LegalCopyright", "Copyright � Alexander Roshal 1993-2026\0" VALUE "OriginalFilename", "Unrar.dll\0" } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/errhnd.hpp new/unrar/errhnd.hpp --- old/unrar/errhnd.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/errhnd.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -17,6 +17,7 @@ RARX_BADPWD = 11, RARX_READ = 12, RARX_BADARC = 13, + RARX_DELETE = 14, RARX_USERBREAK = 255 }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/extinfo.cpp new/unrar/extinfo.cpp --- old/unrar/extinfo.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/extinfo.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -51,7 +51,7 @@ if (!Cmd->Test && Cmd->ProcessOwners && Arc.SubHead.CmpName(SUBHEAD_TYPE_ACL)) ExtractACL(Arc,Name); if (Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) - ExtractStreams(Arc,Name,Cmd->Test); + ExtractStreams(Cmd,Arc,Name); #endif } @@ -112,7 +112,8 @@ // Catch root dir based /path/file paths also as stuff like \\?\. // Do not check PrepSrcName here, it can be root based if destination path // is a root based. - if (IsFullRootPath(SrcName) || IsFullRootPath(TargetName)) + if (IsFullRootPath(SrcName) || IsFullRootPath(TargetName) || + IsDriveLetter(TargetName)) return false; // Number of ".." in link target. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/extract.cpp new/unrar/extract.cpp --- old/unrar/extract.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/extract.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -119,6 +119,9 @@ void CmdExtract::ExtractArchiveInit(Archive &Arc) { + // We do not set the test mode it for 'P' command here, because 'P' needs + // to perform the actual file write to stdout and test mode prevents + // file write. if (Cmd->Command[0]=='T' || Cmd->Command[0]=='I') Cmd->Test=true; @@ -212,6 +215,10 @@ if (Arc.FailedHeaderDecryption) // Bad archive password. return EXTRACT_ARC_NEXT; +#if !defined(SFX_MODULE) && !defined(RARDLL) + bool FirstVolume=Arc.FirstVolume; // It can be changed while extracting, so preserve iot here for later checks. +#endif + #ifndef SFX_MODULE if (Arc.Volume && !Arc.FirstVolume && !UseExactVolName) { @@ -318,12 +325,72 @@ #if !defined(SFX_MODULE) && !defined(RARDLL) if (Cmd->Test && Arc.Volume) RecVolumesTest(Cmd,&Arc,ArcName); + + // Delete archive if exit code is 0 and it is either a single archive + // or extraction started from first volume. + if (Cmd->DeleteArchive && !Cmd->Test && Cmd->Command[0]!='P' && + ErrHandler.GetErrorCode()==RARX_SUCCESS && (!Arc.Volume || FirstVolume)) + DeleteArchive(Arc,ArcName); #endif return EXTRACT_ARC_NEXT; } +#if !defined(SFX_MODULE) && !defined(RARDLL) +void CmdExtract::DeleteArchive(Archive &Arc,const std::wstring &ArcName) +{ + Arc.Close(); + + bool DelSuccess=false; + + std::wstring NextName=ArcName; // Use ArcName, because Arc.FileName can refer to non-first volume. + while (true) // First archive must exist, so we do not call FileExist(NextName) here. + { +#ifdef _WIN_ALL + if (Cmd->DeleteToRecycleBin) + DelSuccess=RecycleFile(NextName); + else + DelSuccess=DelFile(NextName); +#else + DelSuccess=DelFile(NextName); +#endif + uiMsg(UIEVENT_DELADDEDFILE,NextName,(int)DelSuccess,0); + if (!DelSuccess || !Arc.Volume) + break; + NextVolumeName(NextName,!Arc.NewNumbering); + if (!FileExist(NextName)) + break; + } + if (DelSuccess && Arc.Volume) + { + // Delete recovery volumes. RAR5 uses the width of name numeric field + // in REV same as in RAR, so it is enough to set .rev extension to get + // teh first REV name. + NextName=ArcName; + SetExt(NextName,L"rev"); + while (FileExist(NextName)) // It is ok for REV to be missing, check here. + { +#ifdef _WIN_ALL + if (Cmd->DeleteToRecycleBin) + DelSuccess=RecycleFile(NextName); + else + DelSuccess=DelFile(NextName); +#else + DelSuccess=DelFile(NextName); +#endif + uiMsg(UIEVENT_DELADDEDFILE,NextName,(int)DelSuccess,0); + if (!DelSuccess) + break; + NextVolumeName(NextName,!Arc.NewNumbering); + } + } + if (!DelSuccess) + ErrHandler.SetErrorCode(RARX_DELETE); +} +#endif + + bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) { wchar Command=Cmd->Command[0]; @@ -1035,11 +1102,16 @@ if (SetAttr) { + auto FileAttr=Arc.FileHead.FileAttr; #if defined(_WIN_ALL) && !defined(SFX_MODULE) if (Cmd->ClearArc) - Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; + FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; #endif - if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(DestFileName,Arc.FileHead.FileAttr)) +#ifdef _UNIX + if (geteuid()!=0) // Unless root. + FileAttr &= ~(S_ISUID|S_ISGID); // Strip SUID and SGID for standard users. +#endif + if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(DestFileName,FileAttr)) { uiMsg(UIERROR_FILEATTR,Arc.FileName,DestFileName); // Android cannot set file attributes and while UIERROR_FILEATTR @@ -1385,7 +1457,13 @@ return; } - MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + auto FileAttr=Arc.FileHead.FileAttr; +#ifdef _UNIX + if (geteuid()!=0) // Unless root. + FileAttr &= ~S_ISUID; // Strip SUID and keep SGID for standard users. +#endif + + MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,FileAttr); bool DirExist=false; if (MDCode!=MKDIR_SUCCESS) { @@ -1395,13 +1473,13 @@ // File with name same as this directory exists. Propose user // to overwrite it. bool UserReject; - FileCreate(Cmd,NULL,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + FileCreate(Cmd,nullptr,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,FILECR_FOLDER); DirExist=false; } if (!DirExist) { CreatePath(DestFileName,true,Cmd->DisableNames); - MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,FileAttr); if (MDCode!=MKDIR_SUCCESS && !IsNameUsable(DestFileName)) { uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); @@ -1415,7 +1493,7 @@ LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink))) { CreatePath(DestFileName,true,Cmd->DisableNames); - MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,FileAttr); } } } @@ -1427,13 +1505,21 @@ mprintf(St(MCreatDir),DestFileName.c_str()); mprintf(L" %s",St(MOk)); } +#ifdef _UNIX + // We need to set Unix attributes for DirExist as well, because Unix mkdir + // strips all attributes not in umask(). Archives created by recent + // RAR versions store directories after their contents, so risk of setting + // attributes, which prevent us creating files, is minimal. + if (!Cmd->IgnoreGeneralAttr) + SetFileAttr(DestFileName,FileAttr); +#endif PrevProcessed=true; } else if (DirExist) { if (!Cmd->IgnoreGeneralAttr) - SetFileAttr(DestFileName,Arc.FileHead.FileAttr); + SetFileAttr(DestFileName,FileAttr); PrevProcessed=true; } else @@ -1449,7 +1535,7 @@ { #if defined(_WIN_ALL) && !defined(SFX_MODULE) if (Cmd->SetCompressedAttr && - (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT()!=WNT_NONE) + (FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT()!=WNT_NONE) SetFileCompression(DestFileName,true); #endif SetFileHeaderExtra(Cmd,Arc,DestFileName); @@ -1471,8 +1557,9 @@ #endif if ((Command=='E' || Command=='X') && !Cmd->Test) { + FILECR_FLAGS Flags=WriteOnly ? FILECR_WRITEONLY:FILECR_DEFAULT; bool UserReject; - if (!FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,WriteOnly)) + if (!FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,Flags)) { Success=false; if (!UserReject) @@ -1496,7 +1583,7 @@ LinksToDirs(DestFileName,Cmd->ExtrPath,LastCheckedSymlink)) { CreatePath(DestFileName,true,Cmd->DisableNames); - if (FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + if (FileCreate(Cmd,&CurFile,DestFileName,&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,Flags)) { #ifndef SFX_MODULE uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/extract.hpp new/unrar/extract.hpp --- old/unrar/extract.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/extract.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -44,6 +44,9 @@ void GetFirstVolIfFullSet(const std::wstring &SrcName,bool NewNumbering,std::wstring &DestName); #endif bool CheckWinLimit(Archive &Arc,std::wstring &ArcFileName); +#if !defined(SFX_MODULE) && !defined(RARDLL) + void DeleteArchive(Archive &Arc,const std::wstring &ArcName); +#endif RarTime StartTime; // Time when extraction started. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/filcreat.cpp new/unrar/filcreat.cpp --- old/unrar/filcreat.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/filcreat.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -1,11 +1,13 @@ #include "rar.hpp" -// 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. +// If NewFile==NULL, we delete existing file or folder after user confirmation +// without creating a new folder. It is useful when user needs to confirm +// overwriting an existing file or folder to create a folder or link. bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name, - bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly) + bool *UserReject,int64 FileSize,RarTime *FileTime, + FILECR_FLAGS Flags) { + bool WriteOnly=(Flags & FILECR_WRITEONLY)!=0; if (UserReject!=NULL) *UserReject=false; #ifdef _WIN_ALL @@ -29,7 +31,12 @@ // autorename below can change the name, so we need to check it again. ShortNameChanged=false; #endif - UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0)); + + // Hide "Rename" button if source file isn't defined. + uint AskRepFlags=(NewFile==nullptr ? UIASKREP_F_NORENAME:0); + if ((Flags & FILECR_FOLDER)!=0) + AskRepFlags|=UIASKREP_F_SRCFOLDER; // Source is folder. + UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,FileSize,FileTime,(UIASKREP_FLAGS)AskRepFlags); if (Choice==UIASKREP_R_REPLACE) break; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/filcreat.hpp new/unrar/filcreat.hpp --- old/unrar/filcreat.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/filcreat.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -1,9 +1,16 @@ #ifndef _RAR_FILECREATE_ #define _RAR_FILECREATE_ +enum FILECR_FLAGS +{ + FILECR_DEFAULT = 0, // Default mode. + FILECR_WRITEONLY = 1, // Create a file in write only mode. + FILECR_FOLDER = 2, // Delete the existing file, so caller can create a folder. +}; + bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name, bool *UserReject,int64 FileSize=INT64NDF, - RarTime *FileTime=NULL,bool WriteOnly=false); + RarTime *FileTime=nullptr,FILECR_FLAGS Flags=FILECR_DEFAULT); #if defined(_WIN_ALL) bool UpdateExistingShortName(const std::wstring &Name); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/filefn.cpp new/unrar/filefn.cpp --- old/unrar/filefn.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/filefn.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -508,6 +508,19 @@ #if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool RecycleFile(std::wstring &Name) +{ + Name.push_back(L'\0'); // SHFileOperation expects the name to end with \0\0. + SHFILEOPSTRUCT fop{}; + fop.wFunc=FO_DELETE; + fop.fFlags=FOF_NOCONFIRMATION|FOF_SILENT|FOF_ALLOWUNDO; + fop.pFrom=Name.c_str(); + return SHFileOperation(&fop)==0; +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) bool SetFileCompression(const std::wstring &Name,bool State) { HANDLE hFile=CreateFile(Name.c_str(),FILE_READ_DATA|FILE_WRITE_DATA, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/filefn.hpp new/unrar/filefn.hpp --- old/unrar/filefn.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/filefn.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -42,6 +42,7 @@ bool DelDir(const std::wstring &Name); #if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool RecycleFile(std::wstring &Name); bool SetFileCompression(const std::wstring &Name,bool State); bool SetFileCompression(HANDLE hFile,bool State); void ResetFileCache(const std::wstring &Name); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/find.cpp new/unrar/find.cpp --- old/unrar/find.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/find.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -37,13 +37,9 @@ return false; #ifdef _WIN_ALL if (FirstCall) - { - if ((hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd))==INVALID_HANDLE_VALUE) - return false; - } - else - if (Win32Find(hFind,FindMask,fd)==INVALID_HANDLE_VALUE) - return false; + hFind=INVALID_HANDLE_VALUE; + if (!Win32Find(hFind,FindMask,fd)) + return false; #else if (FirstCall) { @@ -111,8 +107,8 @@ return false; #endif #ifdef _WIN_ALL - HANDLE hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd); - if (hFind==INVALID_HANDLE_VALUE) + HANDLE hFind=INVALID_HANDLE_VALUE; + if (!Win32Find(hFind,FindMask,fd)) return false; FindClose(hFind); #elif defined(_UNIX) @@ -154,10 +150,12 @@ #ifdef _WIN_ALL -HANDLE FindFile::Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd) +bool FindFile::Win32Find(HANDLE &hFind,const std::wstring &Mask,FindData *fd) { + fd->Flags=0; + WIN32_FIND_DATA FindData; - if (hFind==INVALID_HANDLE_VALUE) + if (hFind==INVALID_HANDLE_VALUE) // If first call. { hFind=FindFirstFile(Mask.c_str(),&FindData); if (hFind==INVALID_HANDLE_VALUE) @@ -177,32 +175,31 @@ fd->Error=SysErr!=ERROR_FILE_NOT_FOUND && SysErr!=ERROR_PATH_NOT_FOUND && SysErr!=ERROR_NO_MORE_FILES; + return false; } } else if (!FindNextFile(hFind,&FindData)) { + FindClose(hFind); hFind=INVALID_HANDLE_VALUE; fd->Error=GetLastError()!=ERROR_NO_MORE_FILES; + return false; } - if (hFind!=INVALID_HANDLE_VALUE) - { - fd->Name=Mask; - SetName(fd->Name,FindData.cFileName); - fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow); - fd->FileAttr=FindData.dwFileAttributes; - fd->ftCreationTime=FindData.ftCreationTime; - fd->ftLastAccessTime=FindData.ftLastAccessTime; - fd->ftLastWriteTime=FindData.ftLastWriteTime; - fd->mtime.SetWinFT(&FindData.ftLastWriteTime); - fd->ctime.SetWinFT(&FindData.ftCreationTime); - fd->atime.SetWinFT(&FindData.ftLastAccessTime); + fd->Name=Mask; + SetName(fd->Name,FindData.cFileName); + fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow); + fd->FileAttr=FindData.dwFileAttributes; + fd->ftCreationTime=FindData.ftCreationTime; + fd->ftLastAccessTime=FindData.ftLastAccessTime; + fd->ftLastWriteTime=FindData.ftLastWriteTime; + fd->mtime.SetWinFT(&FindData.ftLastWriteTime); + fd->ctime.SetWinFT(&FindData.ftCreationTime); + fd->atime.SetWinFT(&FindData.ftLastAccessTime); - } - fd->Flags=0; - return hFind; + return true; } #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/find.hpp new/unrar/find.hpp --- old/unrar/find.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/find.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -28,7 +28,7 @@ { private: #ifdef _WIN_ALL - static HANDLE Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd); + static bool Win32Find(HANDLE &hFind,const std::wstring &Mask,FindData *fd); #endif std::wstring FindMask; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/headers.hpp new/unrar/headers.hpp --- old/unrar/headers.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/headers.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -73,7 +73,8 @@ #define EARC_VOLNUMBER 0x0008U // Store a number of current volume. enum HEADER_TYPE { - // RAR 5.0 header types. + // RAR 5.0 header types. HEAD_UNKNOWN must be maximum value in enum, + // because we reject anything larger than it. HEAD_MARK=0x00, HEAD_MAIN=0x01, HEAD_FILE=0x02, HEAD_SERVICE=0x03, HEAD_CRYPT=0x04, HEAD_ENDARC=0x05, HEAD_UNKNOWN=0xff, @@ -106,9 +107,11 @@ // We also use these values in extra field, so do not modify them. +// FSREDIR_UNKNOWN must be maximum value in enum, because we reject +// anything larger than it. enum FILE_SYSTEM_REDIRECT { FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION, - FSREDIR_HARDLINK, FSREDIR_FILECOPY + FSREDIR_HARDLINK, FSREDIR_FILECOPY, FSREDIR_UNKNOWN }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/largepage.cpp new/unrar/largepage.cpp --- old/unrar/largepage.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/largepage.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -132,7 +132,11 @@ return false; PSID UserSid; - ConvertStringSidToSid(Sid.c_str(),&UserSid); + if (ConvertStringSidToSid(Sid.c_str(),&UserSid)==0) + { + LsaClose(PolicyHandle); + return false; + } LSA_UNICODE_STRING LsaString; LsaString.Buffer=(PWSTR)SE_LOCK_MEMORY_NAME; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/list.cpp new/unrar/list.cpp --- old/unrar/list.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/list.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -9,10 +9,18 @@ { int64 SumPackSize=0,SumUnpSize=0; uint ArcCount=0,SumFileCount=0; - bool Technical=(Cmd->Command[1]=='T'); - bool ShowService=Technical && Cmd->Command[2]=='A'; - bool Bare=(Cmd->Command[1]=='B'); - bool Verbose=(Cmd->Command[0]=='V'); + + bool Verbose=(Cmd->Command[0]=='V'); // "v" command. + + bool Technical=false; // "vt" or "lt" technical details. + bool Bare=false; // "vb" or "lb" bare output. + bool ShowService=false; // "va" or "vta" include service blocks. + for (wchar Ch : Cmd->Command) + { + Bare|=(Ch=='B'); + Technical|=(Ch=='T'); + ShowService|=(Ch=='A'); + } std::wstring ArcName; while (Cmd->GetArcName(ArcName)) @@ -118,13 +126,13 @@ // want to see service blocks only in this case. if (!Arc.SubHead.SubBlock || Cmd->DisableNames) FileMatched=Cmd->IsProcessFile(Arc.SubHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0; - if (FileMatched && !Bare) + if (FileMatched) { // Here we set DisableNames parameter to true regardless of // Cmd->DisableNames. If "vta -idn" are set together, user // wants to see service blocks like RR only. - if (Technical && ShowService) - ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false,false); + if (Technical || ShowService) + ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,Technical,Bare,false); } break; } @@ -230,9 +238,15 @@ const wchar *Name=hd.FileName.c_str(); RARFORMAT Format=Arc.Format; - if (Bare) + bool FileBlock=hd.HeaderType==HEAD_FILE; + + std::wstring StreamName; // NTFS stream name. + if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) + StreamName=GetStreamNameNTFS(Arc); + + if (Bare) // Bare "vb" list mode. { - mprintf(L"%s\n",Name); + mprintf(L"%s%s\n",Name,StreamName.c_str()); // Print file name and NTFS stream name, if present. return; } @@ -263,18 +277,18 @@ swprintf(RatioStr,ASIZE(RatioStr),L"%u%%",ToPercentUnlim(hd.PackSize,hd.UnpSize)); wchar DateStr[50]; - hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical); + if (hd.mtime.IsSet()) + hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical); + else + wcsncpyz(DateStr,L" ",ASIZE(DateStr)); if (Technical) { mprintf(L"\n%12s: %s",St(MListName),Name); - bool FileBlock=hd.HeaderType==HEAD_FILE; - - if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) + if (!StreamName.empty()) { mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream)); - std::wstring StreamName=GetStreamNameNTFS(Arc); mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName.c_str()); } else @@ -451,9 +465,12 @@ mprintf(L"%02x%02x..%02x ",S[0],S[1],S[31]); } else - mprintf(hd.Dir ? L" ":L"???????? "); // Missing checksum is ok for folder, not for file. + mprintf(L" "); // Missing checksum, such as for folders or service blocks. } mprintf(L"%ls",Name); + + if (!StreamName.empty()) // Print NTFS stream name in non-technical "va" list mode. + mprintf(L"%ls",StreamName.c_str()); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/loclang.hpp new/unrar/loclang.hpp --- old/unrar/loclang.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/loclang.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -57,7 +57,7 @@ #define MCHelpCmdS L"\n s[name|-] Convert archive to or from SFX" #define MCHelpCmdT L"\n t Test archive files" #define MCHelpCmdU L"\n u Update files in archive" -#define MCHelpCmdV L"\n v[t[a],b] Verbosely list archive contents [technical[all],bare]" +#define MCHelpCmdV L"\n v[a,b,t] Verbosely list archive contents [all,bare,technical]" #define MCHelpCmdX L"\n x Extract files with full path" #define MCHelpSw L"\n\n<Switches>" #define MCHelpSwm L"\n - Stop switches scanning" @@ -74,13 +74,14 @@ #define MCHelpSwCFGm L"\n cfg- Disable read configuration" #define MCHelpSwCL L"\n cl Convert names to lower case" #define MCHelpSwCU L"\n cu Convert names to upper case" +#define MCHelpSwDA L"\n da Delete archive after extraction" #define MCHelpSwDF L"\n df Delete files after archiving" #define MCHelpSwDH L"\n dh Open shared files" #define MCHelpSwDR L"\n dr Delete files to Recycle Bin" #define MCHelpSwDS L"\n ds Disable name sort for solid archive" #define MCHelpSwDW L"\n dw Wipe files after archiving" #define MCHelpSwEa L"\n e[+]<attr> Set file exclude and include attributes" -#define MCHelpSwED L"\n ed Do not add empty directories" +#define MCHelpSwED L"\n ed[1] Exclude directories [empty only]" #define MCHelpSwEP L"\n ep Exclude paths from names" #define MCHelpSwEP1 L"\n ep1 Exclude base directory from names" #define MCHelpSwEP2 L"\n ep2 Expand paths to full" @@ -145,7 +146,7 @@ #define MCHelpSwTS L"\n ts[m,c,a,p] Save or restore time (modification, creation, access, preserve)" #define MCHelpSwU L"\n u Update files" #define MCHelpSwV L"\n v Create volumes with size autodetection or list all volumes" -#define MCHelpSwVUnr L"\n v List all volumes" +#define MCHelpSwVUnr L"\n v Apply list command to all volumes" #define MCHelpSwVn L"\n v<size>[u] Create volumes with size in [bBkKmMgGtT] units" #define MCHelpSwVD L"\n vd Erase disk contents before creating volume" #define MCHelpSwVER L"\n ver[n] File version control" @@ -350,6 +351,7 @@ #define MNTFSRequired L"\nWrite error: only NTFS file system supports files larger than 4 GB" #define MFAT32Size L"\nWARNING: FAT32 file system does not support 4 GB or larger files" #define MErrChangeAttr L"\nWARNING: Cannot change attributes of %s" +#define MErrChangePerm L"\nWARNING: Cannot change permissions of %s" #define MWrongSFXVer L"\nERROR: default SFX module does not support RAR %d.%d archives" #define MHeadEncMismatch L"\nCannot change the header encryption mode in already encrypted archive" #define MCannotEmail L"\nCannot email the file %s" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/match.cpp new/unrar/match.cpp --- old/unrar/match.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/match.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -23,8 +23,8 @@ CmpMode&=MATCH_MODEMASK; - wchar *Name1=PointToName(Wildcard); - wchar *Name2=PointToName(Name); + const wchar *Name1=PointToName(Wildcard); + const wchar *Name2=PointToName(Name); if (CmpMode!=MATCH_NAMES) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/model.cpp new/unrar/model.cpp --- old/unrar/model.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/model.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -489,6 +489,8 @@ inline bool RARPPM_CONTEXT::decodeSymbol2(ModelPPM *Model) { + if (Model->NumMasked > NumStats) // 2026.07.24: extra security check. + return false; int count, HiCnt, i=NumStats-Model->NumMasked; RARPPM_SEE2_CONTEXT* psee2c=makeEscFreq2(Model,i); RARPPM_STATE* ps[256], ** pps=ps, * p=U.Stats-1; @@ -498,6 +500,12 @@ do { p++; + + // 2026.07.24: Ensure we don't read beyond the state array bounds. + // This check might be excessive, but we keep it for extra safety. + if (p >= U.Stats + NumStats) + return false; // 'p' is at or past end of valid states. + } while (Model->CharMask[p->Symbol] == Model->EscCount); HiCnt += p->Freq; @@ -511,7 +519,7 @@ Model->Coder.SubRange.scale += HiCnt; count=Model->Coder.GetCurrentCount(); if (count>=(int)Model->Coder.SubRange.scale) - return(false); + return false; p=*(pps=ps); if (count < HiCnt) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/motw.cpp new/unrar/motw.cpp --- old/unrar/motw.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/motw.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -57,14 +57,21 @@ int MarkOfTheWeb::ParseZoneIdStream(std::string &Stream) { if (!starts_with(Stream,"[ZoneTransfer]")) - return -1; // Not a valid Mark of the Web. Prefer the archive MOTW if any. + return -1; // Not a valid Mark of the Web. Prefer the archive MOTW, if any. std::string::size_type ZoneId=Stream.find("ZoneId=",0); if (ZoneId==std::string::npos || !IsDigit(Stream[ZoneId+7])) return -1; // Not a valid Mark of the Web. + char E=Stream[ZoneId+8]; + if (E!=0 && E!=' ' && E!='\t' && E!='\r' && E!='\n') + return -1; // Not a valid Mark of the Web. + int ZoneIdValue=atoi(&Stream[ZoneId+7]); if (ZoneIdValue<0 || ZoneIdValue>4) return -1; // Not a valid Mark of the Web. + std::string::size_type ZoneId2=Stream.find("ZoneId=",ZoneId+8); + if (ZoneId2!=std::string::npos) + return -1; // Not a valid Mark of the Web. if (!AllFields) Stream="[ZoneTransfer]\r\nZoneId=" + std::to_string(ZoneIdValue) + "\r\n"; @@ -126,7 +133,8 @@ // Return true and prepare the file stream to write if its ZoneId is stricter -// than archive ZoneId. If it is missing, less or equally strict, return false. +// than archive ZoneId. If it is missing, broken, less or equally strict, +// return false and prefer an archive MOTW. bool MarkOfTheWeb::IsFileStreamMoreSecure(std::string &FileStream) { int StreamZone=ParseZoneIdStream(FileStream); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/motw.hpp new/unrar/motw.hpp --- old/unrar/motw.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/motw.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -4,7 +4,7 @@ class MarkOfTheWeb { private: - const size_t MOTW_STREAM_MAX_SIZE=1024; + const size_t MOTW_STREAM_MAX_SIZE=4096; const wchar* MOTW_STREAM_NAME=L":Zone.Identifier"; // Must start from ':'. int ParseZoneIdStream(std::string &Stream); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/options.cpp new/unrar/options.cpp --- old/unrar/options.cpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/options.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -1,14 +1,9 @@ #include "rar.hpp" -RAROptions::RAROptions() -{ - Init(); -} - - +// Set all non-default values here. +// Default values are set from CommandData::Init(). void RAROptions::Init() { - memset(this,0,sizeof(RAROptions)); WinSize=0x2000000; WinSizeLimit=0x100000000; Overwrite=OVERWRITE_DEFAULT; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/options.hpp new/unrar/options.hpp --- old/unrar/options.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/options.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -15,6 +15,15 @@ EXCL_ABSPATH // -ep3 (the full path with the disk letter) }; + +enum DIR_FILTER_MODE +{ + DIRFM_INCLUDE_ALL=0, // Include all directories (default). + DIRFM_EXCLUDE_ALL, // -ed, exclude all directories. + DIRFM_EXCLUDE_EMPTY, // -ed1, exclude empty directories. + DIRFM_DIR_ONLY // -e+d add directories only, skip files. +}; + enum { SOLID_NONE=0, // Non-solid mode. SOLID_NORMAL=1, // Standard solid mode. @@ -104,22 +113,22 @@ #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. +// Store here those types, which we want to initialize all at once +// from CommandData::Init(). Initializing them from own constructor using +// *this={} falls into infinite recursion and we do not want to use memset +// assuming POD types only, so we utilize the derived CommandData class. class RAROptions { public: - RAROptions(); void Init(); uint ExclFileAttr; uint InclFileAttr; - // We handle -ed and -e+d with special flags instead of attribute mask, - // so it works with both Windows and Unix archives. - bool ExclDir; - bool InclDir; + // We handle -ed and -e+d with the dedicated variable instead of attribute + // mask, so it works with both Windows and Unix archives. + DIR_FILTER_MODE DirMode; bool InclAttrSet; uint64 WinSize; @@ -186,9 +195,10 @@ bool KeepBroken; bool OpenShared; - bool DeleteFiles; + bool DeleteArchive; #ifdef _WIN_ALL + bool DeleteToRecycleBin; bool AllowIncompatNames; // Allow names with trailing dots and spaces. #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/os.hpp new/unrar/os.hpp --- old/unrar/os.hpp 2026-06-27 13:35:31.000000000 +0200 +++ new/unrar/os.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -38,8 +38,6 @@ #define _UNICODE // Set _T() macro to convert from narrow to wide strings. #endif -#define WINVER _WIN32_WINNT_WINXP -#define _WIN32_WINNT _WIN32_WINNT_WINXP #if !defined(ZIPSFX) #define RAR_SMP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/pathfn.cpp new/unrar/pathfn.cpp --- old/unrar/pathfn.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/pathfn.cpp 2026-09-09 15:11:10.000000000 +0200 @@ -1,11 +1,11 @@ #include "rar.hpp" -wchar* PointToName(const wchar *Path) +const wchar* PointToName(const wchar *Path) { for (int I=(int)wcslen(Path)-1;I>=0;I--) if (IsPathDiv(Path[I])) return (wchar*)&Path[I+1]; - return (wchar*)((*Path!=0 && IsDriveDiv(Path[1])) ? Path+2:Path); + return *Path!=0 && IsDriveDiv(Path[1]) ? Path+2:Path; } @@ -126,7 +126,7 @@ // 'Ext' is an extension with the leading dot, like L".rar". -wchar *GetExt(const wchar *Name) +const wchar *GetExt(const wchar *Name) { return Name==NULL ? NULL:wcsrchr(PointToName(Name),'.'); } @@ -281,9 +281,15 @@ #if defined(_WIN_ALL) bool SHGetPathStrFromIDList(PCIDLIST_ABSOLUTE pidl,std::wstring &Path) { - std::vector<wchar> Buf(MAX_PATH); - bool Success=SHGetPathFromIDList(pidl,Buf.data())!=FALSE; - Path=Buf.data(); + // Allocate size enough for both SHGetPathFromIDList and its *Ex version. + std::vector<wchar> Buf(Max(MAXPATHSIZE,MAX_PATH)); +#if _WIN32_WINNT >= _WIN32_WINNT_VISTA // Vista+. + bool Success=SHGetPathFromIDListEx(pidl,Buf.data(),(DWORD)Buf.size(),0)!=FALSE; +#else + bool Success=SHGetPathFromIDList(pidl,Buf.data())!=FALSE; // XP, limited to 260 chars. +#endif + if (Success) + Path=Buf.data(); return Success; } #endif @@ -306,8 +312,8 @@ std::vector<wchar> PathBuf(DataSize/sizeof(wchar)); RegQueryValueEx(hKey,L"AppData",0,NULL,(BYTE *)PathBuf.data(),&DataSize); Path=PathBuf.data(); - RegCloseKey(hKey); } + RegCloseKey(hKey); } if (Path.empty() || !FileExist(Path)) @@ -1192,7 +1198,7 @@ { HMODULE hModule=nullptr; - std::vector<wchar> Path(256); + std::vector<wchar> Path(MAX_PATH); while (Path.size()<=MAXPATHSIZE) { if (GetModuleFileName(hModule,Path.data(),(DWORD)Path.size())<Path.size()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/pathfn.hpp new/unrar/pathfn.hpp --- old/unrar/pathfn.hpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/pathfn.hpp 2026-09-09 15:11:10.000000000 +0200 @@ -1,7 +1,7 @@ #ifndef _RAR_PATHFN_ #define _RAR_PATHFN_ -wchar* PointToName(const wchar *Path); +const wchar* PointToName(const wchar *Path); std::wstring PointToName(const std::wstring &Path); size_t GetNamePos(const std::wstring &Path); wchar* PointToLastChar(const wchar *Path); @@ -11,7 +11,7 @@ void SetExt(std::wstring &Name,std::wstring NewExt); void RemoveExt(std::wstring &Name); void SetSFXExt(std::wstring &SFXName); -wchar *GetExt(const wchar *Name); +const wchar *GetExt(const wchar *Name); std::wstring GetExt(const std::wstring &Name); std::wstring::size_type GetExtPos(const std::wstring &Name); bool CmpExt(const std::wstring &Name,const std::wstring &Ext); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/rar.cpp new/unrar/rar.cpp --- old/unrar/rar.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/rar.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -59,7 +59,7 @@ } Cmd->AddArcName(ModuleName); Cmd->ParseDone(); - Cmd->AbsoluteLinks=true; // If users runs SFX, he trusts an archive source. + Cmd->AbsoluteLinks=true; // If users run SFX, they trust an archive source. #else // !SFX_MODULE Cmd->ParseCommandLine(true,argc,argv); if (!Cmd->ConfigDisabled) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/rawread.cpp new/unrar/rawread.cpp --- old/unrar/rawread.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/rawread.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -191,11 +191,11 @@ // Read vint from arbitrary byte array. -uint64 RawGetV(const byte *Data,uint &ReadPos,uint DataSize,bool &Overflow) +uint64 RawGetV(const byte *Data,uint &ReadPos,uint EndPos,bool &Overflow) { Overflow=false; uint64 Result=0; - for (uint Shift=0;ReadPos<DataSize;Shift+=7) + for (uint Shift=0;ReadPos<EndPos;Shift+=7) { byte CurByte=Data[ReadPos++]; Result+=uint64(CurByte & 0x7f)<<Shift; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/recvol3.cpp new/unrar/recvol3.cpp --- old/unrar/recvol3.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/recvol3.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -122,6 +122,9 @@ RecVolMask.replace(VolNumStart,std::wstring::npos,L"*.rev"); size_t BaseNamePartLength=VolNumStart; + if (BaseNamePartLength==0) + return false; + int64 RecFileSize=0; // We cannot display "Calculating CRC..." message here, because we do not @@ -171,6 +174,13 @@ CurFile.TOpen(CurName); CurFile.Seek(0,SEEK_END); int64 Length=CurFile.Tell(); + + if (Length<7) // Too small to be valid, skip to avoid seek error below. + { + uiMsg(UIERROR_NOTVOLUME,CurName); + continue; + } + CurFile.Seek(Length-7,SEEK_SET); for (int I=0;I<3;I++) P[2-I]=CurFile.GetByte()+1; @@ -221,7 +231,19 @@ // checks above. Still we keep it here for better clarity and security. int SrcPos=FileNumber+P[0]-1; if (SrcPos<0 || SrcPos>=ASIZE(SrcFile)) + { + delete NewFile; continue; + } + + // Such volume number was already used. Skip to avoid memory leak. + // Alternatively we could 'delete SrcFile[SrcPos]' and overwrite. + if (SrcFile[SrcPos]!=nullptr) + { + delete NewFile; + continue; + } + SrcFile[SrcPos]=NewFile; FoundRecVolumes++; @@ -296,6 +318,8 @@ uiMsg(UIERROR_RECVOLFOUND,FoundRecVolumes); // Intentionally not displayed in console mode. uiMsg(UIERROR_RECONSTRUCTING); ErrHandler.CreateErrorMsg(ArcName); + + delete NewFile; return false; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/recvol5.cpp new/unrar/recvol5.cpp --- old/unrar/recvol5.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/recvol5.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -155,7 +155,7 @@ std::wstring FirstVolName; std::wstring LongestRevName; - int64 RecFileSize=0; + uint64 RecFileSize=0,FirstVolSize=0; FindFile VolFind; VolFind.SetMask(ArcName); @@ -206,6 +206,9 @@ VolNum+=(fd.Name[NumPos]-'0')*K; if (VolNum==0 || VolNum>MaxVolumes) continue; + if (FirstVolSize==0) + FirstVolSize=Vol->FileLength(); + size_t CurSize=RecItems.size(); if (VolNum>CurSize) { @@ -219,17 +222,19 @@ VolNameToFirstName(fd.Name,FirstVolName,true); } } - if (ItemPos==-1) - delete Vol; // Skip found file, it is not RAR or REV volume. + + // Skip found file if it is not RAR or REV volume. Also skip if found + // more REV volumes than needed. + if (ItemPos==-1 || (size_t)ItemPos>=RecItems.size()) + delete Vol; else - if ((uint)ItemPos<RecItems.size()) // Check if found more REV than needed. - { - // Store found RAR or REV volume. - RecVolItem *Item=&RecItems[ItemPos]; - Item->f=Vol; - Item->New=false; - Item->Name=fd.Name; - } + { + // Store found RAR or REV volume. + RecVolItem *Item=&RecItems[ItemPos]; + Item->f=Vol; + Item->New=false; + Item->Name=fd.Name; + } } if (!Silent || FoundRecVolumes!=0) @@ -328,7 +333,7 @@ ErrHandler.CreateErrorMsg(Item->Name); ErrHandler.Exit(UserReject ? RARX_USERBREAK:RARX_CREATE); } - NewVol->Prealloc(Item->FileSize); + NewVol->Prealloc(Min(Item->FileSize,FirstVolSize)); Item->f=NewVol; } NextVolumeName(FirstVolName,false); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/rijndael.cpp new/unrar/rijndael.cpp --- old/unrar/rijndael.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/rijndael.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -82,8 +82,12 @@ Rijndael::Rijndael() { - if (S5[0]==0) + static bool TablesInitialized=false; + if (!TablesInitialized) + { GenerateTables(); + TablesInitialized=true; + } m_uRounds = 0; CBCMode = true; // Always true for RAR. #ifdef USE_SSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/rs.cpp new/unrar/rs.cpp --- old/unrar/rs.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/rs.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -14,6 +14,7 @@ // Initialize logarithms and exponents Galois field tables. void RSCoder::gfInit() { + gfLog[0]=0; // Just in case, likely excessive, because log(0) is undefined. for (int I=0,J=1;I<MAXPAR;I++) { gfLog[J]=I; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/rs16.cpp new/unrar/rs16.cpp --- old/unrar/rs16.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/rs16.cpp 2026-09-09 15:11:11.000000000 +0200 @@ -258,11 +258,14 @@ #endif -// We update ECC in blocks by applying every data block to all ECC blocks. -// This function applies one data block to one ECC block. +// We write recovery data over former ECC blocks by applying every data block +// to each ECC block. This function applies one data block to one ECC block. void RSCoder16::UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize) { - if (DataNum==0) // Init ECC data. + // Init former ECC data before processing the first data block. + // We use this former ECC as output buffer and we copied its actual + // ECC contents over invalid data block before that. + if (DataNum==0) memset(ECC, 0, BlockSize); bool DirectAccess; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/ui.hpp new/unrar/ui.hpp --- old/unrar/ui.hpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/ui.hpp 2026-09-09 15:11:12.000000000 +0200 @@ -68,8 +68,11 @@ // Flags for uiAskReplace function. enum UIASKREP_FLAGS { - UIASKREP_F_NORENAME=1,UIASKREP_F_EXCHSRCDEST=2,UIASKREP_F_SHOWNAMEONLY=4, - UIASKREP_F_SINGLEFILE=8 + UIASKREP_F_NORENAME = 0x01, // Hide "Rename" button. + UIASKREP_F_EXCHSRCDEST = 0x02, // Exchange source and destination file metadata. + UIASKREP_F_SHOWNAMEONLY = 0x04, // Hide source file path. + UIASKREP_F_SINGLEFILE = 0x08, // Single file is processed, hide "Yes/No to All" buttons. + UIASKREP_F_SRCFOLDER = 0x10 // Source is a folder. }; // Codes returned by uiAskReplace. Note that uiAskReplaceEx returns only diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/uicommon.cpp new/unrar/uicommon.cpp --- old/unrar/uicommon.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/uicommon.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -29,12 +29,18 @@ // target attributes, so we can delete the symlink below. PrepareToDelete(Name); - // Overwrite the link itself instead of its target. - // For normal files we prefer to inherit file attributes, permissions - // and hard links. FindData FD; - if (FindFile::FastFind(Name,&FD,true) && FD.IsLink) - DelFile(Name); + if (FindFile::FastFind(Name,&FD,true)) + { + // Overwrite the link itself instead of its target. + // For normal files we prefer to inherit file attributes, permissions + // and hard links. + if (FD.IsLink) + DelFile(Name); + else + if (FD.IsDir) + DelDir(Name); // To let overwriting a directory with a file. + } } if (Choice==UIASKREP_R_REPLACEALL) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/uiconsole.cpp new/unrar/uiconsole.cpp --- old/unrar/uiconsole.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/uiconsole.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -154,7 +154,11 @@ break; #endif case UIERROR_FILEATTR: +#ifdef _UNIX + Log(Str[0],St(MErrChangePerm),Str[1]); +#else Log(Str[0],St(MErrChangeAttr),Str[1]); +#endif break; case UIERROR_FILECOPY: Log(Str[0],St(MCopyError),Str[1],Str[2]); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/ulinks.cpp new/unrar/ulinks.cpp --- old/unrar/ulinks.cpp 2026-06-27 13:35:32.000000000 +0200 +++ new/unrar/ulinks.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -78,7 +78,7 @@ size_t DataSize=(size_t)Arc.FileHead.PackSize; if (DataSize>MAXPATHSIZE) return false; - std::vector<char> TargetBuf(DataSize+1); + std::vector<char> TargetBuf(DataSize); if ((size_t)DataIO.UnpRead((byte*)TargetBuf.data(),DataSize)!=DataSize) return false; std::string Target(TargetBuf.data(),TargetBuf.size()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/version.hpp new/unrar/version.hpp --- old/unrar/version.hpp 2026-06-27 13:35:33.000000000 +0200 +++ new/unrar/version.hpp 2026-09-09 15:11:12.000000000 +0200 @@ -1,6 +1,6 @@ #define RARVER_MAJOR 7 -#define RARVER_MINOR 23 -#define RARVER_BETA 0 -#define RARVER_DAY 27 -#define RARVER_MONTH 6 +#define RARVER_MINOR 30 +#define RARVER_BETA 1 +#define RARVER_DAY 9 +#define RARVER_MONTH 9 #define RARVER_YEAR 2026 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/win32acl.cpp new/unrar/win32acl.cpp --- old/unrar/win32acl.cpp 2026-06-27 13:35:33.000000000 +0200 +++ new/unrar/win32acl.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -9,7 +9,8 @@ { SetACLPrivileges(); - if (Arc.BrokenHeader) + constexpr size_t MAX_ACL_SIZE = 0x100000; + if (Arc.BrokenHeader || Arc.EAHead.UnpSize>MAX_ACL_SIZE) { uiMsg(UIERROR_ACLBROKEN,Arc.FileName,FileName); ErrHandler.SetErrorCode(RARX_CRC); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/win32lnk.cpp new/unrar/win32lnk.cpp --- old/unrar/win32lnk.cpp 2026-06-27 13:35:33.000000000 +0200 +++ new/unrar/win32lnk.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -50,7 +50,7 @@ std::vector<byte> Buf(BufSize); REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)Buf.data(); - // Remove \??\ NTFS junction prefix of present. + // Remove \??\ NTFS junction prefix if present. bool WinPrefix=starts_with(SubstName,L"\\??\\"); std::wstring PrintName=WinPrefix ? SubstName.substr(4):SubstName; @@ -60,12 +60,20 @@ size_t PrintLength=PrintName.size(); bool AbsPath=WinPrefix; - // IsFullPath is not really needed here, AbsPath check is enough. + // IsFullPath is not really needed here for symlinks, AbsPath check is enough. // We added it just for extra safety, in case some Windows version would // allow to create absolute targets with SYMLINK_FLAG_RELATIVE. + // Junction points are either catched by \??\ or fail to follow the target + // once created, if \??\ was manually stripped from path. // Use hd->FileName instead of Name, since Name can include the destination // path as a prefix, which can confuse IsRelativeSymlinkSafe algorithm. + // 2026.08.19: We added FSREDIR_JUNCTION check, because normally junctions + // are always absolute, even if they pretent to not be. But this check is + // excessive and we keep it just in case here. If junction is absolute, + // it will be catched by other checks below. If junction isn't absolute, + // it will not follow the target path once created. if (!Cmd->AbsoluteLinks && (AbsPath || IsFullPath(hd->RedirName) || + hd->RedirType==FSREDIR_JUNCTION || !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName))) { uiMsg(UIERROR_SKIPUNSAFELINK,hd->FileName,hd->RedirName); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/unrar/win32stm.cpp new/unrar/win32stm.cpp --- old/unrar/win32stm.cpp 2026-06-27 13:35:33.000000000 +0200 +++ new/unrar/win32stm.cpp 2026-09-09 15:11:12.000000000 +0200 @@ -125,7 +125,7 @@ #ifdef _WIN_ALL -void ExtractStreams(Archive &Arc,const std::wstring &FileName,bool TestMode) +void ExtractStreams(CommandData *Cmd,Archive &Arc,const std::wstring &FileName) { std::wstring StreamName=GetStreamNameNTFS(Arc); @@ -136,10 +136,16 @@ return; } - if (TestMode) + if (Cmd->Test) { + if (!Cmd->DisableNames) + mprintf(St(MExtrTestFile),(FileName+StreamName).c_str()); File CurFile; - Arc.ReadSubData(nullptr,&CurFile,true); + if (Arc.ReadSubData(nullptr,&CurFile,true) && !Cmd->DisableNames && + !Cmd->DisablePercentage) + { + mprintf(L" %s",St(MOk)); + } return; } @@ -187,6 +193,10 @@ if (CurFile.WCreate(FullName)) { + CurFile.SetAllowDelete(!Cmd->KeepBroken); + + if (!Cmd->DisableNames) + mprintf(St(MExtrFile),FullName.c_str()); #ifdef PROPAGATE_MOTW if (!ParsedMotw.empty()) { @@ -198,10 +208,15 @@ else #endif if (Arc.ReadSubData(nullptr,&CurFile,false)) + { CurFile.Close(); + if (!Cmd->DisableNames && !Cmd->DisablePercentage) + mprintf(L" %s",St(MOk)); + } } - // Restoring original file timestamps. + // Restoring original file timestamps. Note that NTFS ADS share file + // attributes and times with the main file. File HostFile; if (HostFound && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE)) SetFileTime(HostFile.GetHandle(),&FD.ftCreationTime,&FD.ftLastAccessTime,
