diff --git a/.gitignore b/.gitignore index 2e0b14fa..cc539531 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ tests/TestArchives/Scratch tools .vscode .idea/ + +.DS_Store diff --git a/reference/unrar/UnRAR.vcxproj b/reference/unrar/UnRAR.vcxproj new file mode 100644 index 00000000..b55873e3 --- /dev/null +++ b/reference/unrar/UnRAR.vcxproj @@ -0,0 +1,279 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {95CC809B-03FC-4EDB-BB20-FD07A698C05F} + UnRAR + Win32Proj + 8.1 + + + + Application + v140_xp + MultiByte + true + + + Application + v140_xp + MultiByte + + + Application + v140_xp + MultiByte + false + + + Application + v140_xp + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + build\unrar32\$(Configuration)\ + build\unrar32\$(Configuration)\obj\ + true + false + + + build\unrar64\$(Configuration)\ + build\unrar64\$(Configuration)\obj\ + true + false + + + build\unrar32\$(Configuration)\ + build\unrar32\$(Configuration)\obj\ + false + false + + + build\unrar64\$(Configuration)\ + build\unrar64\$(Configuration)\obj\ + false + false + + + + /MP %(AdditionalOptions) + Disabled + UNRAR;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NoExtensions + + + true + Console + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + Disabled + UNRAR;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + true + Console + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + UNRAR;%(PreprocessorDefinitions) + false + MultiThreaded + Default + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + + + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MinSpace + true + Neither + true + false + UNRAR;%(PreprocessorDefinitions) + false + false + MultiThreaded + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + true + Console + true + true + + MachineX64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/unrar/UnRARDll.vcxproj b/reference/unrar/UnRARDll.vcxproj new file mode 100644 index 00000000..ecac0db0 --- /dev/null +++ b/reference/unrar/UnRARDll.vcxproj @@ -0,0 +1,420 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + release_nocrypt + Win32 + + + release_nocrypt + x64 + + + Release + Win32 + + + Release + x64 + + + + UnRAR + {E815C46C-36C4-499F-BBC2-E772C6B17971} + UnRAR + Win32Proj + 8.1 + + + + DynamicLibrary + v140_xp + MultiByte + true + + + DynamicLibrary + v140_xp + MultiByte + true + + + DynamicLibrary + v140_xp + MultiByte + + + DynamicLibrary + v140_xp + MultiByte + false + + + DynamicLibrary + v140_xp + MultiByte + false + + + DynamicLibrary + v140_xp + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + true + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + true + true + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + false + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + false + true + + + build\unrardll32\$(Configuration)\ + build\unrardll32\$(Configuration)\obj\ + false + true + + + build\unrardll64\$(Configuration)\ + build\unrardll64\$(Configuration)\obj\ + false + true + + + + /MP %(AdditionalOptions) + Disabled + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + EnableFastChecks + MultiThreadedDebug + 4Bytes + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NoExtensions + + + $(OutDir)unrar.dll + dll.def + true + Console + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + Disabled + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + EnableFastChecks + MultiThreadedDebug + 4Bytes + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll.def + true + Console + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + Sync + MultiThreaded + 4Bytes + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + + + /SAFESEH %(AdditionalOptions) + $(OutDir)unrar.dll + dll.def + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions) + false + false + Sync + MultiThreaded + 4Bytes + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll.def + true + Console + true + true + + MachineX64 + + + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions) + false + Sync + MultiThreaded + 4Bytes + true + true + NoExtensions + Precise + false + Use + rar.hpp + Level3 + ProgramDatabase + Cdecl + 4007;4996;%(DisableSpecificWarnings) + + + /SAFESEH %(AdditionalOptions) + $(OutDir)unrar.dll + dll_nocrypt.def + true + Console + true + true + + MachineX86 + + + + + X64 + + + /MP %(AdditionalOptions) + MaxSpeed + true + Neither + true + false + RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions) + false + false + Sync + MultiThreaded + 4Bytes + true + true + false + Use + rar.hpp + Level3 + ProgramDatabase + StdCall + 4007;4996;%(DisableSpecificWarnings) + NotSet + + + $(OutDir)unrar.dll + dll_nocrypt.def + true + Console + true + true + + MachineX64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/unrar/acknow.txt b/reference/unrar/acknow.txt new file mode 100644 index 00000000..b982ecee --- /dev/null +++ b/reference/unrar/acknow.txt @@ -0,0 +1,92 @@ + ACKNOWLEDGMENTS + +* We used "Screaming Fast Galois Field Arithmetic Using Intel + SIMD Instructions" paper by James S. Plank, Kevin M. Greenan + and Ethan L. Miller to improve Reed-Solomon coding performance. + Also we are grateful to Artem Drobanov and Bulat Ziganshin + for samples and ideas allowed to make Reed-Solomon coding + more efficient. + +* RAR text compression algorithm is based on Dmitry Shkarin PPMII + and Dmitry Subbotin carryless rangecoder public domain source code. + You may find it in ftp.elf.stuba.sk/pub/pc/pack. + +* RAR encryption includes parts of code from Szymon Stefanek + and Brian Gladman AES implementations also as Steve Reid SHA-1 source. + + --------------------------------------------------------------------------- + Copyright (c) 2002, Dr Brian Gladman < >, Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + + Source code of this package also as other cryptographic technology + and computing project related links are available on Brian Gladman's + web site: http://www.gladman.me.uk + +* RAR uses CRC32 function based on Intel Slicing-by-8 algorithm. + Original Intel Slicing-by-8 code is available here: + + http://sourceforge.net/projects/slicing-by-8/ + + Original Intel Slicing-by-8 code is licensed under BSD License + available at http://www.opensource.org/licenses/bsd-license.html + + Copyright (c) 2004-2006 Intel Corporation. + All Rights Reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with + the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +* RAR archives may optionally include BLAKE2sp hash ( https://blake2.net ), + designed by Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn + and Christian Winnerlein. + +* Useful hints provided by Alexander Khoroshev and Bulat Ziganshin allowed + to significantly improve RAR compression and speed. diff --git a/reference/unrar/arccmt.cpp b/reference/unrar/arccmt.cpp new file mode 100644 index 00000000..79963b27 --- /dev/null +++ b/reference/unrar/arccmt.cpp @@ -0,0 +1,166 @@ +static bool IsAnsiEscComment(const wchar *Data,size_t Size); + +bool Archive::GetComment(Array *CmtData) +{ + if (!MainComment) + return false; + SaveFilePos SavePos(*this); + +#ifndef SFX_MODULE + uint CmtLength; + if (Format==RARFMT14) + { + Seek(SFXSize+SIZEOF_MAINHEAD14,SEEK_SET); + CmtLength=GetByte(); + CmtLength+=(GetByte()<<8); + } + else +#endif + { + if (MainHead.CommentInHeader) + { + // Old style (RAR 2.9) archive comment embedded into the main + // archive header. + Seek(SFXSize+SIZEOF_MARKHEAD3+SIZEOF_MAINHEAD3,SEEK_SET); + ReadHeader(); + } + else + { + // Current (RAR 3.0+) version of archive comment. + Seek(GetStartPos(),SEEK_SET); + return SearchSubBlock(SUBHEAD_TYPE_CMT)!=0 && ReadCommentData(CmtData); + } +#ifndef SFX_MODULE + // Old style (RAR 2.9) comment header embedded into the main + // archive header. + if (BrokenHeader) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + CmtLength=CommHead.HeadSize-SIZEOF_COMMHEAD; +#endif + } +#ifndef SFX_MODULE + if (Format==RARFMT14 && MainHead.PackComment || Format!=RARFMT14 && CommHead.Method!=0x30) + { + if (Format!=RARFMT14 && (CommHead.UnpVer < 15 || CommHead.UnpVer > VER_UNPACK || CommHead.Method > 0x35)) + return false; + ComprDataIO DataIO; + DataIO.SetTestMode(true); + uint UnpCmtLength; + if (Format==RARFMT14) + { +#ifdef RAR_NOCRYPT + return false; +#else + UnpCmtLength=GetByte(); + UnpCmtLength+=(GetByte()<<8); + CmtLength-=2; + DataIO.SetCmt13Encryption(); + CommHead.UnpVer=15; +#endif + } + else + UnpCmtLength=CommHead.UnpSize; + DataIO.SetFiles(this,NULL); + DataIO.EnableShowProgress(false); + DataIO.SetPackedSizeToRead(CmtLength); + DataIO.UnpHash.Init(HASH_CRC32,1); + + Unpack CmtUnpack(&DataIO); + CmtUnpack.Init(0x10000,false); + CmtUnpack.SetDestSize(UnpCmtLength); + CmtUnpack.DoUnpack(CommHead.UnpVer,false); + + if (Format!=RARFMT14 && (DataIO.UnpHash.GetCRC32()&0xffff)!=CommHead.CommCRC) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + else + { + byte *UnpData; + size_t UnpDataSize; + DataIO.GetUnpackedData(&UnpData,&UnpDataSize); +#ifdef _WIN_ALL + // If we ever decide to extend it to Android, we'll need to alloc + // 4x memory for OEM to UTF-8 output here. + OemToCharBuffA((char *)UnpData,(char *)UnpData,(DWORD)UnpDataSize); +#endif + CmtData->Alloc(UnpDataSize+1); + memset(CmtData->Addr(0),0,CmtData->Size()*sizeof(wchar)); + CharToWide((char *)UnpData,CmtData->Addr(0),CmtData->Size()); + CmtData->Alloc(wcslen(CmtData->Addr(0))); + } + } + else + { + if (CmtLength==0) + return false; + Array CmtRaw(CmtLength); + Read(&CmtRaw[0],CmtLength); + + if (Format!=RARFMT14 && CommHead.CommCRC!=(~CRC32(0xffffffff,&CmtRaw[0],CmtLength)&0xffff)) + { + uiMsg(UIERROR_CMTBROKEN,FileName); + return false; + } + CmtData->Alloc(CmtLength+1); + CmtRaw.Push(0); +#ifdef _WIN_ALL + // If we ever decide to extend it to Android, we'll need to alloc + // 4x memory for OEM to UTF-8 output here. + OemToCharA((char *)&CmtRaw[0],(char *)&CmtRaw[0]); +#endif + CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtLength); + CmtData->Alloc(wcslen(CmtData->Addr(0))); + } +#endif + return CmtData->Size() > 0; +} + + +bool Archive::ReadCommentData(Array *CmtData) +{ + Array CmtRaw; + if (!ReadSubData(&CmtRaw,NULL)) + return false; + size_t CmtSize=CmtRaw.Size(); + CmtRaw.Push(0); + CmtData->Alloc(CmtSize+1); + if (Format==RARFMT50) + UtfToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + else + if ((SubHead.SubFlags & SUBHEAD_FLAGS_CMT_UNICODE)!=0) + { + RawToWide(&CmtRaw[0],CmtData->Addr(0),CmtSize/2); + (*CmtData)[CmtSize/2]=0; + + } + else + { + CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size()); + } + CmtData->Alloc(wcslen(CmtData->Addr(0))); // Set buffer size to actual comment length. + return true; +} + + +void Archive::ViewComment() +{ + if (Cmd->DisableComment) + return; + Array CmtBuf; + if (GetComment(&CmtBuf)) // In GUI too, so "Test" command detects broken comments. + { + size_t CmtSize=CmtBuf.Size(); + wchar *ChPtr=wcschr(&CmtBuf[0],0x1A); + if (ChPtr!=NULL) + CmtSize=ChPtr-&CmtBuf[0]; + mprintf(L"\n"); + OutComment(&CmtBuf[0],CmtSize); + } +} + + diff --git a/reference/unrar/archive.cpp b/reference/unrar/archive.cpp new file mode 100644 index 00000000..e66bddb9 --- /dev/null +++ b/reference/unrar/archive.cpp @@ -0,0 +1,332 @@ +#include "rar.hpp" + +#include "arccmt.cpp" + + +Archive::Archive(RAROptions *InitCmd) +{ + Cmd=NULL; // Just in case we'll have an exception in 'new' below. + + DummyCmd=(InitCmd==NULL); + Cmd=DummyCmd ? (new RAROptions):InitCmd; + + OpenShared=Cmd->OpenShared; + Format=RARFMT15; + Solid=false; + Volume=false; + MainComment=false; + Locked=false; + Signed=false; + FirstVolume=false; + NewNumbering=false; + SFXSize=0; + LatestTime.Reset(); + Protected=false; + Encrypted=false; + FailedHeaderDecryption=false; + BrokenHeader=false; + LastReadBlock=0; + + CurBlockPos=0; + NextBlockPos=0; + + RecoverySize=-1; + RecoveryPercent=-1; + + memset(&MainHead,0,sizeof(MainHead)); + memset(&CryptHead,0,sizeof(CryptHead)); + memset(&EndArcHead,0,sizeof(EndArcHead)); + + VolNumber=0; + VolWrite=0; + AddingFilesSize=0; + AddingHeadersSize=0; + *FirstVolumeName=0; + + Splitting=false; + NewArchive=false; + + SilentOpen=false; + +} + + +Archive::~Archive() +{ + if (DummyCmd) + delete Cmd; +} + + +void Archive::CheckArc(bool EnableBroken) +{ + if (!IsArchive(EnableBroken)) + { + // If FailedHeaderDecryption is set, we already reported that archive + // password is incorrect. + if (!FailedHeaderDecryption) + uiMsg(UIERROR_BADARCHIVE,FileName); + ErrHandler.Exit(RARX_FATAL); + } +} + + +#if !defined(SFX_MODULE) +void Archive::CheckOpen(const wchar *Name) +{ + TOpen(Name); + CheckArc(false); +} +#endif + + +bool Archive::WCheckOpen(const wchar *Name) +{ + if (!WOpen(Name)) + return false; + if (!IsArchive(false)) + { + uiMsg(UIERROR_BADARCHIVE,FileName); + Close(); + return false; + } + return true; +} + + +RARFORMAT Archive::IsSignature(const byte *D,size_t Size) +{ + RARFORMAT Type=RARFMT_NONE; + if (Size>=1 && D[0]==0x52) +#ifndef SFX_MODULE + if (Size>=4 && D[1]==0x45 && D[2]==0x7e && D[3]==0x5e) + Type=RARFMT14; + else +#endif + if (Size>=7 && D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07) + { + // We check the last signature byte, so we can return a sensible + // warning in case we'll want to change the archive format + // sometimes in the future. + if (D[6]==0) + Type=RARFMT15; + else + if (D[6]==1) + Type=RARFMT50; + else + if (D[6]==2) + Type=RARFMT_FUTURE; + } + return Type; +} + + +bool Archive::IsArchive(bool EnableBroken) +{ + Encrypted=false; + BrokenHeader=false; // Might be left from previous volume. + +#ifndef SFX_MODULE + if (IsDevice()) + { + uiMsg(UIERROR_INVALIDNAME,FileName,FileName); + return false; + } +#endif + if (Read(MarkHead.Mark,SIZEOF_MARKHEAD3)!=SIZEOF_MARKHEAD3) + return false; + SFXSize=0; + + RARFORMAT Type; + if ((Type=IsSignature(MarkHead.Mark,SIZEOF_MARKHEAD3))!=RARFMT_NONE) + { + Format=Type; + if (Format==RARFMT14) + Seek(Tell()-SIZEOF_MARKHEAD3,SEEK_SET); + } + else + { + Array Buffer(MAXSFXSIZE); + long CurPos=(long)Tell(); + int ReadSize=Read(&Buffer[0],Buffer.Size()-16); + for (int I=0;I0 && CurPos<28 && ReadSize>31) + { + char *D=&Buffer[28-CurPos]; + if (D[0]!=0x52 || D[1]!=0x53 || D[2]!=0x46 || D[3]!=0x58) + continue; + } + SFXSize=CurPos+I; + Seek(SFXSize,SEEK_SET); + if (Format==RARFMT15 || Format==RARFMT50) + Read(MarkHead.Mark,SIZEOF_MARKHEAD3); + break; + } + if (SFXSize==0) + return false; + } + if (Format==RARFMT_FUTURE) + { + uiMsg(UIERROR_NEWRARFORMAT,FileName); + return false; + } + if (Format==RARFMT50) // RAR 5.0 signature is by one byte longer. + { + Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1); + if (MarkHead.Mark[SIZEOF_MARKHEAD3]!=0) + return false; + MarkHead.HeadSize=SIZEOF_MARKHEAD5; + } + else + MarkHead.HeadSize=SIZEOF_MARKHEAD3; + +#ifdef RARDLL + // If callback function is not set, we cannot get the password, + // so we skip the initial header processing for encrypted header archive. + // It leads to skipped archive comment, but the rest of archive data + // is processed correctly. + if (Cmd->Callback==NULL) + SilentOpen=true; +#endif + + // Skip the archive encryption header if any and read the main header. + while (ReadHeader()!=0) + { + HEADER_TYPE Type=GetHeaderType(); + // In RAR 5.0 we need to quit after reading HEAD_CRYPT if we wish to + // avoid the password prompt. + if (Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT) + break; + SeekToNext(); + } + + // This check allows to make RS based recovery even if password is incorrect. + // But we should not do it for EnableBroken or we'll get 'not RAR archive' + // messages when extracting encrypted archives with wrong password. + if (FailedHeaderDecryption && !EnableBroken) + return false; + + SeekToNext(); + if (BrokenHeader) // Main archive header is corrupt. + { + uiMsg(UIERROR_MHEADERBROKEN,FileName); + if (!EnableBroken) + return false; + } + + MainComment=MainHead.CommentInHeader; + + // If we process non-encrypted archive or can request a password, + // we set 'first volume' flag based on file attributes below. + // It is necessary for RAR 2.x archives, which did not have 'first volume' + // flag in main header. Also for all RAR formats we need to scan until + // first file header to set "comment" flag when reading service header. + // Unless we are in silent mode, we need to know about presence of comment + // immediately after IsArchive call. + if (!SilentOpen || !Encrypted) + { + SaveFilePos SavePos(*this); + int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos; + HEADER_TYPE SaveCurHeaderType=CurHeaderType; + + while (ReadHeader()!=0) + { + HEADER_TYPE HeaderType=GetHeaderType(); + if (HeaderType==HEAD_SERVICE) + { + // If we have a split service headers, it surely indicates non-first + // volume. But not split service header does not guarantee the first + // volume, because we can have split file after non-split archive + // comment. So we do not quit from loop here. + FirstVolume=Volume && !SubHead.SplitBefore; + } + else + if (HeaderType==HEAD_FILE) + { + FirstVolume=Volume && !FileHead.SplitBefore; + break; + } + else + if (HeaderType==HEAD_ENDARC) // Might happen if archive contains only a split service header. + break; + SeekToNext(); + } + CurBlockPos=SaveCurBlockPos; + NextBlockPos=SaveNextBlockPos; + CurHeaderType=SaveCurHeaderType; + } + if (!Volume || FirstVolume) + wcscpy(FirstVolumeName,FileName); + + return true; +} + + + + +void Archive::SeekToNext() +{ + Seek(NextBlockPos,SEEK_SET); +} + + + + + + +// Calculate the block size including encryption fields and padding if any. +uint Archive::FullHeaderSize(size_t Size) +{ + if (Encrypted) + { + Size = ALIGN_VALUE(Size, CRYPT_BLOCK_SIZE); // Align to encryption block size. + if (Format == RARFMT50) + Size += SIZE_INITV; + else + Size += SIZE_SALT30; + } + return uint(Size); +} + + + + +#ifdef USE_QOPEN +bool Archive::Open(const wchar *Name,uint Mode) +{ + // Important if we reuse Archive object and it has virtual QOpen + // file position not matching real. For example, for 'l -v volname'. + QOpen.Unload(); + + return File::Open(Name,Mode); +} + + +int Archive::Read(void *Data,size_t Size) +{ + size_t Result; + if (QOpen.Read(Data,Size,Result)) + return (int)Result; + return File::Read(Data,Size); +} + + +void Archive::Seek(int64 Offset,int Method) +{ + if (!QOpen.Seek(Offset,Method)) + File::Seek(Offset,Method); +} + + +int64 Archive::Tell() +{ + int64 QPos; + if (QOpen.Tell(&QPos)) + return QPos; + return File::Tell(); +} +#endif + diff --git a/reference/unrar/archive.hpp b/reference/unrar/archive.hpp new file mode 100644 index 00000000..6e85b9ff --- /dev/null +++ b/reference/unrar/archive.hpp @@ -0,0 +1,151 @@ +#ifndef _RAR_ARCHIVE_ +#define _RAR_ARCHIVE_ + +class PPack; +class RawRead; +class RawWrite; + +enum NOMODIFY_FLAGS +{ + NMDF_ALLOWLOCK=1,NMDF_ALLOWANYVOLUME=2,NMDF_ALLOWFIRSTVOLUME=4 +}; + +enum RARFORMAT {RARFMT_NONE,RARFMT14,RARFMT15,RARFMT50,RARFMT_FUTURE}; + +enum ADDSUBDATA_FLAGS +{ + ASDF_SPLIT = 1, // Allow to split archive just before header if necessary. + ASDF_COMPRESS = 2, // Allow to compress data following subheader. + ASDF_CRYPT = 4, // Encrypt data after subheader if password is set. + ASDF_CRYPTIFHEADERS = 8 // Encrypt data after subheader only in -hp mode. +}; + +class Archive:public File +{ + private: + void UpdateLatestTime(FileHeader *CurBlock); + void ConvertNameCase(wchar *Name); + void ConvertFileHeader(FileHeader *hd); + void WriteBlock50(HEADER_TYPE HeaderType,BaseBlock *wb,bool OnlySetSize,bool NonFinalWrite); + size_t ReadHeader14(); + size_t ReadHeader15(); + size_t ReadHeader50(); + void ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb); + void RequestArcPassword(); + void UnexpEndArcMsg(); + void BrokenHeaderMsg(); + void UnkEncVerMsg(const wchar *Name); + void UnkEncVerMsg(); + bool ReadCommentData(Array *CmtData); + +#if !defined(RAR_NOCRYPT) + CryptData HeadersCrypt; +#endif + ComprDataIO SubDataIO; + bool DummyCmd; + RAROptions *Cmd; + + int64 RecoverySize; + int RecoveryPercent; + + RarTime LatestTime; + int LastReadBlock; + HEADER_TYPE CurHeaderType; + + bool SilentOpen; +#ifdef USE_QOPEN + QuickOpen QOpen; +#endif + public: + Archive(RAROptions *InitCmd=NULL); + ~Archive(); + static RARFORMAT IsSignature(const byte *D,size_t Size); + bool IsArchive(bool EnableBroken); + size_t SearchBlock(HEADER_TYPE HeaderType); + size_t SearchSubBlock(const wchar *Type); + size_t SearchRR(); + void WriteBlock(HEADER_TYPE HeaderType,BaseBlock *wb=NULL,bool OnlySetSize=false,bool NonFinalWrite=false); + void SetBlockSize(HEADER_TYPE HeaderType,BaseBlock *wb=NULL) {WriteBlock(HeaderType,wb,true);} + size_t ReadHeader(); + void CheckArc(bool EnableBroken); + void CheckOpen(const wchar *Name); + bool WCheckOpen(const wchar *Name); + bool GetComment(Array *CmtData); + void ViewComment(); + void SetLatestTime(RarTime *NewTime); + void SeekToNext(); + bool CheckAccess(); + bool IsArcDir(); + void ConvertAttributes(); + void VolSubtractHeaderSize(size_t SubSize); + uint FullHeaderSize(size_t Size); + int64 GetStartPos(); + void AddSubData(byte *SrcData,uint64 DataSize,File *SrcFile, + const wchar *Name,uint Flags); + bool ReadSubData(Array *UnpData,File *DestFile); + HEADER_TYPE GetHeaderType() {return CurHeaderType;}; + RAROptions* GetRAROptions() {return Cmd;} + void SetSilentOpen(bool Mode) {SilentOpen=Mode;} +#if 0 + void GetRecoveryInfo(bool Required,int64 *Size,int *Percent); +#endif +#ifdef USE_QOPEN + bool Open(const wchar *Name,uint Mode=FMF_READ); + int Read(void *Data,size_t Size); + void Seek(int64 Offset,int Method); + int64 Tell(); + void QOpenUnload() {QOpen.Unload();} +#endif + + BaseBlock ShortBlock; + MarkHeader MarkHead; + MainHeader MainHead; + CryptHeader CryptHead; + FileHeader FileHead; + EndArcHeader EndArcHead; + SubBlockHeader SubBlockHead; + FileHeader SubHead; + CommentHeader CommHead; + ProtectHeader ProtectHead; + AVHeader AVHead; + SignHeader SignHead; + UnixOwnersHeader UOHead; + MacFInfoHeader MACHead; + EAHeader EAHead; + StreamHeader StreamHead; + + int64 CurBlockPos; + int64 NextBlockPos; + + RARFORMAT Format; + bool Solid; + bool Volume; + bool MainComment; + bool Locked; + bool Signed; + bool FirstVolume; + bool NewNumbering; + bool Protected; + bool Encrypted; + size_t SFXSize; + bool BrokenHeader; + bool FailedHeaderDecryption; + +#if !defined(RAR_NOCRYPT) + byte ArcSalt[SIZE_SALT50]; +#endif + + bool Splitting; + + uint VolNumber; + int64 VolWrite; + uint64 AddingFilesSize; + uint64 AddingHeadersSize; + + bool NewArchive; + + wchar FirstVolumeName[NM]; +}; + + +#endif diff --git a/reference/unrar/arcread.cpp b/reference/unrar/arcread.cpp new file mode 100644 index 00000000..1120f682 --- /dev/null +++ b/reference/unrar/arcread.cpp @@ -0,0 +1,1437 @@ +#include "rar.hpp" + +size_t Archive::ReadHeader() +{ + // Once we failed to decrypt an encrypted block, there is no reason to + // attempt to do it further. We'll never be successful and only generate + // endless errors. + if (FailedHeaderDecryption) + return 0; + + CurBlockPos=Tell(); + + size_t ReadSize; + switch(Format) + { +#ifndef SFX_MODULE + case RARFMT14: + ReadSize=ReadHeader14(); + break; +#endif + case RARFMT15: + ReadSize=ReadHeader15(); + break; + case RARFMT50: + ReadSize=ReadHeader50(); + break; + } + + if (ReadSize>0 && NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + return ReadSize; +} + + +size_t Archive::SearchBlock(HEADER_TYPE HeaderType) +{ + size_t Size,Count=0; + while ((Size=ReadHeader())!=0 && + (HeaderType==HEAD_ENDARC || GetHeaderType()!=HEAD_ENDARC)) + { + if ((++Count & 127)==0) + Wait(); + if (GetHeaderType()==HeaderType) + return Size; + SeekToNext(); + } + return 0; +} + + +size_t Archive::SearchSubBlock(const wchar *Type) +{ + size_t Size,Count=0; + while ((Size=ReadHeader())!=0 && GetHeaderType()!=HEAD_ENDARC) + { + if ((++Count & 127)==0) + Wait(); + if (GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(Type)) + return Size; + SeekToNext(); + } + return 0; +} + + +size_t Archive::SearchRR() +{ + // If locator extra field is available for recovery record, let's utilize it. + if (MainHead.Locator && MainHead.RROffset!=0) + { + uint64 CurPos=Tell(); + Seek(MainHead.RROffset,SEEK_SET); + size_t Size=ReadHeader(); + if (Size!=0 && !BrokenHeader && GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(SUBHEAD_TYPE_RR)) + return Size; + Seek(CurPos,SEEK_SET); + } + // Otherwise scan the entire archive to find the recovery record. + return SearchSubBlock(SUBHEAD_TYPE_RR); +} + + +void Archive::UnexpEndArcMsg() +{ + int64 ArcSize=FileLength(); + + // 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. + if (CurBlockPos!=ArcSize || NextBlockPos!=ArcSize) + { + uiMsg(UIERROR_UNEXPEOF,FileName); + ErrHandler.SetErrorCode(RARX_WARNING); + } +} + + +void Archive::BrokenHeaderMsg() +{ + uiMsg(UIERROR_HEADERBROKEN,FileName); + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_CRC); +} + + +void Archive::UnkEncVerMsg(const wchar *Name) +{ + uiMsg(UIERROR_UNKNOWNENCMETHOD,FileName,Name); + ErrHandler.SetErrorCode(RARX_WARNING); +} + + +size_t Archive::ReadHeader15() +{ + RawRead Raw(this); + + bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD3; + + if (Decrypt) + { +#ifdef RAR_NOCRYPT // For rarext.dll and unrar_nocrypt.dll. + return 0; +#else + RequestArcPassword(); + + byte Salt[SIZE_SALT30]; + if (Read(Salt,SIZE_SALT30)!=SIZE_SALT30) + { + UnexpEndArcMsg(); + return 0; + } + HeadersCrypt.SetCryptKeys(false,CRYPT_RAR30,&Cmd->Password,Salt,NULL,0,NULL,NULL); + Raw.SetCrypt(&HeadersCrypt); +#endif + } + + Raw.Read(SIZEOF_SHORTBLOCKHEAD); + if (Raw.Size()==0) + { + UnexpEndArcMsg(); + return 0; + } + + ShortBlock.HeadCRC=Raw.Get2(); + + ShortBlock.Reset(); + + uint HeaderType=Raw.Get1(); + ShortBlock.Flags=Raw.Get2(); + ShortBlock.SkipIfUnknown=(ShortBlock.Flags & SKIP_IF_UNKNOWN)!=0; + ShortBlock.HeadSize=Raw.Get2(); + + ShortBlock.HeaderType=(HEADER_TYPE)HeaderType; + if (ShortBlock.HeadSizeReset(); + + *(BaseBlock *)hd=ShortBlock; + + hd->SplitBefore=(hd->Flags & LHD_SPLIT_BEFORE)!=0; + hd->SplitAfter=(hd->Flags & LHD_SPLIT_AFTER)!=0; + hd->Encrypted=(hd->Flags & LHD_PASSWORD)!=0; + hd->SaltSet=(hd->Flags & LHD_SALT)!=0; + 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); + hd->CommentInHeader=(hd->Flags & LHD_COMMENT)!=0; + hd->Version=(hd->Flags & LHD_VERSION)!=0; + + hd->DataSize=Raw.Get4(); + uint LowUnpSize=Raw.Get4(); + hd->HostOS=Raw.Get1(); + + hd->FileHash.Type=HASH_CRC32; + hd->FileHash.CRC32=Raw.Get4(); + + uint FileTime=Raw.Get4(); + hd->UnpVer=Raw.Get1(); + hd->Method=Raw.Get1()-0x30; + size_t NameSize=Raw.Get2(); + hd->FileAttr=Raw.Get4(); + + hd->CryptMethod=CRYPT_NONE; + if (hd->Encrypted) + switch(hd->UnpVer) + { + case 13: hd->CryptMethod=CRYPT_RAR13; break; + case 15: hd->CryptMethod=CRYPT_RAR15; break; + case 20: + case 26: hd->CryptMethod=CRYPT_RAR20; break; + default: hd->CryptMethod=CRYPT_RAR30; break; + } + + hd->HSType=HSYS_UNKNOWN; + if (hd->HostOS==HOST_UNIX || hd->HostOS==HOST_BEOS) + hd->HSType=HSYS_UNIX; + else + if (hd->HostOSHSType=HSYS_WINDOWS; + + hd->RedirType=FSREDIR_NONE; + + // RAR 4.x Unix symlink. + if (hd->HostOS==HOST_UNIX && (hd->FileAttr & 0xF000)==0xA000) + { + hd->RedirType=FSREDIR_UNIXSYMLINK; + *hd->RedirName=0; + } + + hd->Inherited=!FileBlock && (hd->SubFlags & SUBHEAD_FLAGS_INHERITED)!=0; + + hd->LargeFile=(hd->Flags & LHD_LARGE)!=0; + + uint HighPackSize,HighUnpSize; + if (hd->LargeFile) + { + HighPackSize=Raw.Get4(); + HighUnpSize=Raw.Get4(); + hd->UnknownUnpSize=(LowUnpSize==0xffffffff && HighUnpSize==0xffffffff); + } + else + { + HighPackSize=HighUnpSize=0; + // UnpSize equal to 0xffffffff without LHD_LARGE flag indicates + // that we do not know the unpacked file size and must unpack it + // until we find the end of file marker in compressed data. + hd->UnknownUnpSize=(LowUnpSize==0xffffffff); + } + hd->PackSize=INT32TO64(HighPackSize,hd->DataSize); + hd->UnpSize=INT32TO64(HighUnpSize,LowUnpSize); + if (hd->UnknownUnpSize) + hd->UnpSize=INT64NDF; + + char FileName[NM*4]; + size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1); + Raw.GetB((byte *)FileName,ReadNameSize); + FileName[ReadNameSize]=0; + + if (FileBlock) + { + *hd->FileName=0; + if ((hd->Flags & LHD_UNICODE)!=0) + { + EncodeFileName NameCoder; + size_t Length=strlen(FileName); + Length++; + if (ReadNameSize>Length) + NameCoder.Decode(FileName,(byte *)FileName+Length, + ReadNameSize-Length,hd->FileName, + ASIZE(hd->FileName)); + } + + if (*hd->FileName==0) + ArcCharToWide(FileName,hd->FileName,ASIZE(hd->FileName),ACTW_OEM); + +#ifndef SFX_MODULE + ConvertNameCase(hd->FileName); +#endif + ConvertFileHeader(hd); + } + else + { + CharToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + + // Calculate the size of optional data. + int DataSize=int(hd->HeadSize-NameSize-SIZEOF_FILEHEAD3); + if ((hd->Flags & LHD_SALT)!=0) + DataSize-=SIZE_SALT30; + + if (DataSize>0) + { + // Here we read optional additional fields for subheaders. + // They are stored after the file name and before salt. + hd->SubData.Alloc(DataSize); + Raw.GetB(&hd->SubData[0],DataSize); + if (hd->CmpName(SUBHEAD_TYPE_RR)) + { + byte *D=&hd->SubData[8]; + RecoverySize=D[0]+((uint)D[1]<<8)+((uint)D[2]<<16)+((uint)D[3]<<24); + RecoverySize*=512; // Sectors to size. + int64 CurPos=Tell(); + RecoveryPercent=ToPercent(RecoverySize,CurPos); + // Round fractional percent exceeding .5 to upper value. + if (ToPercent(RecoverySize+CurPos/200,CurPos)>RecoveryPercent) + RecoveryPercent++; + } + } + + if (hd->CmpName(SUBHEAD_TYPE_CMT)) + MainComment=true; + } + if ((hd->Flags & LHD_SALT)!=0) + Raw.GetB(hd->Salt,SIZE_SALT30); + hd->mtime.SetDos(FileTime); + if ((hd->Flags & LHD_EXTTIME)!=0) + { + ushort Flags=Raw.Get2(); + RarTime *tbl[4]; + tbl[0]=&FileHead.mtime; + tbl[1]=&FileHead.ctime; + tbl[2]=&FileHead.atime; + tbl[3]=NULL; // Archive time is not used now. + for (int I=0;I<4;I++) + { + RarTime *CurTime=tbl[I]; + uint rmode=Flags>>(3-I)*4; + if ((rmode & 8)==0 || CurTime==NULL) + continue; + if (I!=0) + { + uint DosTime=Raw.Get4(); + CurTime->SetDos(DosTime); + } + RarLocalTime rlt; + CurTime->GetLocal(&rlt); + if (rmode & 4) + rlt.Second++; + rlt.Reminder=0; + int count=rmode&3; + for (int J=0;JSetLocal(&rlt); + } + } + NextBlockPos+=hd->PackSize; + bool CRCProcessedOnly=hd->CommentInHeader; + ushort HeaderCRC=Raw.GetCRC15(CRCProcessedOnly); + if (hd->HeadCRC!=HeaderCRC) + { + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_WARNING); + + // If we have a broken encrypted header, we do not need to display + // the error message here, because it will be displayed for such + // headers later in this function. Also such headers are unlikely + // to have anything sensible in file name field, so it is useless + // to display the file name. + if (!Decrypt) + uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName); + } + } + break; + case HEAD_ENDARC: + *(BaseBlock *)&EndArcHead=ShortBlock; + EndArcHead.NextVolume=(EndArcHead.Flags & EARC_NEXT_VOLUME)!=0; + EndArcHead.DataCRC=(EndArcHead.Flags & EARC_DATACRC)!=0; + EndArcHead.RevSpace=(EndArcHead.Flags & EARC_REVSPACE)!=0; + EndArcHead.StoreVolNumber=(EndArcHead.Flags & EARC_VOLNUMBER)!=0; + if (EndArcHead.DataCRC) + EndArcHead.ArcDataCRC=Raw.Get4(); + if (EndArcHead.StoreVolNumber) + VolNumber=EndArcHead.VolNumber=Raw.Get2(); + break; +#ifndef SFX_MODULE + case HEAD3_CMT: + *(BaseBlock *)&CommHead=ShortBlock; + CommHead.UnpSize=Raw.Get2(); + CommHead.UnpVer=Raw.Get1(); + CommHead.Method=Raw.Get1(); + CommHead.CommCRC=Raw.Get2(); + break; + case HEAD3_SIGN: + *(BaseBlock *)&SignHead=ShortBlock; + SignHead.CreationTime=Raw.Get4(); + SignHead.ArcNameSize=Raw.Get2(); + SignHead.UserNameSize=Raw.Get2(); + break; + case HEAD3_AV: + *(BaseBlock *)&AVHead=ShortBlock; + AVHead.UnpVer=Raw.Get1(); + AVHead.Method=Raw.Get1(); + AVHead.AVVer=Raw.Get1(); + AVHead.AVInfoCRC=Raw.Get4(); + break; + case HEAD3_PROTECT: + *(BaseBlock *)&ProtectHead=ShortBlock; + ProtectHead.DataSize=Raw.Get4(); + ProtectHead.Version=Raw.Get1(); + ProtectHead.RecSectors=Raw.Get2(); + ProtectHead.TotalBlocks=Raw.Get4(); + Raw.GetB(ProtectHead.Mark,8); + NextBlockPos+=ProtectHead.DataSize; + RecoverySize=ProtectHead.RecSectors*512; + break; + case HEAD3_OLDSERVICE: + *(BaseBlock *)&SubBlockHead=ShortBlock; + SubBlockHead.DataSize=Raw.Get4(); + NextBlockPos+=SubBlockHead.DataSize; + SubBlockHead.SubType=Raw.Get2(); + 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 MAC_HEAD: + *(SubBlockHeader *)&MACHead=SubBlockHead; + MACHead.fileType=Raw.Get4(); + MACHead.fileCreator=Raw.Get4(); + break; + case EA_HEAD: + case BEEA_HEAD: + case NTACL_HEAD: + *(SubBlockHeader *)&EAHead=SubBlockHead; + EAHead.UnpSize=Raw.Get4(); + EAHead.UnpVer=Raw.Get1(); + EAHead.Method=Raw.Get1(); + EAHead.EACRC=Raw.Get4(); + break; + case STREAM_HEAD: + *(SubBlockHeader *)&StreamHead=SubBlockHead; + StreamHead.UnpSize=Raw.Get4(); + StreamHead.UnpVer=Raw.Get1(); + StreamHead.Method=Raw.Get1(); + StreamHead.StreamCRC=Raw.Get4(); + StreamHead.StreamNameSize=Raw.Get2(); + if (StreamHead.StreamNameSize>=ASIZE(StreamHead.StreamName)) + StreamHead.StreamNameSize=ASIZE(StreamHead.StreamName)-1; + Raw.GetB(StreamHead.StreamName,StreamHead.StreamNameSize); + StreamHead.StreamName[StreamHead.StreamNameSize]=0; + break; + } + break; +#endif + default: + if (ShortBlock.Flags & LONG_BLOCK) + NextBlockPos+=Raw.Get4(); + break; + } + + ushort HeaderCRC=Raw.GetCRC15(false); + + // Old AV header does not have header CRC properly set. + if (ShortBlock.HeadCRC!=HeaderCRC && ShortBlock.HeaderType!=HEAD3_SIGN && + ShortBlock.HeaderType!=HEAD3_AV) + { + bool Recovered=false; + if (ShortBlock.HeaderType==HEAD_ENDARC && EndArcHead.RevSpace) + { + // Last 7 bytes of recovered volume can contain zeroes, because + // REV files store its own information (volume number, etc.) here. + SaveFilePos SavePos(*this); + int64 Length=Tell(); + Seek(Length-7,SEEK_SET); + Recovered=true; + for (int J=0;J<7;J++) + if (GetByte()!=0) + Recovered=false; + } + if (!Recovered) + { + BrokenHeader=true; + ErrHandler.SetErrorCode(RARX_CRC); + + if (Decrypt) + { + uiMsg(UIERROR_CHECKSUMENC,FileName,FileName); + FailedHeaderDecryption=true; + return 0; + } + } + } + + if (NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + + return Raw.Size(); +} + + +size_t Archive::ReadHeader50() +{ + RawRead Raw(this); + + bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD5; + + if (Decrypt) + { +#if defined(RAR_NOCRYPT) + return 0; +#else + RequestArcPassword(); + + byte HeadersInitV[SIZE_INITV]; + if (Read(HeadersInitV,SIZE_INITV)!=SIZE_INITV) + { + UnexpEndArcMsg(); + return 0; + } + + 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) + { + uiMsg(UIERROR_BADPSW,FileName); + FailedHeaderDecryption=true; + ErrHandler.SetErrorCode(RARX_BADPWD); + return 0; + } + + Raw.SetCrypt(&HeadersCrypt); +#endif + } + + // Header size must not occupy more than 3 variable length integer bytes + // resulting in 2 MB maximum header size, so here we read 4 byte CRC32 + // followed by 3 bytes or less of header size. + const size_t FirstReadSize=7; // Smallest possible block size. + if (Raw.Read(FirstReadSize)=ShortBlock.HeadSize) + { + BrokenHeaderMsg(); + return 0; + } + } + + uint64 DataSize=0; + if ((ShortBlock.Flags & HFL_DATA)!=0) + DataSize=Raw.GetV(); + + NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize)+DataSize; + + switch(ShortBlock.HeaderType) + { + case HEAD_CRYPT: + { + *(BaseBlock *)&CryptHead=ShortBlock; + uint CryptVersion=(uint)Raw.GetV(); + if (CryptVersion>CRYPT_VERSION) + { + UnkEncVerMsg(FileName); + return 0; + } + uint EncFlags=(uint)Raw.GetV(); + CryptHead.UsePswCheck=(EncFlags & CHFL_CRYPT_PSWCHECK)!=0; + CryptHead.Lg2Count=Raw.Get1(); + if (CryptHead.Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) + { + UnkEncVerMsg(FileName); + return 0; + } + Raw.GetB(CryptHead.Salt,SIZE_SALT50); + if (CryptHead.UsePswCheck) + { + Raw.GetB(CryptHead.PswCheck,SIZE_PSWCHECK); + + byte csum[SIZE_PSWCHECK_CSUM]; + Raw.GetB(csum,SIZE_PSWCHECK_CSUM); + + sha256_context ctx; + sha256_init(&ctx); + sha256_process(&ctx, CryptHead.PswCheck, SIZE_PSWCHECK); + + byte Digest[SHA256_DIGEST_SIZE]; + sha256_done(&ctx, Digest); + + CryptHead.UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; + } + Encrypted=true; + } + break; + case HEAD_MAIN: + { + MainHead.Reset(); + *(BaseBlock *)&MainHead=ShortBlock; + uint ArcFlags=(uint)Raw.GetV(); + + Volume=(ArcFlags & MHFL_VOLUME)!=0; + Solid=(ArcFlags & MHFL_SOLID)!=0; + Locked=(ArcFlags & MHFL_LOCK)!=0; + Protected=(ArcFlags & MHFL_PROTECT)!=0; + Signed=false; + NewNumbering=true; + + if ((ArcFlags & MHFL_VOLNUMBER)!=0) + VolNumber=(uint)Raw.GetV(); + else + VolNumber=0; + FirstVolume=Volume && VolNumber==0; + + if (ExtraSize!=0) + ProcessExtra50(&Raw,(size_t)ExtraSize,&MainHead); + +#ifdef USE_QOPEN + if (MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE) + { + // We seek to QO block in the end of archive when processing + // QOpen.Load, so we need to preserve current block positions + // to not break normal archive processing by calling function. + int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos; + HEADER_TYPE SaveCurHeaderType=CurHeaderType; + + QOpen.Init(this,false); + QOpen.Load(MainHead.QOpenOffset); + + CurBlockPos=SaveCurBlockPos; + NextBlockPos=SaveNextBlockPos; + CurHeaderType=SaveCurHeaderType; + } +#endif + } + break; + case HEAD_FILE: + case HEAD_SERVICE: + { + FileHeader *hd=ShortBlock.HeaderType==HEAD_FILE ? &FileHead:&SubHead; + hd->Reset(); + *(BaseBlock *)hd=ShortBlock; + + bool FileBlock=ShortBlock.HeaderType==HEAD_FILE; + + hd->LargeFile=true; + + hd->PackSize=DataSize; + hd->FileFlags=(uint)Raw.GetV(); + hd->UnpSize=Raw.GetV(); + + hd->UnknownUnpSize=(hd->FileFlags & FHFL_UNPUNKNOWN)!=0; + if (hd->UnknownUnpSize) + hd->UnpSize=INT64NDF; + + hd->MaxSize=Max(hd->PackSize,hd->UnpSize); + hd->FileAttr=(uint)Raw.GetV(); + if ((hd->FileFlags & FHFL_UTIME)!=0) + hd->mtime.SetUnix((time_t)Raw.Get4()); + + hd->FileHash.Type=HASH_NONE; + if ((hd->FileFlags & FHFL_CRC32)!=0) + { + hd->FileHash.Type=HASH_CRC32; + hd->FileHash.CRC32=Raw.Get4(); + } + + hd->RedirType=FSREDIR_NONE; + + uint CompInfo=(uint)Raw.GetV(); + hd->Method=(CompInfo>>7) & 7; + + // "+ 50" to not mix with old RAR format algorithms. For example, + // we may need to use the compression algorithm 15 in the future, + // but it was already used in RAR 1.5 and Unpack needs to distinguish + // them. + hd->UnpVer=(CompInfo & 0x3f) + 50; + + hd->HostOS=(byte)Raw.GetV(); + size_t NameSize=(size_t)Raw.GetV(); + hd->Inherited=(ShortBlock.Flags & HFL_INHERITED)!=0; + + hd->HSType=HSYS_UNKNOWN; + if (hd->HostOS==HOST5_UNIX) + hd->HSType=HSYS_UNIX; + else + if (hd->HostOS==HOST5_WINDOWS) + hd->HSType=HSYS_WINDOWS; + + hd->SplitBefore=(hd->Flags & HFL_SPLITBEFORE)!=0; + hd->SplitAfter=(hd->Flags & HFL_SPLITAFTER)!=0; + hd->SubBlock=(hd->Flags & HFL_CHILD)!=0; + hd->Solid=FileBlock && (CompInfo & FCI_SOLID)!=0; + 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); + FileName[ReadNameSize]=0; + + UtfToWide(FileName,hd->FileName,ASIZE(hd->FileName)); + + // Should do it before converting names, because extra fields can + // affect name processing, like in case of NTFS streams. + if (ExtraSize!=0) + ProcessExtra50(&Raw,(size_t)ExtraSize,hd); + + if (FileBlock) + { +#ifndef SFX_MODULE + ConvertNameCase(hd->FileName); +#endif + ConvertFileHeader(hd); + } + + 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) + { + RecoveryPercent=hd->SubData[0]; + 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); + } + break; + case HEAD_ENDARC: + { + *(BaseBlock *)&EndArcHead=ShortBlock; + uint ArcFlags=(uint)Raw.GetV(); + EndArcHead.NextVolume=(ArcFlags & EHFL_NEXTVOLUME)!=0; + EndArcHead.StoreVolNumber=false; + EndArcHead.DataCRC=false; + EndArcHead.RevSpace=false; + } + break; + } + + if (NextBlockPos<=CurBlockPos) + { + BrokenHeaderMsg(); + return 0; + } + return Raw.Size(); +} + + +#if !defined(RAR_NOCRYPT) +void Archive::RequestArcPassword() +{ + if (!Cmd->Password.IsSet()) + { +#ifdef RARDLL + if (Cmd->Callback!=NULL) + { + wchar PasswordW[MAXPASSWORD]; + *PasswordW=0; + if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1) + *PasswordW=0; + if (*PasswordW==0) + { + char PasswordA[MAXPASSWORD]; + *PasswordA=0; + if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) + *PasswordA=0; + GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + cleandata(PasswordA,sizeof(PasswordA)); + } + Cmd->Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); + } + if (!Cmd->Password.IsSet()) + { + Close(); + Cmd->DllError=ERAR_MISSING_PASSWORD; + ErrHandler.Exit(RARX_USERBREAK); + } +#else + if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password) || + !Cmd->Password.IsSet()) + { + Close(); + uiMsg(UIERROR_INCERRCOUNT); + ErrHandler.Exit(RARX_USERBREAK); + } +#endif + Cmd->ManualPassword=true; + } +} +#endif + + +void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb) +{ + // Read extra data from the end of block skipping any fields before it. + size_t ExtraStart=Raw->Size()-ExtraSize; + if (ExtraStartGetPos()) + return; + Raw->SetPos(ExtraStart); + while (Raw->DataLeft()>=2) + { + int64 FieldSize=Raw->GetV(); + if (FieldSize==0 || Raw->DataLeft()==0 || FieldSize>(int64)Raw->DataLeft()) + break; + size_t NextPos=size_t(Raw->GetPos()+FieldSize); + uint64 FieldType=Raw->GetV(); + + FieldSize=int64(NextPos-Raw->GetPos()); // Field size without size and type fields. + + if (bb->HeaderType==HEAD_MAIN) + { + MainHeader *hd=(MainHeader *)bb; + if (FieldType==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; + } + } + } + + if (bb->HeaderType==HEAD_FILE || bb->HeaderType==HEAD_SERVICE) + { + FileHeader *hd=(FileHeader *)bb; + switch(FieldType) + { + case FHEXTRA_CRYPT: + { + FileHeader *hd=(FileHeader *)bb; + uint EncVersion=(uint)Raw->GetV(); + if (EncVersion > CRYPT_VERSION) + UnkEncVerMsg(hd->FileName); + else + { + uint Flags=(uint)Raw->GetV(); + hd->UsePswCheck=(Flags & FHEXTRA_CRYPT_PSWCHECK)!=0; + hd->UseHashKey=(Flags & FHEXTRA_CRYPT_HASHMAC)!=0; + hd->Lg2Count=Raw->Get1(); + if (hd->Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX) + UnkEncVerMsg(hd->FileName); + Raw->GetB(hd->Salt,SIZE_SALT50); + Raw->GetB(hd->InitV,SIZE_INITV); + if (hd->UsePswCheck) + { + Raw->GetB(hd->PswCheck,SIZE_PSWCHECK); + + // It is important to know if password check data is valid. + // If it is damaged and header CRC32 fails to detect it, + // archiver would refuse to decompress a possibly valid file. + // Since we want to be sure distinguishing a wrong password + // or corrupt file data, we use 64-bit password check data + // and to control its validity we use 32 bits of password + // check data SHA-256 additionally to 32-bit header CRC32. + byte csum[SIZE_PSWCHECK_CSUM]; + Raw->GetB(csum,SIZE_PSWCHECK_CSUM); + + sha256_context ctx; + sha256_init(&ctx); + sha256_process(&ctx, hd->PswCheck, SIZE_PSWCHECK); + + byte Digest[SHA256_DIGEST_SIZE]; + sha256_done(&ctx, Digest); + + hd->UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0; + + // RAR 5.21 and earlier set PswCheck field in service records to 0 + // even if UsePswCheck was present. + if (bb->HeaderType==HEAD_SERVICE && memcmp(hd->PswCheck,"\0\0\0\0\0\0\0\0",SIZE_PSWCHECK)==0) + hd->UsePswCheck=0; + } + hd->SaltSet=true; + hd->CryptMethod=CRYPT_RAR50; + hd->Encrypted=true; + } + } + break; + case FHEXTRA_HASH: + { + FileHeader *hd=(FileHeader *)bb; + uint Type=(uint)Raw->GetV(); + if (Type==FHEXTRA_HASH_BLAKE2) + { + hd->FileHash.Type=HASH_BLAKE2; + Raw->GetB(hd->FileHash.Digest,BLAKE2_DIGEST_SIZE); + } + } + break; + case FHEXTRA_HTIME: + if (FieldSize>=5) + { + byte Flags=(byte)Raw->GetV(); + bool UnixTime=(Flags & FHEXTRA_HTIME_UNIXTIME)!=0; + if ((Flags & FHEXTRA_HTIME_MTIME)!=0) + if (UnixTime) + hd->mtime.SetUnix(Raw->Get4()); + else + hd->mtime.SetWin(Raw->Get8()); + if ((Flags & FHEXTRA_HTIME_CTIME)!=0) + if (UnixTime) + hd->ctime.SetUnix(Raw->Get4()); + else + hd->ctime.SetWin(Raw->Get8()); + if ((Flags & FHEXTRA_HTIME_ATIME)!=0) + if (UnixTime) + hd->atime.SetUnix((time_t)Raw->Get4()); + else + hd->atime.SetWin(Raw->Get8()); + if (UnixTime && (Flags & FHEXTRA_HTIME_UNIX_NS)!=0) // Add nanoseconds. + { + uint ns; + if ((Flags & FHEXTRA_HTIME_MTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->mtime.Adjust(ns); + if ((Flags & FHEXTRA_HTIME_CTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->ctime.Adjust(ns); + if ((Flags & FHEXTRA_HTIME_ATIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000) + hd->atime.Adjust(ns); + } + } + break; + case FHEXTRA_VERSION: + if (FieldSize>=1) + { + Raw->GetV(); // Skip flags field. + uint Version=(uint)Raw->GetV(); + if (Version!=0) + { + hd->Version=true; + + wchar VerText[20]; + swprintf(VerText,ASIZE(VerText),L";%u",Version); + wcsncatz(FileHead.FileName,VerText,ASIZE(FileHead.FileName)); + } + } + break; + case FHEXTRA_REDIR: + { + hd->RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV(); + uint Flags=(uint)Raw->GetV(); + hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0; + size_t NameSize=(size_t)Raw->GetV(); + + char UtfName[NM*4]; + *UtfName=0; + if (NameSizeGetB(UtfName,NameSize); + UtfName[NameSize]=0; + } +#ifdef _WIN_ALL + UnixSlashToDos(UtfName,UtfName,ASIZE(UtfName)); +#endif + UtfToWide(UtfName,hd->RedirName,ASIZE(hd->RedirName)); + } + break; + case FHEXTRA_UOWNER: + { + uint Flags=(uint)Raw->GetV(); + hd->UnixOwnerNumeric=(Flags & FHEXTRA_UOWNER_NUMUID)!=0; + hd->UnixGroupNumeric=(Flags & FHEXTRA_UOWNER_NUMGID)!=0; + *hd->UnixOwnerName=*hd->UnixGroupName=0; + if ((Flags & FHEXTRA_UOWNER_UNAME)!=0) + { + size_t Length=(size_t)Raw->GetV(); + Length=Min(Length,ASIZE(hd->UnixOwnerName)-1); + Raw->GetB(hd->UnixOwnerName,Length); + hd->UnixOwnerName[Length]=0; + } + if ((Flags & FHEXTRA_UOWNER_GNAME)!=0) + { + size_t Length=(size_t)Raw->GetV(); + Length=Min(Length,ASIZE(hd->UnixGroupName)-1); + Raw->GetB(hd->UnixGroupName,Length); + hd->UnixGroupName[Length]=0; + } +#ifdef _UNIX + if (hd->UnixOwnerNumeric) + hd->UnixOwnerID=(uid_t)Raw->GetV(); + if (hd->UnixGroupNumeric) + hd->UnixGroupID=(gid_t)Raw->GetV(); +#else + // Need these fields in Windows too for 'list' command, + // but uid_t and gid_t are not defined. + if (hd->UnixOwnerNumeric) + hd->UnixOwnerID=(uint)Raw->GetV(); + if (hd->UnixGroupNumeric) + hd->UnixGroupID=(uint)Raw->GetV(); +#endif + hd->UnixOwnerSet=true; + } + break; + case FHEXTRA_SUBDATA: + { + // RAR 5.21 and earlier set FHEXTRA_SUBDATA size to 1 less than + // required. It did not hurt extraction, because UnRAR 5.21 + // and earlier ignored this field and set FieldSize as data left + // in entire extra area. But now we set the correct field size + // and set FieldSize based on actual extra record size, + // so we need to adjust it for those older archives here. + // FHEXTRA_SUBDATA in those archives always belongs to HEAD_SERVICE + // and always is last in extra area. So since its size is by 1 + // less than needed, we always have 1 byte left in extra area, + // which fact we use here to detect such archives. + if (bb->HeaderType==HEAD_SERVICE && Raw->Size()-NextPos==1) + FieldSize++; + + hd->SubData.Alloc((size_t)FieldSize); + Raw->GetB(hd->SubData.Addr(0),(size_t)FieldSize); + } + break; + } + } + + Raw->SetPos(NextPos); + } +} + + +#ifndef SFX_MODULE +size_t Archive::ReadHeader14() +{ + RawRead Raw(this); + if (CurBlockPos<=(int64)SFXSize) + { + Raw.Read(SIZEOF_MAINHEAD14); + MainHead.Reset(); + byte Mark[4]; + Raw.GetB(Mark,4); + uint HeadSize=Raw.Get2(); + byte Flags=Raw.Get1(); + NextBlockPos=CurBlockPos+HeadSize; + CurHeaderType=HEAD_MAIN; + + Volume=(Flags & MHD_VOLUME)!=0; + Solid=(Flags & MHD_SOLID)!=0; + Locked=(Flags & MHD_LOCK)!=0; + MainHead.CommentInHeader=(Flags & MHD_COMMENT)!=0; + MainHead.PackComment=(Flags & MHD_PACK_COMMENT)!=0; + } + else + { + Raw.Read(SIZEOF_FILEHEAD14); + FileHead.Reset(); + + FileHead.HeaderType=HEAD_FILE; + FileHead.DataSize=Raw.Get4(); + FileHead.UnpSize=Raw.Get4(); + FileHead.FileHash.Type=HASH_RAR14; + FileHead.FileHash.CRC32=Raw.Get2(); + FileHead.HeadSize=Raw.Get2(); + uint FileTime=Raw.Get4(); + FileHead.FileAttr=Raw.Get1(); + FileHead.Flags=Raw.Get1()|LONG_BLOCK; + FileHead.UnpVer=(Raw.Get1()==2) ? 13 : 10; + size_t NameSize=Raw.Get1(); + FileHead.Method=Raw.Get1(); + + FileHead.SplitBefore=(FileHead.Flags & LHD_SPLIT_BEFORE)!=0; + FileHead.SplitAfter=(FileHead.Flags & LHD_SPLIT_AFTER)!=0; + FileHead.Encrypted=(FileHead.Flags & LHD_PASSWORD)!=0; + FileHead.CryptMethod=FileHead.Encrypted ? CRYPT_RAR13:CRYPT_NONE; + + FileHead.PackSize=FileHead.DataSize; + FileHead.WinSize=0x10000; + + FileHead.mtime.SetDos(FileTime); + + Raw.Read(NameSize); + + char FileName[NM]; + Raw.GetB((byte *)FileName,Min(NameSize,ASIZE(FileName))); + FileName[NameSize]=0; + IntToExt(FileName,FileName,ASIZE(FileName)); + CharToWide(FileName,FileHead.FileName,ASIZE(FileHead.FileName)); + ConvertNameCase(FileHead.FileName); + + if (Raw.Size()!=0) + NextBlockPos=CurBlockPos+FileHead.HeadSize+FileHead.PackSize; + CurHeaderType=HEAD_FILE; + } + return NextBlockPos>CurBlockPos ? Raw.Size() : 0; +} +#endif + + +#ifndef SFX_MODULE +void Archive::ConvertNameCase(wchar *Name) +{ + if (Cmd->ConvertNames==NAMES_UPPERCASE) + wcsupper(Name); + if (Cmd->ConvertNames==NAMES_LOWERCASE) + wcslower(Name); +} +#endif + + +bool Archive::IsArcDir() +{ + return FileHead.Dir; +} + + +void Archive::ConvertAttributes() +{ +#if defined(_WIN_ALL) || defined(_EMX) + if (FileHead.HSType!=HSYS_WINDOWS) + FileHead.FileAttr=FileHead.Dir ? 0x10 : 0x20; +#endif +#ifdef _UNIX + // umask defines which permission bits must not be set by default + // when creating a file or directory. The typical default value + // for the process umask is S_IWGRP | S_IWOTH (octal 022), + // resulting in 0644 mode for new files. + // Normally umask is applied automatically when creating a file, + // but we set attributes with chmod later, so we need to calculate + // resulting attributes here. We do it only for non-Unix archives. + // We restore native Unix attributes as is, because it can be backup. + static mode_t mask = (mode_t) -1; + + if (mask == (mode_t) -1) + { + // umask call returns the current umask value. Argument (022) is not + // really important here. + mask = umask(022); + + // Restore the original umask value, which was changed to 022 above. + umask(mask); + } + + switch(FileHead.HSType) + { + case HSYS_WINDOWS: + { + // Mapping MSDOS, OS/2 and Windows file attributes to Unix. + + if (FileHead.FileAttr & 0x10) // FILE_ATTRIBUTE_DIRECTORY + { + // For directories we use 0777 mask. + FileHead.FileAttr=0777 & ~mask; + } + else + if (FileHead.FileAttr & 1) // FILE_ATTRIBUTE_READONLY + { + // For read only files we use 0444 mask with 'w' bits turned off. + FileHead.FileAttr=0444 & ~mask; + } + else + { + // umask does not set +x for regular files, so we use 0666 + // instead of 0777 as for directories. + FileHead.FileAttr=0666 & ~mask; + } + } + break; + case HSYS_UNIX: + break; + default: + if (FileHead.Dir) + FileHead.FileAttr=0x41ff & ~mask; + else + FileHead.FileAttr=0x81b6 & ~mask; + break; + } +#endif +} + + +void Archive::ConvertFileHeader(FileHeader *hd) +{ + if (Format==RARFMT15 && hd->UnpVer<20 && (hd->FileAttr & 0x10)) + hd->Dir=true; + if (hd->HSType==HSYS_UNKNOWN) + if (hd->Dir) + hd->FileAttr=0x10; + else + hd->FileAttr=0x20; + +#ifdef _WIN_ALL + if (hd->HSType==HSYS_UNIX) // Convert Unix, OS X and Android decomposed chracters to Windows precomposed. + ConvertToPrecomposed(hd->FileName,ASIZE(hd->FileName)); +#endif + + for (wchar *s=hd->FileName;*s!=0;s++) + { +#ifdef _UNIX + // Backslash is the invalid character for Windows file headers, + // but it can present in Unix file names extracted in Unix. + if (*s=='\\' && Format==RARFMT50 && hd->HSType==HSYS_WINDOWS) + *s='_'; +#endif + +#if defined(_WIN_ALL) || defined(_EMX) + // RAR 5.0 archives do not use '\' as path separator, so if we see it, + // it means that it is a part of Unix file name, which we cannot + // extract in Windows. + if (*s=='\\' && Format==RARFMT50) + *s='_'; + + // ':' in file names is allowed in Unix, but not in Windows. + // Even worse, file data will be written to NTFS stream on NTFS, + // so automatic name correction on file create error in extraction + // routine does not work. In Windows and DOS versions we better + // replace ':' now. + if (*s==':') + *s='_'; +#endif + + // This code must be performed only after other path separator checks, + // because it produces backslashes illegal for some of checks above. + // Backslash is allowed in file names in Unix, but not in Windows. + // Still, RAR 4.x uses backslashes as path separator even in Unix. + // Forward slash is not allowed in both systems. In RAR 5.0 we use + // the forward slash as universal path separator. + if (*s=='/' || *s=='\\' && Format!=RARFMT50) + *s=CPATHDIVIDER; + } +} + + +int64 Archive::GetStartPos() +{ + int64 StartPos=SFXSize+MarkHead.HeadSize; + if (Format==RARFMT15) + StartPos+=MainHead.HeadSize; + else // RAR 5.0. + StartPos+=CryptHead.HeadSize+FullHeaderSize(MainHead.HeadSize); + return StartPos; +} + + +bool Archive::ReadSubData(Array *UnpData,File *DestFile) +{ + if (BrokenHeader) + { + uiMsg(UIERROR_SUBHEADERBROKEN,FileName); + ErrHandler.SetErrorCode(RARX_CRC); + return false; + } + if (SubHead.Method>5 || SubHead.UnpVer>(Format==RARFMT50 ? VER_UNPACK5:VER_UNPACK)) + { + uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); + return false; + } + + if (SubHead.PackSize==0 && !SubHead.SplitAfter) + return true; + + SubDataIO.Init(); + Unpack Unpack(&SubDataIO); + Unpack.Init(SubHead.WinSize,false); + + if (DestFile==NULL) + { + if (SubHead.UnpSize>0x1000000) + { + // So huge allocation must never happen in valid archives. + uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName); + return false; + } + if (UnpData==NULL) + SubDataIO.SetTestMode(true); + else + { + UnpData->Alloc((size_t)SubHead.UnpSize); + SubDataIO.SetUnpackToMemory(&(*UnpData)[0],(uint)SubHead.UnpSize); + } + } + if (SubHead.Encrypted) + if (Cmd->Password.IsSet()) + SubDataIO.SetEncryption(false,SubHead.CryptMethod,&Cmd->Password, + SubHead.SaltSet ? SubHead.Salt:NULL,SubHead.InitV, + SubHead.Lg2Count,SubHead.HashKey,SubHead.PswCheck); + else + return false; + SubDataIO.UnpHash.Init(SubHead.FileHash.Type,1); + SubDataIO.SetPackedSizeToRead(SubHead.PackSize); + SubDataIO.EnableShowProgress(false); + SubDataIO.SetFiles(this,DestFile); + SubDataIO.UnpVolume=SubHead.SplitAfter; + SubDataIO.SetSubHeader(&SubHead,NULL); + Unpack.SetDestSize(SubHead.UnpSize); + if (SubHead.Method==0) + CmdExtract::UnstoreFile(SubDataIO,SubHead.UnpSize); + else + Unpack.DoUnpack(SubHead.UnpVer,false); + + if (!SubDataIO.UnpHash.Cmp(&SubHead.FileHash,SubHead.UseHashKey ? SubHead.HashKey:NULL)) + { + uiMsg(UIERROR_SUBHEADERDATABROKEN,FileName,SubHead.FileName); + ErrHandler.SetErrorCode(RARX_CRC); + if (UnpData!=NULL) + UnpData->Reset(); + return false; + } + return true; +} diff --git a/reference/unrar/array.hpp b/reference/unrar/array.hpp new file mode 100644 index 00000000..20d258d5 --- /dev/null +++ b/reference/unrar/array.hpp @@ -0,0 +1,191 @@ +#ifndef _RAR_ARRAY_ +#define _RAR_ARRAY_ + +extern ErrorHandler ErrHandler; + +template class Array +{ + private: + T *Buffer; + size_t BufSize; + size_t AllocSize; + size_t MaxSize; + bool Secure; // Clean memory if true. + public: + Array(); + Array(size_t Size); + Array(const Array &Src); // Copy constructor. + ~Array(); + inline void CleanData(); + inline T& operator [](size_t Item) const; + inline T* operator + (size_t Pos); + inline size_t Size(); // Returns the size in items, not in bytes. + void Add(size_t Items); + void Alloc(size_t Items); + void Reset(); + void SoftReset(); + void operator = (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;} +}; + + +template void Array::CleanData() +{ + Buffer=NULL; + BufSize=0; + AllocSize=0; + MaxSize=0; + Secure=false; +} + + +template Array::Array() +{ + CleanData(); +} + + +template Array::Array(size_t Size) +{ + CleanData(); + Add(Size); +} + + +// Copy constructor in case we need to pass an object as value. +template Array::Array(const Array &Src) +{ + CleanData(); + Alloc(Src.BufSize); + if (Src.BufSize!=0) + memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); +} + + +template Array::~Array() +{ + if (Buffer!=NULL) + { + if (Secure) + cleandata(Buffer,AllocSize*sizeof(T)); + free(Buffer); + } +} + + +template inline T& Array::operator [](size_t Item) const +{ + return Buffer[Item]; +} + + +template inline T* Array::operator +(size_t Pos) +{ + return Buffer+Pos; +} + + +template inline size_t Array::Size() +{ + return BufSize; +} + + +template void Array::Add(size_t Items) +{ + BufSize+=Items; + if (BufSize>AllocSize) + { + if (MaxSize!=0 && BufSize>MaxSize) + { + ErrHandler.GeneralErrMsg(L"Maximum allowed array size (%u) is exceeded",MaxSize); + ErrHandler.MemoryError(); + } + + 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(); + } + Buffer=NewBuffer; + AllocSize=NewSize; + } +} + + +template void Array::Alloc(size_t Items) +{ + if (Items>AllocSize) + Add(Items-BufSize); + else + BufSize=Items; +} + + +template void Array::Reset() +{ + if (Buffer!=NULL) + { + free(Buffer); + Buffer=NULL; + } + BufSize=0; + AllocSize=0; +} + + +// Reset buffer size, but preserve already allocated memory if any, +// so we can reuse it without wasting time to allocation. +template void Array::SoftReset() +{ + BufSize=0; +} + + +template void Array::operator =(Array &Src) +{ + Reset(); + Alloc(Src.BufSize); + if (Src.BufSize!=0) + memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); +} + + +template void Array::Push(T Item) +{ + Add(1); + (*this)[Size()-1]=Item; +} + + +template void Array::Append(T *Items,size_t Count) +{ + size_t CurSize=Size(); + Add(Count); + memcpy(Buffer+CurSize,Items,Count*sizeof(T)); +} + +#endif diff --git a/reference/unrar/blake2s.cpp b/reference/unrar/blake2s.cpp new file mode 100644 index 00000000..317603da --- /dev/null +++ b/reference/unrar/blake2s.cpp @@ -0,0 +1,183 @@ +// Based on public domain code written in 2012 by Samuel Neves + +#include "rar.hpp" + +#ifdef USE_SSE +#include "blake2s_sse.cpp" +#endif + +static void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth); +static void blake2s_update( blake2s_state *S, const byte *in, size_t inlen ); +static void blake2s_final( blake2s_state *S, byte *digest ); + +#include "blake2sp.cpp" + +static const uint32 blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const byte blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + +static inline void blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = ~0U; +} + + +/* Some helper functions, not necessarily useful */ +static inline void blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = ~0U; +} + + +static inline void blake2s_increment_counter( blake2s_state *S, const uint32 inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + + +/* init2 xors IV with input parameter block */ +void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth) +{ +#ifdef USE_SSE + if (_SSE_Version>=SSE_SSE2) + blake2s_init_sse(); +#endif + + S->init(); // Clean data. + for( int i = 0; i < 8; ++i ) + S->h[i] = blake2s_IV[i]; + + S->h[0] ^= 0x02080020; // We use BLAKE2sp parameters block. + S->h[2] ^= node_offset; + S->h[3] ^= (node_depth<<16)|0x20000000; +} + + +#define G(r,i,m,a,b,c,d) \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = rotr32(d ^ a, 16); \ + c = c + d; \ + b = rotr32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = rotr32(d ^ a, 8); \ + c = c + d; \ + b = rotr32(b ^ c, 7); + + +static void blake2s_compress( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] ) +{ + uint32 m[16]; + uint32 v[16]; + + for( size_t i = 0; i < 16; ++i ) + m[i] = RawGet4( block + i * 4 ); + + for( size_t i = 0; i < 8; ++i ) + v[i] = S->h[i]; + + v[ 8] = blake2s_IV[0]; + v[ 9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; + + for ( uint r = 0; r <= 9; ++r ) // No gain on i7 if unrolled, but exe size grows. + { + G(r,0,m,v[ 0],v[ 4],v[ 8],v[12]); + G(r,1,m,v[ 1],v[ 5],v[ 9],v[13]); + G(r,2,m,v[ 2],v[ 6],v[10],v[14]); + G(r,3,m,v[ 3],v[ 7],v[11],v[15]); + G(r,4,m,v[ 0],v[ 5],v[10],v[15]); + G(r,5,m,v[ 1],v[ 6],v[11],v[12]); + G(r,6,m,v[ 2],v[ 7],v[ 8],v[13]); + G(r,7,m,v[ 3],v[ 4],v[ 9],v[14]); + } + + for( size_t i = 0; i < 8; ++i ) + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; +} + + +void blake2s_update( blake2s_state *S, const byte *in, size_t inlen ) +{ + while( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = 2 * BLAKE2S_BLOCKBYTES - left; + + if( inlen > fill ) + { + memcpy( S->buf + left, in, fill ); // Fill buffer + S->buflen += fill; + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + +#ifdef USE_SSE +#ifdef _WIN_32 // We use SSSE3 _mm_shuffle_epi8 only in x64 mode. + if (_SSE_Version>=SSE_SSE2) +#else + if (_SSE_Version>=SSE_SSSE3) +#endif + blake2s_compress_sse( S, S->buf ); + else + blake2s_compress( S, S->buf ); // Compress +#else + blake2s_compress( S, S->buf ); // Compress +#endif + + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left + S->buflen -= BLAKE2S_BLOCKBYTES; + in += fill; + inlen -= fill; + } + else // inlen <= fill + { + memcpy( S->buf + left, in, (size_t)inlen ); + S->buflen += (size_t)inlen; // Be lazy, do not compress + in += inlen; + inlen = 0; + } + } +} + + +void blake2s_final( blake2s_state *S, byte *digest ) +{ + if( S->buflen > BLAKE2S_BLOCKBYTES ) + { + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); + S->buflen -= BLAKE2S_BLOCKBYTES; + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen ); + } + + blake2s_increment_counter( S, ( uint32 )S->buflen ); + blake2s_set_lastblock( S ); + memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( int i = 0; i < 8; ++i ) /* Output full hash */ + RawPut4( S->h[i], digest + 4 * i ); +} + diff --git a/reference/unrar/blake2s.hpp b/reference/unrar/blake2s.hpp new file mode 100644 index 00000000..7dd71571 --- /dev/null +++ b/reference/unrar/blake2s.hpp @@ -0,0 +1,101 @@ +// Based on public domain code written in 2012 by Samuel Neves +#ifndef _RAR_BLAKE2_ +#define _RAR_BLAKE2_ + +#define BLAKE2_DIGEST_SIZE 32 + +enum blake2s_constant +{ + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32 +}; + + +// Alignment to 64 improves performance of both SSE and non-SSE versions. +// Alignment to n*16 is required for SSE version, so we selected 64. +// We use the custom alignment scheme instead of __declspec(align(x)), +// because it is less compiler dependent. Also the compiler directive +// does not help if structure is a member of class allocated through +// 'new' operator. +struct blake2s_state +{ + enum { BLAKE_ALIGNMENT = 64 }; + + // buffer and uint32 h[8], t[2], f[2]; + enum { BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES }; + + byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT]; + + byte *buf; // byte buf[2 * BLAKE2S_BLOCKBYTES]. + uint32 *h, *t, *f; // uint32 h[8], t[2], f[2]. + + size_t buflen; + byte last_node; + + blake2s_state() + { + set_pointers(); + } + + // Required when we declare and assign in the same command. + blake2s_state(blake2s_state &st) + { + set_pointers(); + *this=st; + } + + void set_pointers() + { + // Set aligned pointers. Must be done in constructor, not in Init(), + // so assignments like 'blake2sp_state res=blake2ctx' work correctly + // even if blake2sp_init is not called for 'res'. + buf = (byte *) ALIGN_VALUE(ubuf, BLAKE_ALIGNMENT); + h = (uint32 *) (buf + 2 * BLAKE2S_BLOCKBYTES); + t = h + 8; + f = t + 2; + } + + void init() + { + memset( ubuf, 0, sizeof( ubuf ) ); + buflen = 0; + last_node = 0; + } + + // Since we use pointers, the default = would work incorrectly. + blake2s_state& operator = (blake2s_state &st) + { + if (this != &st) + { + memcpy(buf, st.buf, BLAKE_DATA_SIZE); + buflen = st.buflen; + last_node = st.last_node; + } + return *this; + } +}; + + +#ifdef RAR_SMP +class ThreadPool; +#endif + +struct blake2sp_state +{ + blake2s_state S[8]; + blake2s_state R; + byte buf[8 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + +#ifdef RAR_SMP + ThreadPool *ThPool; + uint MaxThreads; +#endif +}; + +void blake2sp_init( blake2sp_state *S ); +void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen ); +void blake2sp_final( blake2sp_state *S, byte *digest ); + +#endif + diff --git a/reference/unrar/blake2s_sse.cpp b/reference/unrar/blake2s_sse.cpp new file mode 100644 index 00000000..1a02f210 --- /dev/null +++ b/reference/unrar/blake2s_sse.cpp @@ -0,0 +1,129 @@ +// Based on public domain code written in 2012 by Samuel Neves + +extern const byte blake2s_sigma[10][16]; + +// Initialization vector. +static __m128i blake2s_IV_0_3, blake2s_IV_4_7; + +#ifdef _WIN_64 +// Constants for cyclic rotation. Used in 64-bit mode in mm_rotr_epi32 macro. +static __m128i crotr8, crotr16; +#endif + +static void blake2s_init_sse() +{ + // We cannot initialize these 128 bit variables in place when declaring + // them globally, because global scope initialization is performed before + // our SSE check and it would make code incompatible with older non-SSE2 + // CPUs. Also we cannot initialize them as static inside of function + // using these variables, because SSE static initialization is not thread + // safe: first thread starts initialization and sets "init done" flag even + // if it is not done yet, second thread can attempt to access half-init + // SSE data. So we moved init code here. + + blake2s_IV_0_3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A ); + blake2s_IV_4_7 = _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ); + +#ifdef _WIN_64 + crotr8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 ); + crotr16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 ); +#endif +} + + +#define LOAD(p) _mm_load_si128( (__m128i *)(p) ) +#define STORE(p,r) _mm_store_si128((__m128i *)(p), r) + +#ifdef _WIN_32 +// 32-bit mode has less SSE2 registers and in MSVC2008 it is more efficient +// to not use _mm_shuffle_epi8 here. +#define mm_rotr_epi32(r, c) ( \ + _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) +#else +#define mm_rotr_epi32(r, c) ( \ + c==8 ? _mm_shuffle_epi8(r,crotr8) \ + : c==16 ? _mm_shuffle_epi8(r,crotr16) \ + : _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) ) +#endif + + +#define G1(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = mm_rotr_epi32(row4, 16); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = mm_rotr_epi32(row2, 12); + +#define G2(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = mm_rotr_epi32(row4, 8); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = mm_rotr_epi32(row2, 7); + +#define DIAGONALIZE(row1,row2,row3,row4) \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(2,1,0,3) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \ + row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(0,3,2,1) ); + +#define UNDIAGONALIZE(row1,row2,row3,row4) \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(0,3,2,1) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \ + row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) ); + +#ifdef _WIN_64 + // MSVC 2008 in x64 mode expands _mm_set_epi32 to store to stack and load + // from stack operations, which are slower than this code. + #define _mm_set_epi32(i3,i2,i1,i0) \ + _mm_unpacklo_epi32(_mm_unpacklo_epi32(_mm_cvtsi32_si128(i0),_mm_cvtsi32_si128(i2)), \ + _mm_unpacklo_epi32(_mm_cvtsi32_si128(i1),_mm_cvtsi32_si128(i3))) +#endif + +// Original BLAKE2 SSE4.1 message loading code was a little slower in x86 mode +// and about the same in x64 mode in our test. Perhaps depends on compiler. +// We also tried _mm_i32gather_epi32 and _mm256_i32gather_epi32 AVX2 gather +// instructions here, but they did not show any speed gain on i7-6700K. +#define SSE_ROUND(m,row,r) \ +{ \ + __m128i buf; \ + buf=_mm_set_epi32(m[blake2s_sigma[r][6]],m[blake2s_sigma[r][4]],m[blake2s_sigma[r][2]],m[blake2s_sigma[r][0]]); \ + G1(row[0],row[1],row[2],row[3],buf); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][7]],m[blake2s_sigma[r][5]],m[blake2s_sigma[r][3]],m[blake2s_sigma[r][1]]); \ + G2(row[0],row[1],row[2],row[3],buf); \ + DIAGONALIZE(row[0],row[1],row[2],row[3]); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][14]],m[blake2s_sigma[r][12]],m[blake2s_sigma[r][10]],m[blake2s_sigma[r][8]]); \ + G1(row[0],row[1],row[2],row[3],buf); \ + buf=_mm_set_epi32(m[blake2s_sigma[r][15]],m[blake2s_sigma[r][13]],m[blake2s_sigma[r][11]],m[blake2s_sigma[r][9]]); \ + G2(row[0],row[1],row[2],row[3],buf); \ + UNDIAGONALIZE(row[0],row[1],row[2],row[3]); \ +} + + +static int blake2s_compress_sse( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] ) +{ + __m128i row[4]; + __m128i ff0, ff1; + + const uint32 *m = ( uint32 * )block; + + row[0] = ff0 = LOAD( &S->h[0] ); + row[1] = ff1 = LOAD( &S->h[4] ); + + row[2] = blake2s_IV_0_3; + row[3] = _mm_xor_si128( blake2s_IV_4_7, LOAD( &S->t[0] ) ); + SSE_ROUND( m, row, 0 ); + SSE_ROUND( m, row, 1 ); + SSE_ROUND( m, row, 2 ); + SSE_ROUND( m, row, 3 ); + SSE_ROUND( m, row, 4 ); + SSE_ROUND( m, row, 5 ); + SSE_ROUND( m, row, 6 ); + SSE_ROUND( m, row, 7 ); + SSE_ROUND( m, row, 8 ); + SSE_ROUND( m, row, 9 ); + STORE( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row[0], row[2] ) ) ); + STORE( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row[1], row[3] ) ) ); + return 0; +} diff --git a/reference/unrar/blake2sp.cpp b/reference/unrar/blake2sp.cpp new file mode 100644 index 00000000..da645883 --- /dev/null +++ b/reference/unrar/blake2sp.cpp @@ -0,0 +1,153 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Written in 2012 by Samuel Neves + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see . +*/ + +#define PARALLELISM_DEGREE 8 + +void blake2sp_init( blake2sp_state *S ) +{ + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + + blake2s_init_param( &S->R, 0, 1 ); // Init root. + + for( uint i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_init_param( &S->S[i], i, 0 ); // Init leaf. + + S->R.last_node = 1; + S->S[PARALLELISM_DEGREE - 1].last_node = 1; +} + + +struct Blake2ThreadData +{ + void Update(); + blake2s_state *S; + const byte *in; + size_t inlen; +}; + + +void Blake2ThreadData::Update() +{ + size_t inlen__ = inlen; + const byte *in__ = ( const byte * )in; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { +#ifdef USE_SSE + // We gain 5% in i7 SSE mode by prefetching next data block. + if (_SSE_Version>=SSE_SSE && inlen__ >= 2 * PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES) + _mm_prefetch((char*)(in__ + PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES), _MM_HINT_T0); +#endif + blake2s_update( S, in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } +} + +#ifdef RAR_SMP +THREAD_PROC(Blake2Thread) +{ + Blake2ThreadData *td=(Blake2ThreadData *)Data; + td->Update(); +} +#endif + + +void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen ) +{ + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + + Blake2ThreadData btd_array[PARALLELISM_DEGREE]; + +#ifdef RAR_SMP + uint ThreadNumber = inlen < 0x1000 ? 1 : S->MaxThreads; + + if (ThreadNumber==6 || ThreadNumber==7) // 6 and 7 threads work slower than 4 here. + ThreadNumber=4; +#else + uint ThreadNumber=1; +#endif + + for (size_t id__=0;id__inlen = inlen; + btd->in = in + id__ * BLAKE2S_BLOCKBYTES; + btd->S = &S->S[id__]; + +#ifdef RAR_SMP + if (ThreadNumber>1) + S->ThPool->AddTask(Blake2Thread,(void*)btd); + else + btd->Update(); +#else + btd->Update(); +#endif + id__++; + } +#ifdef RAR_SMP + if (S->ThPool!=NULL) // Can be NULL in -mt1 mode. + S->ThPool->WaitDone(); +#endif // RAR_SMP + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, (size_t)inlen ); + + S->buflen = left + (size_t)inlen; +} + + +void blake2sp_final( blake2sp_state *S, byte *digest ) +{ + byte hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2S_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES; + + if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES; + + blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left ); + } + + blake2s_final( &S->S[i], hash[i] ); + } + + for( size_t i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( &S->R, hash[i], BLAKE2S_OUTBYTES ); + + blake2s_final( &S->R, digest ); +} diff --git a/reference/unrar/cmddata.cpp b/reference/unrar/cmddata.cpp new file mode 100644 index 00000000..758cd171 --- /dev/null +++ b/reference/unrar/cmddata.cpp @@ -0,0 +1,1400 @@ +#include "rar.hpp" + +CommandData::CommandData() +{ + Init(); +} + + +void CommandData::Init() +{ + RAROptions::Init(); + + *Command=0; + *ArcName=0; + FileLists=false; + + ListMode=RCLM_AUTO; + + BareOutput=false; + + + FileArgs.Reset(); + ExclArgs.Reset(); + InclArgs.Reset(); + StoreArgs.Reset(); + ArcNames.Reset(); + NextVolSizes.Reset(); +} + + +// Return the pointer to next position in the string and store dynamically +// allocated command line parameter in Par. +static const wchar *AllocCmdParam(const wchar *CmdLine,wchar **Par) +{ + const wchar *NextCmd=GetCmdParam(CmdLine,NULL,0); + if (NextCmd==NULL) + return NULL; + size_t ParSize=NextCmd-CmdLine+2; // Parameter size including the trailing zero. + *Par=(wchar *)malloc(ParSize*sizeof(wchar)); + if (*Par==NULL) + return NULL; + return GetCmdParam(CmdLine,*Par,ParSize); +} + + +#if !defined(SFX_MODULE) +void CommandData::ParseCommandLine(bool Preprocess,int argc, char *argv[]) +{ + *Command=0; + NoMoreSwitches=false; +#ifdef CUSTOM_CMDLINE_PARSER + // In Windows we may prefer to implement our own command line parser + // to avoid replacing \" by " in standard parser. Such replacing corrupts + // destination paths like "dest path\" in extraction commands. + // Also our own parser is Unicode compatible. + const wchar *CmdLine=GetCommandLine(); + + wchar *Par; + for (bool FirstParam=true;;FirstParam=false) + { + if ((CmdLine=AllocCmdParam(CmdLine,&Par))==NULL) + break; + if (!FirstParam) // First parameter is the executable name. + if (Preprocess) + PreprocessArg(Par); + else + ParseArg(Par); + free(Par); + } +#else + Array Arg; + for (int I=1;I EnvStrW(strlen(EnvStr)+1); + CharToWide(EnvStr,&EnvStrW[0],EnvStrW.Size()); + ProcessSwitchesString(&EnvStrW[0]); + } +} +#endif + + + +#if !defined(SFX_MODULE) +// Preprocess those parameters, which must be processed before the rest of +// command line. Return 'false' to stop further processing. +void CommandData::PreprocessArg(const wchar *Arg) +{ + if (IsSwitch(Arg[0]) && !NoMoreSwitches) + { + Arg++; + if (Arg[0]=='-' && Arg[1]==0) // Switch "--". + NoMoreSwitches=true; + if (wcsicomp(Arg,L"cfg-")==0) + ConfigDisabled=true; + if (wcsnicomp(Arg,L"ilog",4)==0) + { + // Ensure that correct log file name is already set + // if we need to report an error when processing the command line. + ProcessSwitch(Arg); + InitLogOptions(LogName,ErrlogCharset); + } + if (wcsnicomp(Arg,L"sc",2)==0) + { + // Process -sc before reading any file lists. + ProcessSwitch(Arg); + if (*LogName!=0) + InitLogOptions(LogName,ErrlogCharset); + } + } + else + if (*Command==0) + wcsncpy(Command,Arg,ASIZE(Command)); // Need for rar.ini. +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ReadConfig() +{ + StringList List; + if (ReadTextFile(DefConfigName,&List,true)) + { + wchar *Str; + while ((Str=List.GetString())!=NULL) + { + while (IsSpace(*Str)) + Str++; + if (wcsnicomp(Str,L"switches=",9)==0) + ProcessSwitchesString(Str+9); + if (*Command!=0) + { + wchar Cmd[16]; + wcsncpyz(Cmd,Command,ASIZE(Cmd)); + wchar C0=toupperw(Cmd[0]); + wchar C1=toupperw(Cmd[1]); + if (C0=='I' || C0=='L' || C0=='M' || C0=='S' || C0=='V') + Cmd[1]=0; + if (C0=='R' && (C1=='R' || C1=='V')) + Cmd[2]=0; + wchar SwName[16+ASIZE(Cmd)]; + swprintf(SwName,ASIZE(SwName),L"switches_%s=",Cmd); + size_t Length=wcslen(SwName); + if (wcsnicomp(Str,SwName,Length)==0) + ProcessSwitchesString(Str+Length); + } + } + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ProcessSwitchesString(const wchar *Str) +{ + wchar *Par; + while ((Str=AllocCmdParam(Str,&Par))!=NULL) + { + if (IsSwitch(*Par)) + ProcessSwitch(Par+1); + free(Par); + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::ProcessSwitch(const wchar *Switch) +{ + + switch(toupperw(Switch[0])) + { + case '@': + ListMode=Switch[1]=='+' ? RCLM_ACCEPT_LISTS:RCLM_REJECT_LISTS; + break; + case 'A': + switch(toupperw(Switch[1])) + { + case 'C': + ClearArc=true; + break; + case 'D': + AppendArcNameToPath=true; + break; +#ifndef SFX_MODULE + case 'G': + if (Switch[2]=='-' && Switch[3]==0) + GenerateArcName=0; + else + { + GenerateArcName=true; + wcsncpyz(GenerateMask,Switch+2,ASIZE(GenerateMask)); + } + break; +#endif + case 'I': + IgnoreGeneralAttr=true; + break; + case 'N': // Reserved for archive name. + break; + case 'O': + AddArcOnly=true; + break; + case 'P': + wcscpy(ArcPath,Switch+2); + break; + case 'S': + SyncFiles=true; + break; + default: + BadSwitch(Switch); + break; + } + break; + case 'C': + if (Switch[2]==0) + switch(toupperw(Switch[1])) + { + case '-': + DisableComment=true; + break; + case 'U': + ConvertNames=NAMES_UPPERCASE; + break; + case 'L': + ConvertNames=NAMES_LOWERCASE; + break; + } + break; + case 'D': + if (Switch[2]==0) + switch(toupperw(Switch[1])) + { + case 'S': + DisableSortSolid=true; + break; + case 'H': + OpenShared=true; + break; + case 'F': + DeleteFiles=true; + break; + } + break; + case 'E': + switch(toupperw(Switch[1])) + { + case 'P': + switch(Switch[2]) + { + case 0: + ExclPath=EXCL_SKIPWHOLEPATH; + break; + case '1': + ExclPath=EXCL_BASEPATH; + break; + case '2': + ExclPath=EXCL_SAVEFULLPATH; + break; + case '3': + ExclPath=EXCL_ABSPATH; + break; + } + break; + default: + if (Switch[1]=='+') + { + InclFileAttr|=GetExclAttr(Switch+2); + InclAttrSet=true; + } + else + ExclFileAttr|=GetExclAttr(Switch+1); + break; + } + break; + case 'F': + if (Switch[1]==0) + FreshFiles=true; + else + BadSwitch(Switch); + break; + case 'H': + switch (toupperw(Switch[1])) + { + case 'P': + EncryptHeaders=true; + if (Switch[2]!=0) + { + Password.Set(Switch+2); + cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); + } + else + if (!Password.IsSet()) + { + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + eprintf(L"\n"); + } + break; + default : + BadSwitch(Switch); + break; + } + break; + case 'I': + if (wcsnicomp(Switch+1,L"LOG",3)==0) + { + wcsncpyz(LogName,Switch[4]!=0 ? Switch+4:DefLogName,ASIZE(LogName)); + break; + } + if (wcsicomp(Switch+1,L"SND")==0) + { + Sound=true; + break; + } + if (wcsicomp(Switch+1,L"ERR")==0) + { + MsgStream=MSG_STDERR; + break; + } + if (wcsnicomp(Switch+1,L"EML",3)==0) + { + wcsncpyz(EmailTo,Switch[4]!=0 ? Switch+4:L"@",ASIZE(EmailTo)); + break; + } + if (wcsicomp(Switch+1,L"NUL")==0) + { + MsgStream=MSG_NULL; + break; + } + if (toupperw(Switch[1])=='D') + { + for (uint I=2;Switch[I]!=0;I++) + switch(toupperw(Switch[I])) + { + case 'Q': + MsgStream=MSG_ERRONLY; + break; + case 'C': + DisableCopyright=true; + break; + case 'D': + DisableDone=true; + break; + case 'P': + DisablePercentage=true; + break; + } + break; + } + if (wcsicomp(Switch+1,L"OFF")==0) + { + Shutdown=true; + break; + } + if (wcsicomp(Switch+1,L"VER")==0) + { + PrintVersion=true; + break; + } + break; + case 'K': + switch(toupperw(Switch[1])) + { + case 'B': + KeepBroken=true; + break; + case 0: + Lock=true; + break; + } + break; + case 'M': + switch(toupperw(Switch[1])) + { + case 'C': + { + const wchar *Str=Switch+2; + if (*Str=='-') + for (uint I=0;IMaxPoolThreads || Threads<1) + BadSwitch(Switch); + else + { + } + break; +#endif + default: + Method=Switch[1]-'0'; + if (Method>5 || Method<0) + BadSwitch(Switch); + break; + } + break; + case 'N': + case 'X': + if (Switch[1]!=0) + { + StringList *Args=toupperw(Switch[0])=='N' ? &InclArgs:&ExclArgs; + if (Switch[1]=='@' && !IsWildcard(Switch)) + { + RAR_CHARSET Charset=FilelistCharset; + +#if defined(_WIN_ALL) + // for compatibility reasons we use OEM encoding + // in Win32 console version by default + +// if (Charset==RCH_DEFAULT) +// Charset=RCH_OEM; +#endif + + ReadTextFile(Switch+2,Args,false,true,Charset,true,true,true); + } + else + Args->AddString(Switch+1); + } + break; + case 'O': + switch(toupperw(Switch[1])) + { + case '+': + Overwrite=OVERWRITE_ALL; + break; + case '-': + Overwrite=OVERWRITE_NONE; + break; + case 0: + Overwrite=OVERWRITE_FORCE_ASK; + break; +#ifdef _WIN_ALL + case 'C': + SetCompressedAttr=true; + break; +#endif + case 'H': + SaveHardLinks=true; + break; + + +#ifdef SAVE_LINKS + case 'L': + SaveSymLinks=true; + if (toupperw(Switch[2])=='A') + AbsoluteLinks=true; + break; +#endif +#ifdef _WIN_ALL + case 'N': + if (toupperw(Switch[2])=='I') + AllowIncompatNames=true; + break; +#endif + case 'R': + Overwrite=OVERWRITE_AUTORENAME; + break; +#ifdef _WIN_ALL + case 'S': + SaveStreams=true; + break; +#endif + case 'W': + ProcessOwners=true; + break; + default : + BadSwitch(Switch); + break; + } + break; + case 'P': + if (Switch[1]==0) + { + uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password); + eprintf(L"\n"); + } + else + { + Password.Set(Switch+1); + cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0])); + } + break; +#ifndef SFX_MODULE + case 'Q': + if (toupperw(Switch[1])=='O') + switch(toupperw(Switch[2])) + { + case 0: + QOpenMode=QOPEN_AUTO; + break; + case '-': + QOpenMode=QOPEN_NONE; + break; + case '+': + QOpenMode=QOPEN_ALWAYS; + break; + default: + BadSwitch(Switch); + break; + } + else + BadSwitch(Switch); + break; +#endif + case 'R': + switch(toupperw(Switch[1])) + { + case 0: + Recurse=RECURSE_ALWAYS; + break; + case '-': + Recurse=RECURSE_DISABLE; + break; + case '0': + Recurse=RECURSE_WILDCARDS; + break; + case 'I': + { + Priority=atoiw(Switch+2); + if (Priority<0 || Priority>15) + BadSwitch(Switch); + const wchar *ChPtr=wcschr(Switch+2,':'); + if (ChPtr!=NULL) + { + SleepTime=atoiw(ChPtr+1); + if (SleepTime>1000) + BadSwitch(Switch); + InitSystemOptions(SleepTime); + } + SetPriority(Priority); + } + break; + } + break; + case 'S': + if (IsDigit(Switch[1])) + { + Solid|=SOLID_COUNT; + SolidCount=atoiw(&Switch[1]); + } + else + switch(toupperw(Switch[1])) + { + case 0: + Solid|=SOLID_NORMAL; + break; + case '-': + Solid=SOLID_NONE; + break; + case 'E': + Solid|=SOLID_FILEEXT; + break; + case 'V': + Solid|=Switch[2]=='-' ? SOLID_VOLUME_DEPENDENT:SOLID_VOLUME_INDEPENDENT; + break; + case 'D': + Solid|=SOLID_VOLUME_DEPENDENT; + break; + case 'L': + if (IsDigit(Switch[2])) + FileSizeLess=atoilw(Switch+2); + break; + case 'M': + if (IsDigit(Switch[2])) + FileSizeMore=atoilw(Switch+2); + break; + case 'C': + { + bool AlreadyBad=false; // Avoid reporting "bad switch" several times. + + RAR_CHARSET rch=RCH_DEFAULT; + switch(toupperw(Switch[2])) + { + case 'A': + rch=RCH_ANSI; + break; + case 'O': + rch=RCH_OEM; + break; + case 'U': + rch=RCH_UNICODE; + break; + case 'F': + rch=RCH_UTF8; + break; + default : + BadSwitch(Switch); + AlreadyBad=true; + break; + }; + if (!AlreadyBad) + if (Switch[3]==0) + CommentCharset=FilelistCharset=ErrlogCharset=RedirectCharset=rch; + else + for (uint I=3;Switch[I]!=0 && !AlreadyBad;I++) + switch(toupperw(Switch[I])) + { + case 'C': + CommentCharset=rch; + break; + case 'L': + FilelistCharset=rch; + break; + case 'R': + RedirectCharset=rch; + break; + default: + BadSwitch(Switch); + AlreadyBad=true; + break; + } + } + break; + + } + break; + case 'T': + switch(toupperw(Switch[1])) + { + case 'K': + ArcTime=ARCTIME_KEEP; + break; + case 'L': + ArcTime=ARCTIME_LATEST; + break; + case 'O': + FileTimeBefore.SetAgeText(Switch+2); + break; + case 'N': + FileTimeAfter.SetAgeText(Switch+2); + break; + case 'B': + FileTimeBefore.SetIsoText(Switch+2); + break; + case 'A': + FileTimeAfter.SetIsoText(Switch+2); + break; + case 'S': + { + EXTTIME_MODE Mode=EXTTIME_HIGH3; + bool CommonMode=Switch[2]>='0' && Switch[2]<='4'; + if (CommonMode) + Mode=(EXTTIME_MODE)(Switch[2]-'0'); + if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore. + Mode=EXTTIME_HIGH3; + if (Switch[2]=='-') + Mode=EXTTIME_NONE; + if (CommonMode || Switch[2]=='-' || Switch[2]=='+' || Switch[2]==0) + xmtime=xctime=xatime=Mode; + else + { + if (Switch[3]>='0' && Switch[3]<='4') + Mode=(EXTTIME_MODE)(Switch[3]-'0'); + if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore. + Mode=EXTTIME_HIGH3; + if (Switch[3]=='-') + Mode=EXTTIME_NONE; + switch(toupperw(Switch[2])) + { + case 'M': + xmtime=Mode; + break; + case 'C': + xctime=Mode; + break; + case 'A': + xatime=Mode; + break; + } + } + } + break; + case '-': + Test=false; + break; + case 0: + Test=true; + break; + default: + BadSwitch(Switch); + break; + } + break; + case 'U': + if (Switch[1]==0) + UpdateFiles=true; + else + BadSwitch(Switch); + break; + case 'V': + switch(toupperw(Switch[1])) + { + case 'P': + VolumePause=true; + break; + case 'E': + if (toupperw(Switch[2])=='R') + VersionControl=atoiw(Switch+3)+1; + break; + case '-': + VolSize=0; + break; + default: + VolSize=VOLSIZE_AUTO; // UnRAR -v switch for list command. + break; + } + break; + case 'W': + wcsncpyz(TempPath,Switch+1,ASIZE(TempPath)); + AddEndSlash(TempPath,ASIZE(TempPath)); + break; + case 'Y': + AllYes=true; + break; + case 'Z': + if (Switch[1]==0) + { + // If comment file is not specified, we read data from stdin. + wcscpy(CommentFile,L"stdin"); + } + else + wcsncpyz(CommentFile,Switch+1,ASIZE(CommentFile)); + break; + case '?' : + OutHelp(RARX_SUCCESS); + break; + default : + BadSwitch(Switch); + break; + } +} +#endif + + +#if !defined(SFX_MODULE) +void CommandData::BadSwitch(const wchar *Switch) +{ + mprintf(St(MUnknownOption),Switch); + ErrHandler.Exit(RARX_USERERROR); +} +#endif + + +void CommandData::OutTitle() +{ + if (BareOutput || DisableCopyright) + return; +#if defined(__GNUC__) && defined(SFX_MODULE) + mprintf(St(MCopyrightS)); +#else +#ifndef SILENT + static bool TitleShown=false; + if (TitleShown) + return; + TitleShown=true; + + wchar Version[80]; + if (RARVER_BETA!=0) + swprintf(Version,ASIZE(Version),L"%d.%02d %ls %d",RARVER_MAJOR,RARVER_MINOR,St(MBeta),RARVER_BETA); + else + swprintf(Version,ASIZE(Version),L"%d.%02d",RARVER_MAJOR,RARVER_MINOR); +#if defined(_WIN_32) || defined(_WIN_64) + wcsncatz(Version,L" ",ASIZE(Version)); +#endif +#ifdef _WIN_32 + wcsncatz(Version,St(Mx86),ASIZE(Version)); +#endif +#ifdef _WIN_64 + wcsncatz(Version,St(Mx64),ASIZE(Version)); +#endif + if (PrintVersion) + { + mprintf(L"%s",Version); + exit(0); + } + mprintf(St(MUCopyright),Version,RARVER_YEAR); +#endif +#endif +} + + +inline bool CmpMSGID(MSGID i1,MSGID i2) +{ +#ifdef MSGID_INT + return i1==i2; +#else + // If MSGID is const char*, we cannot compare pointers only. + // Pointers to different instances of same string can differ, + // so we need to compare complete strings. + return wcscmp(i1,i2)==0; +#endif +} + +void CommandData::OutHelp(RAR_EXIT ExitCode) +{ +#if !defined(SILENT) + OutTitle(); + static MSGID Help[]={ +#ifdef SFX_MODULE + // Console SFX switches definition. + MCHelpCmd,MSHelpCmdE,MSHelpCmdT,MSHelpCmdV +#else + // UnRAR switches definition. + MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL, + MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm, + MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP, + MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU, + MCHelpSwDH,MCHelpSwEP,MCHelpSwEP3,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR, + MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwN,MCHelpSwNa,MCHelpSwNal, + MCHelpSwO,MCHelpSwOC,MCHelpSwOL,MCHelpSwOR,MCHelpSwOW,MCHelpSwP, + MCHelpSwPm,MCHelpSwR,MCHelpSwRI,MCHelpSwSC,MCHelpSwSL,MCHelpSwSM, + MCHelpSwTA,MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU, + MCHelpSwVUnr,MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal, + MCHelpSwY +#endif + }; + + for (uint I=0;IRewind(); + while (Args->GetString(CurMask,ASIZE(CurMask))) + { + wchar *LastMaskChar=PointToLastChar(CurMask); + bool DirMask=IsPathDiv(*LastMaskChar); // Mask for directories only. + + if (Dir) + { + // CheckName is a directory. + if (DirMask) + { + // We process the directory and have the directory exclusion mask. + // So let's convert "mask\" to "mask" and process it normally. + + *LastMaskChar=0; + } + else + { + // REMOVED, we want -npath\* to match empty folders too. + // If mask has wildcards in name part and does not have the trailing + // '\' character, we cannot use it for directories. + + // if (IsWildcard(PointToName(CurMask))) + // continue; + } + } + else + { + // If we process a file inside of directory excluded by "dirmask\". + // we want to exclude such file too. So we convert "dirmask\" to + // "dirmask\*". It is important for operations other than archiving + // with -x. When archiving with -x, directory matched by "dirmask\" + // is excluded from further scanning. + + if (DirMask) + wcsncatz(CurMask,L"*",ASIZE(CurMask)); + } + +#ifndef SFX_MODULE + if (CheckFullPath && IsFullPath(CurMask)) + { + // We do not need to do the special "*\" processing here, because + // unlike the "else" part of this "if", now we convert names to full + // format, so they all include the path, which is matched by "*\" + // correctly. Moreover, removing "*\" from mask would break + // the comparison, because now all names have the path. + + if (*FullName==0) + ConvertNameToFull(CheckName,FullName,ASIZE(FullName)); + if (CmpName(CurMask,FullName,MatchMode)) + return true; + } + else +#endif + { + wchar NewName[NM+2],*CurName=Name; + + // Important to convert before "*\" check below, so masks like + // d:*\something are processed properly. + wchar *CmpMask=ConvertPath(CurMask,NULL); + + if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1])) + { + // We want "*\name" to match 'name' not only in subdirectories, + // but also in the current directory. We convert the name + // from 'name' to '.\name' to be matched by "*\" part even if it is + // in current directory. + NewName[0]='.'; + NewName[1]=CPATHDIVIDER; + wcsncpyz(NewName+2,Name,ASIZE(NewName)-2); + CurName=NewName; + } + + if (CmpName(CmpMask,CurName,MatchMode)) + return true; + } + } + return false; +} + + +#ifndef SFX_MODULE +// Now this function performs only one task and only in Windows version: +// it skips symlinks to directories if -e1024 switch is specified. +// Symlinks are skipped in ScanTree class, so their entire contents +// is skipped too. Without this function we would check the attribute +// only directly before archiving, so we would skip the symlink record, +// but not the contents of symlinked directory. +bool CommandData::ExclDirByAttr(uint FileAttr) +{ +#ifdef _WIN_ALL + if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0 && + (ExclFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0) + return true; +#endif + return false; +} +#endif + + + + +#ifndef SFX_MODULE +// Return 'true' if we need to exclude the file from processing. +bool CommandData::TimeCheck(RarTime &ft) +{ + if (FileTimeBefore.IsSet() && ft>=FileTimeBefore) + return true; + if (FileTimeAfter.IsSet() && ft<=FileTimeAfter) + return true; + return false; +} +#endif + + +#ifndef SFX_MODULE +// Return 'true' if we need to exclude the file from processing. +bool CommandData::SizeCheck(int64 Size) +{ + if (FileSizeLess!=INT64NDF && Size>=FileSizeLess) + return(true); + if (FileSizeMore!=INT64NDF && Size<=FileSizeMore) + return(true); + return(false); +} +#endif + + + + +int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType, + wchar *MatchedArg,uint MatchedArgSize) +{ + if (MatchedArg!=NULL && MatchedArgSize>0) + *MatchedArg=0; + if (wcslen(FileHead.FileName)>=NM) + return 0; + bool Dir=FileHead.Dir; + if (ExclCheck(FileHead.FileName,Dir,false,true)) + return 0; +#ifndef SFX_MODULE + if (TimeCheck(FileHead.mtime)) + return 0; + if ((FileHead.FileAttr & ExclFileAttr)!=0 || InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0) + return 0; + if (!Dir && SizeCheck(FileHead.UnpSize)) + return 0; +#endif + wchar *ArgName; + FileArgs.Rewind(); + for (int StringCount=1;(ArgName=FileArgs.GetString())!=NULL;StringCount++) + if (CmpName(ArgName,FileHead.FileName,MatchType)) + { + if (ExactMatch!=NULL) + *ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0; + if (MatchedArg!=NULL) + wcsncpyz(MatchedArg,ArgName,MatchedArgSize); + return StringCount; + } + return 0; +} + + +void CommandData::ProcessCommand() +{ +#ifndef SFX_MODULE + + const wchar *SingleCharCommands=L"FUADPXETK"; + if (Command[0]!=0 && Command[1]!=0 && wcschr(SingleCharCommands,Command[0])!=NULL || *ArcName==0) + OutHelp(*Command==0 ? RARX_SUCCESS:RARX_USERERROR); // Return 'success' for 'rar' without parameters. + + const wchar *ArcExt=GetExt(ArcName); +#ifdef _UNIX + if (ArcExt==NULL && (!FileExist(ArcName) || IsDir(GetFileAttr(ArcName)))) + wcsncatz(ArcName,L".rar",ASIZE(ArcName)); +#else + if (ArcExt==NULL) + wcsncatz(ArcName,L".rar",ASIZE(ArcName)); +#endif + // Treat arcname.part1 as arcname.part1.rar. + if (ArcExt!=NULL && wcsnicomp(ArcExt,L".part",5)==0 && IsDigit(ArcExt[5]) && + !FileExist(ArcName)) + { + wchar Name[NM]; + wcsncpyz(Name,ArcName,ASIZE(Name)); + wcsncatz(Name,L".rar",ASIZE(Name)); + if (FileExist(Name)) + wcsncpyz(ArcName,Name,ASIZE(ArcName)); + } + + if (wcschr(L"AFUMD",*Command)==NULL) + { + if (GenerateArcName) + GenerateArchiveName(ArcName,ASIZE(ArcName),GenerateMask,false); + + StringList ArcMasks; + ArcMasks.AddString(ArcName); + ScanTree Scan(&ArcMasks,Recurse,SaveSymLinks,SCAN_SKIPDIRS); + FindData FindData; + while (Scan.GetNext(&FindData)==SCAN_SUCCESS) + AddArcName(FindData.Name); + } + else + AddArcName(ArcName); +#endif + + switch(Command[0]) + { + case 'P': + case 'X': + case 'E': + case 'T': + case 'I': + { + CmdExtract Extract(this); + Extract.DoExtract(); + } + break; +#ifndef SILENT + case 'V': + case 'L': + ListArchive(this); + break; + default: + OutHelp(RARX_USERERROR); +#endif + } + if (!BareOutput) + mprintf(L"\n"); +} + + +void CommandData::AddArcName(const wchar *Name) +{ + ArcNames.AddString(Name); +} + + +bool CommandData::GetArcName(wchar *Name,int MaxSize) +{ + return ArcNames.GetString(Name,MaxSize); +} + + +bool CommandData::IsSwitch(int Ch) +{ +#if defined(_WIN_ALL) || defined(_EMX) + return Ch=='-' || Ch=='/'; +#else + return Ch=='-'; +#endif +} + + +#ifndef SFX_MODULE +uint CommandData::GetExclAttr(const wchar *Str) +{ + if (IsDigit(*Str)) + return wcstol(Str,NULL,0); + + uint Attr=0; + while (*Str!=0) + { + switch(toupperw(*Str)) + { +#ifdef _UNIX + case 'D': + Attr|=S_IFDIR; + break; + case 'V': + Attr|=S_IFCHR; + break; +#elif defined(_WIN_ALL) || defined(_EMX) + case 'R': + Attr|=0x1; + break; + case 'H': + Attr|=0x2; + break; + case 'S': + Attr|=0x4; + break; + case 'D': + Attr|=0x10; + break; + case 'A': + Attr|=0x20; + break; +#endif + } + Str++; + } + return Attr; +} +#endif + + + + +#ifndef SFX_MODULE +bool CommandData::CheckWinSize() +{ + // Define 0x100000000 as macro to avoid troubles with older compilers. + const uint64 MaxDictSize=INT32TO64(1,0); + // Limit the dictionary size to 4 GB. + for (uint64 I=0x10000;I<=MaxDictSize;I*=2) + if (WinSize==I) + return true; + WinSize=0x400000; + return false; +} +#endif + + +#ifndef SFX_MODULE +void CommandData::ReportWrongSwitches(RARFORMAT Format) +{ + if (Format==RARFMT15) + { + if (HashType!=HASH_CRC32) + uiMsg(UIERROR_INCOMPATSWITCH,L"-ht",4); +#ifdef _WIN_ALL + if (SaveSymLinks) + uiMsg(UIERROR_INCOMPATSWITCH,L"-ol",4); +#endif + if (SaveHardLinks) + uiMsg(UIERROR_INCOMPATSWITCH,L"-oh",4); + +#ifdef _WIN_ALL + // Do not report a wrong dictionary size here, because we are not sure + // yet about archive format. We can switch to RAR5 mode later + // if we update RAR5 archive. + + +#endif + if (QOpenMode!=QOPEN_AUTO) + uiMsg(UIERROR_INCOMPATSWITCH,L"-qo",4); + } + if (Format==RARFMT50) + { + } +} +#endif diff --git a/reference/unrar/cmddata.hpp b/reference/unrar/cmddata.hpp new file mode 100644 index 00000000..e12d4fbc --- /dev/null +++ b/reference/unrar/cmddata.hpp @@ -0,0 +1,64 @@ +#ifndef _RAR_CMDDATA_ +#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" + +enum RAR_CMD_LIST_MODE {RCLM_AUTO,RCLM_REJECT_LISTS,RCLM_ACCEPT_LISTS}; + +class CommandData:public RAROptions +{ + private: + void ProcessSwitchesString(const wchar *Str); + void ProcessSwitch(const wchar *Switch); + void BadSwitch(const wchar *Switch); + uint GetExclAttr(const wchar *Str); + + bool FileLists; + bool NoMoreSwitches; + RAR_CMD_LIST_MODE ListMode; + bool BareOutput; + public: + CommandData(); + void Init(); + + void ParseCommandLine(bool Preprocess,int argc, char *argv[]); + void ParseArg(wchar *ArgW); + void ParseDone(); + void ParseEnvVar(); + void ReadConfig(); + void PreprocessArg(const wchar *Arg); + void OutTitle(); + void OutHelp(RAR_EXIT ExitCode); + bool IsSwitch(int Ch); + bool ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList); + static bool CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode); + bool ExclDirByAttr(uint FileAttr); + bool TimeCheck(RarTime &ft); + bool SizeCheck(int64 Size); + bool AnyFiltersActive(); + int IsProcessFile(FileHeader &FileHead,bool *ExactMatch=NULL,int MatchType=MATCH_WILDSUBPATH, + wchar *MatchedArg=NULL,uint MatchedArgSize=0); + void ProcessCommand(); + void AddArcName(const wchar *Name); + bool GetArcName(wchar *Name,int MaxSize); + bool CheckWinSize(); + + int GetRecoverySize(const wchar *Str,int DefSize); + +#ifndef SFX_MODULE + void ReportWrongSwitches(RARFORMAT Format); +#endif + + wchar Command[NM+16]; + + wchar ArcName[NM]; + + StringList FileArgs; + StringList ExclArgs; + StringList InclArgs; + StringList ArcNames; + StringList StoreArgs; +}; + +#endif diff --git a/reference/unrar/coder.cpp b/reference/unrar/coder.cpp new file mode 100644 index 00000000..9d971a8a --- /dev/null +++ b/reference/unrar/coder.cpp @@ -0,0 +1,48 @@ + + +inline unsigned int RangeCoder::GetChar() +{ + return(UnpackRead->GetChar()); +} + + +void RangeCoder::InitDecoder(Unpack *UnpackRead) +{ + RangeCoder::UnpackRead=UnpackRead; + + low=code=0; + range=uint(-1); + for (int i=0;i < 4;i++) + code=(code << 8) | GetChar(); +} + + +// (int) cast before "low" added only to suppress compiler warnings. +#define ARI_DEC_NORMALIZE(code,low,range,read) \ +{ \ + while ((low^(low+range))GetChar(); \ + range <<= 8; \ + low <<= 8; \ + } \ +} + + +inline int RangeCoder::GetCurrentCount() +{ + return (code-low)/(range /= SubRange.scale); +} + + +inline uint RangeCoder::GetCurrentShiftCount(uint SHIFT) +{ + return (code-low)/(range >>= SHIFT); +} + + +inline void RangeCoder::Decode() +{ + low += range*SubRange.LowCount; + range *= SubRange.HighCount-SubRange.LowCount; +} diff --git a/reference/unrar/coder.hpp b/reference/unrar/coder.hpp new file mode 100644 index 00000000..7b36ff21 --- /dev/null +++ b/reference/unrar/coder.hpp @@ -0,0 +1,23 @@ +/**************************************************************************** + * Contents: 'Carryless rangecoder' by Dmitry Subbotin * + ****************************************************************************/ + + +class RangeCoder +{ + public: + void InitDecoder(Unpack *UnpackRead); + inline int GetCurrentCount(); + inline uint GetCurrentShiftCount(uint SHIFT); + inline void Decode(); + inline void PutChar(unsigned int c); + inline unsigned int GetChar(); + + uint low, code, range; + struct SUBRANGE + { + uint LowCount, HighCount, scale; + } SubRange; + + Unpack *UnpackRead; +}; diff --git a/reference/unrar/compress.hpp b/reference/unrar/compress.hpp new file mode 100644 index 00000000..8e4f1ed3 --- /dev/null +++ b/reference/unrar/compress.hpp @@ -0,0 +1,50 @@ +#ifndef _RAR_COMPRESS_ +#define _RAR_COMPRESS_ + +// Combine pack and unpack constants to class to avoid polluting global +// namespace with numerous short names. +class PackDef +{ + public: + static const uint MAX_LZ_MATCH = 0x1001; + static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + static const uint LOW_DIST_REP_COUNT = 16; + + static const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC = 64; + static const uint LDC = 16; + static const uint RC = 44; + static const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC; + static const uint BC = 20; + + static const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC30 = 60; + static const uint LDC30 = 17; + static const uint RC30 = 28; + static const uint BC30 = 20; + static const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + + static const uint NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + static const uint DC20 = 48; + static const uint RC20 = 28; + static const uint BC20 = 19; + static const uint MC20 = 257; + + // Largest alphabet size among all values listed above. + static const uint LARGEST_TABLE_SIZE = 306; + + enum { + CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + }; +}; + + +enum FilterType { + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE +}; + +#endif diff --git a/reference/unrar/consio.cpp b/reference/unrar/consio.cpp new file mode 100644 index 00000000..2f71a991 --- /dev/null +++ b/reference/unrar/consio.cpp @@ -0,0 +1,352 @@ +#include "rar.hpp" +#include "log.cpp" + +static MESSAGE_TYPE MsgStream=MSG_STDOUT; +static RAR_CHARSET RedirectCharset=RCH_DEFAULT; + +const int MaxMsgSize=2*NM+2048; + +static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false; + +#ifdef _WIN_ALL +static bool IsRedirected(DWORD nStdHandle) +{ + HANDLE hStd=GetStdHandle(nStdHandle); + DWORD Mode; + return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0; +} +#endif + + +void InitConsole() +{ +#ifdef _WIN_ALL + // We want messages like file names or progress percent to be printed + // immediately. Use only in Windows, in Unix they can cause wprintf %ls + // to fail with non-English strings. + setbuf(stdout,NULL); + setbuf(stderr,NULL); + + // Detect if output is redirected and set output mode properly. + // We do not want to send Unicode output to files and especially to pipes + // like '|more', which cannot handle them correctly in Windows. + // In Unix console output is UTF-8 and it is handled correctly + // when redirecting, so no need to perform any adjustments. + StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE); + StderrRedirected=IsRedirected(STD_ERROR_HANDLE); + StdinRedirected=IsRedirected(STD_INPUT_HANDLE); +#ifdef _MSC_VER + if (!StdoutRedirected) + _setmode(_fileno(stdout), _O_U16TEXT); + if (!StderrRedirected) + _setmode(_fileno(stderr), _O_U16TEXT); +#endif +#elif defined(_UNIX) + StdoutRedirected=!isatty(fileno(stdout)); + StderrRedirected=!isatty(fileno(stderr)); + StdinRedirected=!isatty(fileno(stdin)); +#endif +} + + +void InitConsoleOptions(MESSAGE_TYPE MsgStream,RAR_CHARSET RedirectCharset) +{ + ::MsgStream=MsgStream; + ::RedirectCharset=RedirectCharset; +} + + +#ifndef SILENT +static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist) +{ + // This buffer is for format string only, not for entire output, + // so it can be short enough. + wchar fmtw[1024]; + PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw)); +#ifdef _WIN_ALL + safebuf wchar Msg[MaxMsgSize]; + if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected) + { + HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE); + vswprintf(Msg,ASIZE(Msg),fmtw,arglist); + DWORD Written; + if (RedirectCharset==RCH_UNICODE) + WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL); + else + { + // Avoid Unicode for redirect in Windows, it does not work with pipes. + safebuf char MsgA[MaxMsgSize]; + if (RedirectCharset==RCH_UTF8) + WideToUtf(Msg,MsgA,ASIZE(MsgA)); + else + WideToChar(Msg,MsgA,ASIZE(MsgA)); + if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM) + CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding. + + // We already converted \n to \r\n above, so we use WriteFile instead + // of C library to avoid unnecessary additional conversion. + WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL); + } + return; + } + // MSVC2008 vfwprintf writes every character to console separately + // and it is too slow. We use direct WriteConsole call instead. + vswprintf(Msg,ASIZE(Msg),fmtw,arglist); + HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE); + DWORD Written; + WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL); +#else + vfwprintf(dest,fmtw,arglist); + // We do not use setbuf(NULL) in Unix (see comments in InitConsole). + fflush(dest); +#endif +} + + +void mprintf(const wchar *fmt,...) +{ + if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY) + return; + + fflush(stderr); // Ensure proper message order. + + va_list arglist; + va_start(arglist,fmt); + FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout; + cvt_wprintf(dest,fmt,arglist); + va_end(arglist); +} +#endif + + +#ifndef SILENT +void eprintf(const wchar *fmt,...) +{ + if (MsgStream==MSG_NULL) + return; + + fflush(stdout); // Ensure proper message order. + + va_list arglist; + va_start(arglist,fmt); + cvt_wprintf(stderr,fmt,arglist); + va_end(arglist); +} +#endif + + +#ifndef SILENT +static void GetPasswordText(wchar *Str,uint MaxLength) +{ + if (MaxLength==0) + return; + if (StdinRedirected) + getwstr(Str,MaxLength); // Read from pipe or redirected file. + else + { +#ifdef _WIN_ALL + HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE); + HANDLE hConOut=GetStdHandle(STD_OUTPUT_HANDLE); + DWORD ConInMode,ConOutMode; + DWORD Read=0; + GetConsoleMode(hConIn,&ConInMode); + GetConsoleMode(hConOut,&ConOutMode); + SetConsoleMode(hConIn,ENABLE_LINE_INPUT); + SetConsoleMode(hConOut,ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT); + + ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL); + Str[Read]=0; + SetConsoleMode(hConIn,ConInMode); + SetConsoleMode(hConOut,ConOutMode); +#else + char StrA[MAXPASSWORD]; +#if defined(_EMX) || defined (__VMS) + fgets(StrA,ASIZE(StrA)-1,stdin); +#elif defined(__sun) + strncpyz(StrA,getpassphrase(""),ASIZE(StrA)); +#else + strncpyz(StrA,getpass(""),ASIZE(StrA)); +#endif + CharToWide(StrA,Str,MaxLength); + cleandata(StrA,sizeof(StrA)); +#endif + } + Str[MaxLength-1]=0; + RemoveLF(Str); +} +#endif + + +#ifndef SILENT +bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) +{ + if (!StdinRedirected) + uiAlarm(UIALARM_QUESTION); + + while (true) + { + if (!StdinRedirected) + if (Type==UIPASSWORD_GLOBAL) + eprintf(L"\n%s: ",St(MAskPsw)); + else + eprintf(St(MAskPswFor),FileName); + + wchar PlainPsw[MAXPASSWORD]; + GetPasswordText(PlainPsw,ASIZE(PlainPsw)); + if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL) + return false; + if (!StdinRedirected && Type==UIPASSWORD_GLOBAL) + { + eprintf(St(MReAskPsw)); + wchar CmpStr[MAXPASSWORD]; + GetPasswordText(CmpStr,ASIZE(CmpStr)); + if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0) + { + eprintf(St(MNotMatchPsw)); + cleandata(PlainPsw,sizeof(PlainPsw)); + cleandata(CmpStr,sizeof(CmpStr)); + continue; + } + cleandata(CmpStr,sizeof(CmpStr)); + } + Password->Set(PlainPsw); + cleandata(PlainPsw,sizeof(PlainPsw)); + break; + } + return true; +} +#endif + + +#ifndef SILENT +bool getwstr(wchar *str,size_t n) +{ + // Print buffered prompt title function before waiting for input. + fflush(stderr); + + *str=0; +#if defined(_WIN_ALL) + // fgetws does not work well with non-English text in Windows, + // so we do not use it. + if (StdinRedirected) // ReadConsole does not work if redirected. + { + // fgets does not work well with pipes in Windows in our test. + // Let's use files. + Array StrA(n*4); // Up to 4 UTF-8 characters per wchar_t. + File SrcFile; + SrcFile.SetHandleType(FILE_HANDLESTD); + int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1); + if (ReadSize<=0) + { + // Looks like stdin is a null device. We can enter to infinite loop + // calling Ask(), so let's better exit. + ErrHandler.Exit(RARX_USERBREAK); + } + StrA[ReadSize]=0; + CharToWide(&StrA[0],str,n); + cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords. + } + else + { + DWORD ReadSize=0; + if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0) + return false; + str[ReadSize]=0; + } +#else + if (fgetws(str,n,stdin)==NULL) + ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop. +#endif + RemoveLF(str); + return true; +} +#endif + + +#ifndef SILENT +// We allow this function to return 0 in case of invalid input, +// because it might be convenient to press Enter to some not dangerous +// prompts like "insert disk with next volume". We should call this function +// again in case of 0 in dangerous prompt such as overwriting file. +int Ask(const wchar *AskStr) +{ + uiAlarm(UIALARM_QUESTION); + + const int MaxItems=10; + wchar Item[MaxItems][40]; + int ItemKeyPos[MaxItems],NumItems=0; + + for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_')) + { + wchar *CurItem=Item[NumItems]; + wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0])); + wchar *EndItem=wcschr(CurItem,'_'); + if (EndItem!=NULL) + *EndItem=0; + int KeyPos=0,CurKey; + while ((CurKey=CurItem[KeyPos])!=0) + { + bool Found=false; + for (int I=0;I4 ? L"\n":L" "):L", "); + int KeyPos=ItemKeyPos[I]; + for (int J=0;J[{key};"{string}"p used to redefine + // a keyboard key on some terminals. + if (Data[J]=='\"') + return true; + if (!IsDigit(Data[J]) && Data[J]!=';') + break; + } + return false; +} + + +void OutComment(const wchar *Comment,size_t Size) +{ + if (IsCommentUnsafe(Comment,Size)) + return; + const size_t MaxOutSize=0x400; + for (size_t I=0;I>1)^0xEDB88320 : (C>>1); + CRCTab[I]=C; + } +} + + +static void InitTables() +{ + InitCRC32(crc_tables[0]); + + for (uint I=0;I<256;I++) // Build additional lookup tables. + { + uint C=crc_tables[0][I]; + for (uint J=1;J<8;J++) + { + C=crc_tables[0][(byte)C]^(C>>8); + crc_tables[J][I]=C; + } + } +} + + +struct CallInitCRC {CallInitCRC() {InitTables();}} static CallInit32; + +uint CRC32(uint 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);Size--,Data++) + StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + for (;Size>=8;Size-=8,Data+=8) + { +#ifdef BIG_ENDIAN + StartCRC ^= Data[0]|(Data[1] << 8)|(Data[2] << 16)|(Data[3] << 24); + uint NextData = Data[4]|(Data[5] << 8)|(Data[6] << 16)|(Data[7] << 24); +#else + StartCRC ^= *(uint32 *) Data; + uint NextData = *(uint32 *) (Data +4); +#endif + StartCRC = crc_tables[7][(byte) StartCRC ] ^ + crc_tables[6][(byte)(StartCRC >> 8) ] ^ + crc_tables[5][(byte)(StartCRC >> 16)] ^ + crc_tables[4][(byte)(StartCRC >> 24)] ^ + crc_tables[3][(byte) NextData ] ^ + crc_tables[2][(byte)(NextData >>8 ) ] ^ + crc_tables[1][(byte)(NextData >> 16)] ^ + crc_tables[0][(byte)(NextData >> 24)]; + } + + for (;Size>0;Size--,Data++) // Process left data. + StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8); + + return StartCRC; +} + + +#ifndef SFX_MODULE +// For RAR 1.4 archives in case somebody still has them. +ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size) +{ + byte *Data=(byte *)Addr; + for (size_t I=0;I>15))&0xffff; + } + return StartCRC; +} +#endif + + diff --git a/reference/unrar/crc.hpp b/reference/unrar/crc.hpp new file mode 100644 index 00000000..d8fea281 --- /dev/null +++ b/reference/unrar/crc.hpp @@ -0,0 +1,15 @@ +#ifndef _RAR_CRC_ +#define _RAR_CRC_ + +// This function is only to intialize external CRC tables. We do not need to +// call it before calculating CRC32. +void InitCRC32(uint *CRCTab); + +uint CRC32(uint StartCRC,const void *Addr,size_t Size); + +#ifndef SFX_MODULE +ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size); +#endif + + +#endif diff --git a/reference/unrar/crypt.cpp b/reference/unrar/crypt.cpp new file mode 100644 index 00000000..fc2126d4 --- /dev/null +++ b/reference/unrar/crypt.cpp @@ -0,0 +1,134 @@ +#include "rar.hpp" + +#ifndef SFX_MODULE +#include "crypt1.cpp" +#include "crypt2.cpp" +#endif +#include "crypt3.cpp" +#include "crypt5.cpp" + + +CryptData::CryptData() +{ + Method=CRYPT_NONE; + memset(KDF3Cache,0,sizeof(KDF3Cache)); + memset(KDF5Cache,0,sizeof(KDF5Cache)); + KDF3CachePos=0; + KDF5CachePos=0; + memset(CRCTab,0,sizeof(CRCTab)); +} + + +CryptData::~CryptData() +{ + cleandata(KDF3Cache,sizeof(KDF3Cache)); + cleandata(KDF5Cache,sizeof(KDF5Cache)); +} + + + + +void CryptData::DecryptBlock(byte *Buf,size_t Size) +{ + switch(Method) + { +#ifndef SFX_MODULE + case CRYPT_RAR13: + Decrypt13(Buf,Size); + break; + case CRYPT_RAR15: + Crypt15(Buf,Size); + break; + case CRYPT_RAR20: + for (size_t I=0;IIsSet() || Method==CRYPT_NONE) + return false; + + CryptData::Method=Method; + + wchar PwdW[MAXPASSWORD]; + Password->Get(PwdW,ASIZE(PwdW)); + char PwdA[MAXPASSWORD]; + WideToChar(PwdW,PwdA,ASIZE(PwdA)); + + switch(Method) + { +#ifndef SFX_MODULE + case CRYPT_RAR13: + SetKey13(PwdA); + break; + case CRYPT_RAR15: + SetKey15(PwdA); + break; + case CRYPT_RAR20: + SetKey20(PwdA); + break; +#endif + case CRYPT_RAR30: + SetKey30(Encrypt,Password,PwdW,Salt); + break; + case CRYPT_RAR50: + SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck); + break; + } + cleandata(PwdA,sizeof(PwdA)); + cleandata(PwdW,sizeof(PwdW)); + return true; +} + + +// 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> ( (I & 7) * 8 )); + RndBuf[I]=byte( (RndByte ^ I) + Count++); + } +} + + + + +// Fill buffer with random data. +void GetRnd(byte *RndBuf,size_t BufSize) +{ + bool Success=false; +#if defined(_WIN_ALL) + HCRYPTPROV hProvider = 0; + if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) == TRUE; + CryptReleaseContext(hProvider, 0); + } +#elif defined(_UNIX) + FILE *rndf = fopen("/dev/urandom", "r"); + if (rndf!=NULL) + { + Success=fread(RndBuf, BufSize, 1, rndf) == BufSize; + fclose(rndf); + } +#endif + // We use this code only as the last resort if code above failed. + if (!Success) + TimeRandomize(RndBuf,BufSize); +} diff --git a/reference/unrar/crypt.hpp b/reference/unrar/crypt.hpp new file mode 100644 index 00000000..f6382ef5 --- /dev/null +++ b/reference/unrar/crypt.hpp @@ -0,0 +1,101 @@ +#ifndef _RAR_CRYPT_ +#define _RAR_CRYPT_ + + +enum CRYPT_METHOD { + CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50 +}; + +#define SIZE_SALT50 16 +#define SIZE_SALT30 8 +#define SIZE_INITV 16 +#define SIZE_PSWCHECK 8 +#define SIZE_PSWCHECK_CSUM 4 + +#define CRYPT_BLOCK_SIZE 16 +#define CRYPT_BLOCK_MASK (CRYPT_BLOCK_SIZE-1) // 0xf + +#define CRYPT5_KDF_LG2_COUNT 15 // LOG2 of PDKDF2 iteration count. +#define CRYPT5_KDF_LG2_COUNT_MAX 24 // LOG2 of maximum accepted iteration count. +#define CRYPT_VERSION 0 // Supported encryption version. + + +class CryptData +{ + struct KDF5CacheItem + { + SecPassword Pwd; + byte Salt[SIZE_SALT50]; + byte Key[32]; + uint Lg2Count; // Log2 of PBKDF2 repetition count. + byte PswCheckValue[SHA256_DIGEST_SIZE]; + byte HashKeyValue[SHA256_DIGEST_SIZE]; + }; + + struct KDF3CacheItem + { + SecPassword Pwd; + byte Salt[SIZE_SALT30]; + byte Key[16]; + byte Init[16]; + bool SaltPresent; + }; + + + private: + void SetKey13(const char *Password); + void Decrypt13(byte *Data,size_t Count); + + void SetKey15(const char *Password); + void Crypt15(byte *Data,size_t Count); + + void SetKey20(const char *Password); + void Swap20(byte *Ch1,byte *Ch2); + void UpdKeys20(byte *Buf); + void EncryptBlock20(byte *Buf); + void DecryptBlock20(byte *Buf); + + void SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt); + void SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck); + + KDF3CacheItem KDF3Cache[4]; + uint KDF3CachePos; + + KDF5CacheItem KDF5Cache[4]; + uint KDF5CachePos; + + CRYPT_METHOD Method; + + Rijndael rin; + + uint CRCTab[256]; // For RAR 1.5 and RAR 2.0 encryption. + + byte SubstTable20[256]; + uint Key20[4]; + + byte Key13[3]; + 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); + void SetAV15Encryption(); + void SetCmt13Encryption(); + void EncryptBlock(byte *Buf,size_t Size); + void DecryptBlock(byte *Buf,size_t Size); + static void SetSalt(byte *Salt,size_t SaltSize); +}; + +void GetRnd(byte *RndBuf,size_t BufSize); + +void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, + size_t DataLength,byte *ResDigest); +void pbkdf2(const byte *pass, size_t pass_len, const byte *salt, + size_t salt_len,byte *key, byte *Value1, byte *Value2, + uint rounds); + +void ConvertHashToMAC(HashValue *Value,byte *Key); + +#endif diff --git a/reference/unrar/crypt1.cpp b/reference/unrar/crypt1.cpp new file mode 100644 index 00000000..14263937 --- /dev/null +++ b/reference/unrar/crypt1.cpp @@ -0,0 +1,79 @@ +extern uint CRCTab[256]; + +void CryptData::SetKey13(const char *Password) +{ + Key13[0]=Key13[1]=Key13[2]=0; + for (size_t I=0;Password[I]!=0;I++) + { + byte P=Password[I]; + Key13[0]+=P; + Key13[1]^=P; + Key13[2]+=P; + Key13[2]=(byte)rotls(Key13[2],1,8); + } +} + + +void CryptData::SetKey15(const char *Password) +{ + InitCRC32(CRCTab); + uint PswCRC=CRC32(0xffffffff,Password,strlen(Password)); + Key15[0]=PswCRC&0xffff; + Key15[1]=(PswCRC>>16)&0xffff; + Key15[2]=Key15[3]=0; + for (size_t I=0;Password[I]!=0;I++) + { + byte P=Password[I]; + Key15[2]^=P^CRCTab[P]; + Key15[3]+=P+(CRCTab[P]>>16); + } +} + + +void CryptData::SetAV15Encryption() +{ + InitCRC32(CRCTab); + Method=CRYPT_RAR15; + Key15[0]=0x4765; + Key15[1]=0x9021; + Key15[2]=0x7382; + Key15[3]=0x5215; +} + + +void CryptData::SetCmt13Encryption() +{ + Method=CRYPT_RAR13; + Key13[0]=0; + Key13[1]=7; + Key13[2]=77; +} + + +void CryptData::Decrypt13(byte *Data,size_t Count) +{ + while (Count--) + { + Key13[1]+=Key13[2]; + Key13[0]+=Key13[1]; + *Data-=Key13[0]; + Data++; + } +} + + +void CryptData::Crypt15(byte *Data,size_t Count) +{ + while (Count--) + { + Key15[0]+=0x1234; + Key15[1]^=CRCTab[(Key15[0] & 0x1fe)>>1]; + Key15[2]-=CRCTab[(Key15[0] & 0x1fe)>>1]>>16; + Key15[0]^=Key15[2]; + Key15[3]=rotrs(Key15[3]&0xffff,1,16)^Key15[1]; + Key15[3]=rotrs(Key15[3]&0xffff,1,16); + Key15[0]^=Key15[3]; + *Data^=(byte)(Key15[0]>>8); + Data++; + } +} diff --git a/reference/unrar/crypt2.cpp b/reference/unrar/crypt2.cpp new file mode 100644 index 00000000..5fa4a97d --- /dev/null +++ b/reference/unrar/crypt2.cpp @@ -0,0 +1,133 @@ +#define NROUNDS 32 + +#define substLong(t) ( (uint)SubstTable20[(uint)t&255] | \ + ((uint)SubstTable20[(int)(t>> 8)&255]<< 8) | \ + ((uint)SubstTable20[(int)(t>>16)&255]<<16) | \ + ((uint)SubstTable20[(int)(t>>24)&255]<<24) ) + + +static byte InitSubstTable20[256]={ + 215, 19,149, 35, 73,197,192,205,249, 28, 16,119, 48,221, 2, 42, + 232, 1,177,233, 14, 88,219, 25,223,195,244, 90, 87,239,153,137, + 255,199,147, 70, 92, 66,246, 13,216, 40, 62, 29,217,230, 86, 6, + 71, 24,171,196,101,113,218,123, 93, 91,163,178,202, 67, 44,235, + 107,250, 75,234, 49,167,125,211, 83,114,157,144, 32,193,143, 36, + 158,124,247,187, 89,214,141, 47,121,228, 61,130,213,194,174,251, + 97,110, 54,229,115, 57,152, 94,105,243,212, 55,209,245, 63, 11, + 164,200, 31,156, 81,176,227, 21, 76, 99,139,188,127, 17,248, 51, + 207,120,189,210, 8,226, 41, 72,183,203,135,165,166, 60, 98, 7, + 122, 38,155,170, 69,172,252,238, 39,134, 59,128,236, 27,240, 80, + 131, 3, 85,206,145, 79,154,142,159,220,201,133, 74, 64, 20,129, + 224,185,138,103,173,182, 43, 34,254, 82,198,151,231,180, 58, 10, + 118, 26,102, 12, 50,132, 22,191,136,111,162,179, 45, 4,148,108, + 161, 56, 78,126,242,222, 15,175,146, 23, 33,241,181,190, 77,225, + 0, 46,169,186, 68, 95,237, 65, 53,208,253,168, 9, 18,100, 52, + 116,184,160, 96,109, 37, 30,106,140,104,150, 5,204,117,112, 84 +}; + + +void CryptData::SetKey20(const char *Password) +{ + InitCRC32(CRCTab); + + char Psw[MAXPASSWORD]; + strncpyz(Psw,Password,ASIZE(Psw)); // We'll need to modify it below. + size_t PswLength=strlen(Psw); + + Key20[0]=0xD3A3B879L; + Key20[1]=0x3F6D12F7L; + Key20[2]=0x7515A235L; + Key20[3]=0xA4E7F123L; + + memcpy(SubstTable20,InitSubstTable20,sizeof(SubstTable20)); + for (uint J=0;J<256;J++) + for (size_t I=0;I=0;I--) + { + T=((C+rotls(D,11,32))^Key20[I&3]); + TA=A^substLong(T); + T=((D^rotls(C,17,32))+Key20[I&3]); + TB=B^substLong(T); + A=C; + B=D; + C=TA; + D=TB; + } + RawPut4(C^Key20[0],Buf+0); + RawPut4(D^Key20[1],Buf+4); + RawPut4(A^Key20[2],Buf+8); + RawPut4(B^Key20[3],Buf+12); + UpdKeys20(InBuf); +} + + +void CryptData::UpdKeys20(byte *Buf) +{ + for (int I=0;I<16;I+=4) + { + Key20[0]^=CRCTab[Buf[I]]; + Key20[1]^=CRCTab[Buf[I+1]]; + Key20[2]^=CRCTab[Buf[I+2]]; + Key20[3]^=CRCTab[Buf[I+3]]; + } +} + + +void CryptData::Swap20(byte *Ch1,byte *Ch2) +{ + byte Ch=*Ch1; + *Ch1=*Ch2; + *Ch2=Ch; +} diff --git a/reference/unrar/crypt3.cpp b/reference/unrar/crypt3.cpp new file mode 100644 index 00000000..48406487 --- /dev/null +++ b/reference/unrar/crypt3.cpp @@ -0,0 +1,68 @@ +void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt) +{ + byte AESKey[16],AESInit[16]; + + bool Cached=false; + for (uint I=0;I>8); + PswNum[2]=(byte)(I>>16); + sha1_process(&c, PswNum, 3); + if (I%(HashRounds/16)==0) + { + sha1_context tempc=c; + uint32 digest[5]; + sha1_done( &tempc, digest ); + AESInit[I/(HashRounds/16)]=(byte)digest[4]; + } + } + uint32 digest[5]; + sha1_done( &c, digest ); + for (int I=0;I<4;I++) + for (int J=0;J<4;J++) + AESKey[I*4+J]=(byte)(digest[I]>>(J*8)); + + KDF3Cache[KDF3CachePos].Pwd=*Password; + if ((KDF3Cache[KDF3CachePos].SaltPresent=(Salt!=NULL))==true) + memcpy(KDF3Cache[KDF3CachePos].Salt,Salt,SIZE_SALT30); + memcpy(KDF3Cache[KDF3CachePos].Key,AESKey,sizeof(AESKey)); + SecHideData(KDF3Cache[KDF3CachePos].Key,sizeof(KDF3Cache[KDF3CachePos].Key),true,false); + memcpy(KDF3Cache[KDF3CachePos].Init,AESInit,sizeof(AESInit)); + KDF3CachePos=(KDF3CachePos+1)%ASIZE(KDF3Cache); + + cleandata(RawPsw,sizeof(RawPsw)); + } + rin.Init(Encrypt, AESKey, 128, AESInit); + cleandata(AESKey,sizeof(AESKey)); + cleandata(AESInit,sizeof(AESInit)); +} + diff --git a/reference/unrar/crypt5.cpp b/reference/unrar/crypt5.cpp new file mode 100644 index 00000000..7562469f --- /dev/null +++ b/reference/unrar/crypt5.cpp @@ -0,0 +1,233 @@ +static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data, + size_t DataLength,byte *ResDigest, + sha256_context *ICtxOpt,bool *SetIOpt, + sha256_context *RCtxOpt,bool *SetROpt) +{ + const size_t Sha256BlockSize=64; // As defined in RFC 4868. + + byte KeyHash[SHA256_DIGEST_SIZE]; + if (KeyLength > Sha256BlockSize) // Convert longer keys to key hash. + { + sha256_context KCtx; + sha256_init(&KCtx); + sha256_process(&KCtx, Key, KeyLength); + sha256_done(&KCtx, KeyHash); + + Key = KeyHash; + KeyLength = SHA256_DIGEST_SIZE; + } + + byte KeyBuf[Sha256BlockSize]; // Store the padded key here. + sha256_context ICtx; + + if (ICtxOpt!=NULL && *SetIOpt) + ICtx=*ICtxOpt; // Use already calculated first block context. + else + { + // This calculation is the same for all iterations with same password. + // So for PBKDF2 we can calculate it only for first block and then reuse + // to improve performance. + + for (size_t I = 0; I < KeyLength; I++) // Use 0x36 padding for inner digest. + KeyBuf[I] = Key[I] ^ 0x36; + for (size_t I = KeyLength; I < Sha256BlockSize; I++) + KeyBuf[I] = 0x36; + + sha256_init(&ICtx); + sha256_process(&ICtx, KeyBuf, Sha256BlockSize); // Hash padded key. + } + + if (ICtxOpt!=NULL && !*SetIOpt) // Store constant context for further reuse. + { + *ICtxOpt=ICtx; + *SetIOpt=true; + } + + sha256_process(&ICtx, Data, DataLength); // Hash data. + + byte IDig[SHA256_DIGEST_SIZE]; // Internal digest for padded key and data. + sha256_done(&ICtx, IDig); + + sha256_context RCtx; + + if (RCtxOpt!=NULL && *SetROpt) + RCtx=*RCtxOpt; // Use already calculated first block context. + else + { + // This calculation is the same for all iterations with same password. + // So for PBKDF2 we can calculate it only for first block and then reuse + // to improve performance. + + for (size_t I = 0; I < KeyLength; I++) // Use 0x5c for outer key padding. + KeyBuf[I] = Key[I] ^ 0x5c; + for (size_t I = KeyLength; I < Sha256BlockSize; I++) + KeyBuf[I] = 0x5c; + + sha256_init(&RCtx); + sha256_process(&RCtx, KeyBuf, Sha256BlockSize); // Hash padded key. + } + + if (RCtxOpt!=NULL && !*SetROpt) // Store constant context for further reuse. + { + *RCtxOpt=RCtx; + *SetROpt=true; + } + + sha256_process(&RCtx, IDig, SHA256_DIGEST_SIZE); // Hash internal digest. + + sha256_done(&RCtx, ResDigest); +} + + +// PBKDF2 for 32 byte key length. We generate the key for specified number +// of iteration count also as two supplementary values (key for checksums +// and password verification) for iterations+16 and iterations+32. +void pbkdf2(const byte *Pwd, size_t PwdLength, + const byte *Salt, size_t SaltLength, + byte *Key, byte *V1, byte *V2, uint Count) +{ + const size_t MaxSalt=64; + 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; + + // First iteration: HMAC of password, salt and block index (1). + byte U1[SHA256_DIGEST_SIZE]; + hmac_sha256(Pwd, PwdLength, SaltData, SaltLength + 4, U1, NULL, NULL, NULL, NULL); + byte Fn[SHA256_DIGEST_SIZE]; // Current function value. + memcpy(Fn, U1, sizeof(Fn)); // Function at first iteration. + + uint CurCount[] = { Count-1, 16, 16 }; + byte *CurValue[] = { Key , V1, V2 }; + + sha256_context ICtxOpt,RCtxOpt; + bool SetIOpt=false,SetROpt=false; + + byte U2[SHA256_DIGEST_SIZE]; + for (uint I = 0; I < 3; I++) // For output key and 2 supplementary values. + { + for (uint J = 0; J < CurCount[I]; J++) + { + // U2 = PRF (P, U1). + hmac_sha256(Pwd, PwdLength, U1, sizeof(U1), U2, &ICtxOpt, &SetIOpt, &RCtxOpt, &SetROpt); + memcpy(U1, U2, sizeof(U1)); + for (uint K = 0; K < sizeof(Fn); K++) // Function ^= U. + Fn[K] ^= U1[K]; + } + memcpy(CurValue[I], Fn, SHA256_DIGEST_SIZE); + } + + cleandata(SaltData, sizeof(SaltData)); + cleandata(Fn, sizeof(Fn)); + cleandata(U1, sizeof(U1)); + cleandata(U2, sizeof(U2)); +} + + +void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW, + const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey, + byte *PswCheck) +{ + if (Lg2Cnt>CRYPT5_KDF_LG2_COUNT_MAX) + return; + + byte Key[32],PswCheckValue[SHA256_DIGEST_SIZE],HashKeyValue[SHA256_DIGEST_SIZE]; + bool Found=false; + for (uint I=0;ILg2Count==Lg2Cnt && Item->Pwd==*Password && + memcmp(Item->Salt,Salt,SIZE_SALT50)==0) + { + memcpy(Key,Item->Key,sizeof(Key)); + SecHideData(Key,sizeof(Key),false,false); + + memcpy(PswCheckValue,Item->PswCheckValue,sizeof(PswCheckValue)); + memcpy(HashKeyValue,Item->HashKeyValue,sizeof(HashKeyValue)); + Found=true; + break; + } + } + + if (!Found) + { + char PwdUtf[MAXPASSWORD*4]; + WideToUtf(PwdW,PwdUtf,ASIZE(PwdUtf)); + + pbkdf2((byte *)PwdUtf,strlen(PwdUtf),Salt,SIZE_SALT50,Key,HashKeyValue,PswCheckValue,(1<Lg2Count=Lg2Cnt; + Item->Pwd=*Password; + memcpy(Item->Salt,Salt,SIZE_SALT50); + memcpy(Item->Key,Key,sizeof(Item->Key)); + memcpy(Item->PswCheckValue,PswCheckValue,sizeof(PswCheckValue)); + memcpy(Item->HashKeyValue,HashKeyValue,sizeof(HashKeyValue)); + SecHideData(Item->Key,sizeof(Item->Key),true,false); + } + if (HashKey!=NULL) + memcpy(HashKey,HashKeyValue,SHA256_DIGEST_SIZE); + if (PswCheck!=NULL) + { + memset(PswCheck,0,SIZE_PSWCHECK); + for (uint I=0;IType==HASH_CRC32) + { + byte RawCRC[4]; + RawPut4(Value->CRC32,RawCRC); + byte Digest[SHA256_DIGEST_SIZE]; + hmac_sha256(Key,SHA256_DIGEST_SIZE,RawCRC,sizeof(RawCRC),Digest,NULL,NULL,NULL,NULL); + Value->CRC32=0; + for (uint I=0;ICRC32^=Digest[I] << ((I & 3) * 8); + } + if (Value->Type==HASH_BLAKE2) + { + byte Digest[BLAKE2_DIGEST_SIZE]; + hmac_sha256(Key,BLAKE2_DIGEST_SIZE,Value->Digest,sizeof(Value->Digest),Digest,NULL,NULL,NULL,NULL); + memcpy(Value->Digest,Digest,sizeof(Value->Digest)); + } +} + + +#if 0 +static void TestPBKDF2(); +struct TestKDF {TestKDF() {TestPBKDF2();exit(0);}} GlobalTestKDF; + +void TestPBKDF2() // Test PBKDF2 HMAC-SHA256 +{ + byte Key[32],V1[32],V2[32]; + + pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 1); + byte Res1[32]={0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, 0xa8, 0x65, 0x48, 0xc9, 0x2c, 0xcc, 0x35, 0x48, 0x08, 0x05, 0x98, 0x7c, 0xb7, 0x0b, 0xe1, 0x7b }; + mprintf(L"\nPBKDF2 test1: %s", memcmp(Key,Res1,32)==0 ? L"OK":L"Failed"); + + pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 4096); + byte Res2[32]={0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, 0x96, 0x28, 0x93, 0xa0, 0x01, 0xce, 0x4e, 0x11, 0xa4, 0x96, 0x38, 0x73, 0xaa, 0x98, 0x13, 0x4a }; + mprintf(L"\nPBKDF2 test2: %s", memcmp(Key,Res2,32)==0 ? L"OK":L"Failed"); + + pbkdf2((byte *)"just some long string pretending to be a password", 49, (byte *)"salt, salt, salt, a lot of salt", 31, Key, V1, V2, 65536); + byte Res3[32]={0x08, 0x0f, 0xa3, 0x1d, 0x42, 0x2d, 0xb0, 0x47, 0x83, 0x9b, 0xce, 0x3a, 0x3b, 0xce, 0x49, 0x51, 0xe2, 0x62, 0xb9, 0xff, 0x76, 0x2f, 0x57, 0xe9, 0xc4, 0x71, 0x96, 0xce, 0x4b, 0x6b, 0x6e, 0xbf}; + mprintf(L"\nPBKDF2 test3: %s", memcmp(Key,Res3,32)==0 ? L"OK":L"Failed"); +} +#endif diff --git a/reference/unrar/dll.cpp b/reference/unrar/dll.cpp new file mode 100644 index 00000000..d12c0b17 --- /dev/null +++ b/reference/unrar/dll.cpp @@ -0,0 +1,474 @@ +#include "rar.hpp" + +static int RarErrorToDll(RAR_EXIT ErrCode); + +struct DataSet +{ + CommandData Cmd; + Archive Arc; + CmdExtract Extract; + int OpenMode; + int HeaderSize; + + DataSet():Arc(&Cmd),Extract(&Cmd) {}; +}; + + +HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r) +{ + RAROpenArchiveDataEx rx; + memset(&rx,0,sizeof(rx)); + rx.ArcName=r->ArcName; + rx.OpenMode=r->OpenMode; + rx.CmtBuf=r->CmtBuf; + rx.CmtBufSize=r->CmtBufSize; + HANDLE hArc=RAROpenArchiveEx(&rx); + r->OpenResult=rx.OpenResult; + r->CmtSize=rx.CmtSize; + r->CmtState=rx.CmtState; + return hArc; +} + + +HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r) +{ + DataSet *Data=NULL; + try + { + r->OpenResult=0; + Data=new DataSet; + Data->Cmd.DllError=0; + Data->OpenMode=r->OpenMode; + Data->Cmd.FileArgs.AddString(L"*"); + + char AnsiArcName[NM]; + *AnsiArcName=0; + if (r->ArcName!=NULL) + { + strncpyz(AnsiArcName,r->ArcName,ASIZE(AnsiArcName)); +#ifdef _WIN_ALL + if (!AreFileApisANSI()) + { + OemToCharBuffA(r->ArcName,AnsiArcName,ASIZE(AnsiArcName)); + AnsiArcName[ASIZE(AnsiArcName)-1]=0; + } +#endif + } + + wchar ArcName[NM]; + GetWideName(AnsiArcName,r->ArcNameW,ArcName,ASIZE(ArcName)); + + Data->Cmd.AddArcName(ArcName); + Data->Cmd.Overwrite=OVERWRITE_ALL; + Data->Cmd.VersionControl=1; + + Data->Cmd.Callback=r->Callback; + Data->Cmd.UserData=r->UserData; + + // Open shared mode is added by request of dll users, who need to + // browse and unpack archives while downloading. + Data->Cmd.OpenShared = true; + if (!Data->Arc.Open(ArcName,FMF_OPENSHARED)) + { + r->OpenResult=ERAR_EOPEN; + delete Data; + return NULL; + } + if (!Data->Arc.IsArchive(true)) + { + if (Data->Cmd.DllError!=0) + r->OpenResult=Data->Cmd.DllError; + else + { + RAR_EXIT ErrCode=ErrHandler.GetErrorCode(); + if (ErrCode!=RARX_SUCCESS && ErrCode!=RARX_WARNING) + r->OpenResult=RarErrorToDll(ErrCode); + else + r->OpenResult=ERAR_BAD_ARCHIVE; + } + delete Data; + return NULL; + } + r->Flags=0; + + if (Data->Arc.Volume) + r->Flags|=0x01; + if (Data->Arc.Locked) + r->Flags|=0x04; + if (Data->Arc.Solid) + r->Flags|=0x08; + if (Data->Arc.NewNumbering) + r->Flags|=0x10; + if (Data->Arc.Signed) + r->Flags|=0x20; + if (Data->Arc.Protected) + r->Flags|=0x40; + if (Data->Arc.Encrypted) + r->Flags|=0x80; + if (Data->Arc.FirstVolume) + r->Flags|=0x100; + + Array CmtDataW; + if (r->CmtBufSize!=0 && Data->Arc.GetComment(&CmtDataW)) + { + Array CmtData(CmtDataW.Size()*4+1); + memset(&CmtData[0],0,CmtData.Size()); + WideToChar(&CmtDataW[0],&CmtData[0],CmtData.Size()-1); + size_t Size=strlen(&CmtData[0])+1; + + r->Flags|=2; + r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1; + r->CmtSize=(uint)Min(Size,r->CmtBufSize); + memcpy(r->CmtBuf,&CmtData[0],r->CmtSize-1); + if (Size<=r->CmtBufSize) + r->CmtBuf[r->CmtSize-1]=0; + } + else + r->CmtState=r->CmtSize=0; + Data->Extract.ExtractArchiveInit(Data->Arc); + return (HANDLE)Data; + } + catch (RAR_EXIT ErrCode) + { + if (Data!=NULL && Data->Cmd.DllError!=0) + r->OpenResult=Data->Cmd.DllError; + else + r->OpenResult=RarErrorToDll(ErrCode); + if (Data != NULL) + delete Data; + return NULL; + } + catch (std::bad_alloc&) // Catch 'new' exception. + { + r->OpenResult=ERAR_NO_MEMORY; + if (Data != NULL) + delete Data; + } + return NULL; // To make compilers happy. +} + + +int PASCAL RARCloseArchive(HANDLE hArcData) +{ + DataSet *Data=(DataSet *)hArcData; + bool Success=Data==NULL ? false:Data->Arc.Close(); + delete Data; + return Success ? ERAR_SUCCESS : ERAR_ECLOSE; +} + + +int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *D) +{ + struct RARHeaderDataEx X; + memset(&X,0,sizeof(X)); + + int Code=RARReadHeaderEx(hArcData,&X); + + strncpyz(D->ArcName,X.ArcName,ASIZE(D->ArcName)); + strncpyz(D->FileName,X.FileName,ASIZE(D->FileName)); + D->Flags=X.Flags; + D->PackSize=X.PackSize; + D->UnpSize=X.UnpSize; + D->HostOS=X.HostOS; + D->FileCRC=X.FileCRC; + D->FileTime=X.FileTime; + D->UnpVer=X.UnpVer; + D->Method=X.Method; + D->FileAttr=X.FileAttr; + D->CmtSize=0; + D->CmtState=0; + + return Code; +} + + +int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D) +{ + DataSet *Data=(DataSet *)hArcData; + try + { + if ((Data->HeaderSize=(int)Data->Arc.SearchBlock(HEAD_FILE))<=0) + { + if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_ENDARC && + Data->Arc.EndArcHead.NextVolume) + if (MergeArchive(Data->Arc,NULL,false,'L')) + { + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + return RARReadHeaderEx(hArcData,D); + } + else + return ERAR_EOPEN; + + if (Data->Arc.BrokenHeader) + return ERAR_BAD_DATA; + + // Might be necessary if RARSetPassword is still called instead of + // open callback for RAR5 archives and if password is invalid. + if (Data->Arc.FailedHeaderDecryption) + return ERAR_BAD_PASSWORD; + + return ERAR_END_ARCHIVE; + } + FileHeader *hd=&Data->Arc.FileHead; + if (Data->OpenMode==RAR_OM_LIST && hd->SplitBefore) + { + int Code=RARProcessFile(hArcData,RAR_SKIP,NULL,NULL); + if (Code==0) + return RARReadHeaderEx(hArcData,D); + else + return Code; + } + wcsncpy(D->ArcNameW,Data->Arc.FileName,ASIZE(D->ArcNameW)); + WideToChar(D->ArcNameW,D->ArcName,ASIZE(D->ArcName)); + + wcsncpy(D->FileNameW,hd->FileName,ASIZE(D->FileNameW)); + WideToChar(D->FileNameW,D->FileName,ASIZE(D->FileName)); +#ifdef _WIN_ALL + CharToOemA(D->FileName,D->FileName); +#endif + + D->Flags=0; + if (hd->SplitBefore) + D->Flags|=RHDF_SPLITBEFORE; + if (hd->SplitAfter) + D->Flags|=RHDF_SPLITAFTER; + if (hd->Encrypted) + D->Flags|=RHDF_ENCRYPTED; + if (hd->Solid) + D->Flags|=RHDF_SOLID; + if (hd->Dir) + D->Flags|=RHDF_DIRECTORY; + + D->PackSize=uint(hd->PackSize & 0xffffffff); + D->PackSizeHigh=uint(hd->PackSize>>32); + D->UnpSize=uint(hd->UnpSize & 0xffffffff); + D->UnpSizeHigh=uint(hd->UnpSize>>32); + D->HostOS=hd->HSType==HSYS_WINDOWS ? HOST_WIN32:HOST_UNIX; + if (Data->Arc.Format==RARFMT50) + D->UnpVer=Data->Arc.FileHead.UnpVer==0 ? 50 : 200; // If it is not 0, just set it to something big. + else + D->UnpVer=Data->Arc.FileHead.UnpVer; + D->FileCRC=hd->FileHash.CRC32; + D->FileTime=hd->mtime.GetDos(); + + uint64 MRaw=hd->mtime.GetWin(); + D->MtimeLow=(uint)MRaw; + D->MtimeHigh=(uint)(MRaw>>32); + uint64 CRaw=hd->ctime.GetWin(); + D->CtimeLow=(uint)CRaw; + D->CtimeHigh=(uint)(CRaw>>32); + uint64 ARaw=hd->atime.GetWin(); + D->AtimeLow=(uint)ARaw; + D->AtimeHigh=(uint)(ARaw>>32); + + D->Method=hd->Method+0x30; + D->FileAttr=hd->FileAttr; + D->CmtSize=0; + D->CmtState=0; + + D->DictSize=uint(hd->WinSize/1024); + + switch (hd->FileHash.Type) + { + case HASH_RAR14: + case HASH_CRC32: + D->HashType=RAR_HASH_CRC32; + break; + case HASH_BLAKE2: + D->HashType=RAR_HASH_BLAKE2; + memcpy(D->Hash,hd->FileHash.Digest,BLAKE2_DIGEST_SIZE); + break; + default: + D->HashType=RAR_HASH_NONE; + break; + } + + D->RedirType=hd->RedirType; + // RedirNameSize sanity check is useful in case some developer + // did not initialize Reserved area with 0 as required in docs. + // We have taken 'Redir*' fields from Reserved area. We may remove + // this RedirNameSize check sometimes later. + if (hd->RedirType!=FSREDIR_NONE && D->RedirName!=NULL && + D->RedirNameSize>0 && D->RedirNameSize<100000) + wcsncpyz(D->RedirName,hd->RedirName,D->RedirNameSize); + D->DirTarget=hd->DirTarget; + } + catch (RAR_EXIT ErrCode) + { + return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); + } + return ERAR_SUCCESS; +} + + +int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName,wchar *DestPathW,wchar *DestNameW) +{ + DataSet *Data=(DataSet *)hArcData; + try + { + Data->Cmd.DllError=0; + if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT || + Operation==RAR_SKIP && !Data->Arc.Solid) + { + if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE && + Data->Arc.FileHead.SplitAfter) + if (MergeArchive(Data->Arc,NULL,false,'L')) + { + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + return ERAR_SUCCESS; + } + else + return ERAR_EOPEN; + Data->Arc.SeekToNext(); + } + else + { + Data->Cmd.DllOpMode=Operation; + + *Data->Cmd.ExtrPath=0; + *Data->Cmd.DllDestName=0; + + if (DestPath!=NULL) + { + char ExtrPathA[NM]; + strncpyz(ExtrPathA,DestPath,ASIZE(ExtrPathA)-2); +#ifdef _WIN_ALL + // We must not apply OemToCharBuffA directly to DestPath, + // because we do not know DestPath length and OemToCharBuffA + // does not stop at 0. + OemToCharA(ExtrPathA,ExtrPathA); +#endif + CharToWide(ExtrPathA,Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + } + if (DestName!=NULL) + { + char DestNameA[NM]; + strncpyz(DestNameA,DestName,ASIZE(DestNameA)-2); +#ifdef _WIN_ALL + // We must not apply OemToCharBuffA directly to DestName, + // because we do not know DestName length and OemToCharBuffA + // does not stop at 0. + OemToCharA(DestNameA,DestNameA); +#endif + CharToWide(DestNameA,Data->Cmd.DllDestName,ASIZE(Data->Cmd.DllDestName)); + } + + if (DestPathW!=NULL) + { + wcsncpy(Data->Cmd.ExtrPath,DestPathW,ASIZE(Data->Cmd.ExtrPath)); + AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath)); + } + + if (DestNameW!=NULL) + wcsncpyz(Data->Cmd.DllDestName,DestNameW,ASIZE(Data->Cmd.DllDestName)); + + wcscpy(Data->Cmd.Command,Operation==RAR_EXTRACT ? L"X":L"T"); + Data->Cmd.Test=Operation!=RAR_EXTRACT; + bool Repeat=false; + Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat); + + // Now we process extra file information if any. + // + // Archive can be closed if we process volumes, next volume is missing + // and current one is already removed or deleted. So we need to check + // if archive is still open to avoid calling file operations on + // the invalid file handle. Some of our file operations like Seek() + // process such invalid handle correctly, some not. + while (Data->Arc.IsOpened() && Data->Arc.ReadHeader()!=0 && + Data->Arc.GetHeaderType()==HEAD_SERVICE) + { + Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat); + Data->Arc.SeekToNext(); + } + Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET); + } + } + catch (std::bad_alloc&) + { + return ERAR_NO_MEMORY; + } + catch (RAR_EXIT ErrCode) + { + return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode); + } + return Data->Cmd.DllError; +} + + +int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName) +{ + return(ProcessFile(hArcData,Operation,DestPath,DestName,NULL,NULL)); +} + + +int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar *DestPath,wchar *DestName) +{ + return(ProcessFile(hArcData,Operation,NULL,NULL,DestPath,DestName)); +} + + +void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.ChangeVolProc=ChangeVolProc; +} + + +void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.Callback=Callback; + Data->Cmd.UserData=UserData; +} + + +void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc) +{ + DataSet *Data=(DataSet *)hArcData; + Data->Cmd.ProcessDataProc=ProcessDataProc; +} + + +#ifndef RAR_NOCRYPT +void PASCAL RARSetPassword(HANDLE hArcData,char *Password) +{ + DataSet *Data=(DataSet *)hArcData; + wchar PasswordW[MAXPASSWORD]; + GetWideName(Password,NULL,PasswordW,ASIZE(PasswordW)); + Data->Cmd.Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); +} +#endif + + +int PASCAL RARGetDllVersion() +{ + return RAR_DLL_VERSION; +} + + +static int RarErrorToDll(RAR_EXIT ErrCode) +{ + switch(ErrCode) + { + case RARX_FATAL: + return ERAR_EREAD; + case RARX_CRC: + return ERAR_BAD_DATA; + case RARX_WRITE: + return ERAR_EWRITE; + case RARX_OPEN: + return ERAR_EOPEN; + case RARX_CREATE: + return ERAR_ECREATE; + case RARX_MEMORY: + return ERAR_NO_MEMORY; + case RARX_BADPWD: + return ERAR_BAD_PASSWORD; + case RARX_SUCCESS: + return ERAR_SUCCESS; // 0. + default: + return ERAR_UNKNOWN; + } +} diff --git a/reference/unrar/dll.def b/reference/unrar/dll.def new file mode 100644 index 00000000..54420e2a --- /dev/null +++ b/reference/unrar/dll.def @@ -0,0 +1,12 @@ +EXPORTS + RAROpenArchive + RAROpenArchiveEx + RARCloseArchive + RARReadHeader + RARReadHeaderEx + RARProcessFile + RARSetCallback + RARSetChangeVolProc + RARSetProcessDataProc + RARSetPassword + RARGetDllVersion diff --git a/reference/unrar/dll.hpp b/reference/unrar/dll.hpp new file mode 100644 index 00000000..7f829066 --- /dev/null +++ b/reference/unrar/dll.hpp @@ -0,0 +1,185 @@ +#ifndef _UNRAR_DLL_ +#define _UNRAR_DLL_ + +#pragma pack(1) + +#define ERAR_SUCCESS 0 +#define ERAR_END_ARCHIVE 10 +#define ERAR_NO_MEMORY 11 +#define ERAR_BAD_DATA 12 +#define ERAR_BAD_ARCHIVE 13 +#define ERAR_UNKNOWN_FORMAT 14 +#define ERAR_EOPEN 15 +#define ERAR_ECREATE 16 +#define ERAR_ECLOSE 17 +#define ERAR_EREAD 18 +#define ERAR_EWRITE 19 +#define ERAR_SMALL_BUF 20 +#define ERAR_UNKNOWN 21 +#define ERAR_MISSING_PASSWORD 22 +#define ERAR_EREFERENCE 23 +#define ERAR_BAD_PASSWORD 24 + +#define RAR_OM_LIST 0 +#define RAR_OM_EXTRACT 1 +#define RAR_OM_LIST_INCSPLIT 2 + +#define RAR_SKIP 0 +#define RAR_TEST 1 +#define RAR_EXTRACT 2 + +#define RAR_VOL_ASK 0 +#define RAR_VOL_NOTIFY 1 + +#define RAR_DLL_VERSION 8 + +#define RAR_HASH_NONE 0 +#define RAR_HASH_CRC32 1 +#define RAR_HASH_BLAKE2 2 + + +#ifdef _UNIX +#define CALLBACK +#define PASCAL +#define LONG long +#define HANDLE void * +#define LPARAM long +#define UINT unsigned int +#endif + +#define RHDF_SPLITBEFORE 0x01 +#define RHDF_SPLITAFTER 0x02 +#define RHDF_ENCRYPTED 0x04 +#define RHDF_SOLID 0x10 +#define RHDF_DIRECTORY 0x20 + + +struct RARHeaderData +{ + char ArcName[260]; + char FileName[260]; + unsigned int Flags; + unsigned int PackSize; + unsigned int UnpSize; + unsigned int HostOS; + unsigned int FileCRC; + unsigned int FileTime; + unsigned int UnpVer; + unsigned int Method; + unsigned int FileAttr; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; +}; + + +struct RARHeaderDataEx +{ + char ArcName[1024]; + wchar_t ArcNameW[1024]; + char FileName[1024]; + wchar_t FileNameW[1024]; + unsigned int Flags; + unsigned int PackSize; + unsigned int PackSizeHigh; + unsigned int UnpSize; + unsigned int UnpSizeHigh; + unsigned int HostOS; + unsigned int FileCRC; + unsigned int FileTime; + unsigned int UnpVer; + unsigned int Method; + unsigned int FileAttr; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; + unsigned int DictSize; + unsigned int HashType; + char Hash[32]; + unsigned int RedirType; + wchar_t *RedirName; + unsigned int RedirNameSize; + unsigned int DirTarget; + unsigned int MtimeLow; + unsigned int MtimeHigh; + unsigned int CtimeLow; + unsigned int CtimeHigh; + unsigned int AtimeLow; + unsigned int AtimeHigh; + unsigned int Reserved[988]; +}; + + +struct RAROpenArchiveData +{ + char *ArcName; + unsigned int OpenMode; + unsigned int OpenResult; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; +}; + +typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2); + +#define ROADF_VOLUME 0x0001 +#define ROADF_COMMENT 0x0002 +#define ROADF_LOCK 0x0004 +#define ROADF_SOLID 0x0008 +#define ROADF_NEWNUMBERING 0x0010 +#define ROADF_SIGNED 0x0020 +#define ROADF_RECOVERY 0x0040 +#define ROADF_ENCHEADERS 0x0080 +#define ROADF_FIRSTVOLUME 0x0100 + +struct RAROpenArchiveDataEx +{ + char *ArcName; + wchar_t *ArcNameW; + unsigned int OpenMode; + unsigned int OpenResult; + char *CmtBuf; + unsigned int CmtBufSize; + unsigned int CmtSize; + unsigned int CmtState; + unsigned int Flags; + UNRARCALLBACK Callback; + LPARAM UserData; + unsigned int Reserved[28]; +}; + +enum UNRARCALLBACK_MESSAGES { + UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD,UCM_CHANGEVOLUMEW, + UCM_NEEDPASSWORDW +}; + +typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode); +typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size); + +#ifdef __cplusplus +extern "C" { +#endif + +HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData); +HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData); +int PASCAL RARCloseArchive(HANDLE hArcData); +int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData); +int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData); +int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName); +int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName); +void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData); +void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc); +void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc); +void PASCAL RARSetPassword(HANDLE hArcData,char *Password); +int PASCAL RARGetDllVersion(); + +#ifdef __cplusplus +} +#endif + +#pragma pack() + +#endif diff --git a/reference/unrar/dll.rc b/reference/unrar/dll.rc new file mode 100644 index 00000000..7aa83145 --- /dev/null +++ b/reference/unrar/dll.rc @@ -0,0 +1,28 @@ +#include +#include + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 5, 50, 100, 2418 +PRODUCTVERSION 5, 50, 100, 2418 +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Alexander Roshal\0" + VALUE "ProductName", "RAR decompression library\0" + VALUE "FileDescription", "RAR decompression library\0" + VALUE "FileVersion", "5.50.0\0" + VALUE "ProductVersion", "5.50.0\0" + VALUE "LegalCopyright", "Copyright © Alexander Roshal 1993-2017\0" + VALUE "OriginalFilename", "Unrar.dll\0" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0x04E4 + } +} + diff --git a/reference/unrar/encname.cpp b/reference/unrar/encname.cpp new file mode 100644 index 00000000..5556af3e --- /dev/null +++ b/reference/unrar/encname.cpp @@ -0,0 +1,69 @@ +#include "rar.hpp" + +EncodeFileName::EncodeFileName() +{ + Flags=0; + FlagBits=0; + FlagsPos=0; + DestSize=0; +} + + + + +void EncodeFileName::Decode(char *Name,byte *EncName,size_t EncSize,wchar *NameW, + size_t MaxDecSize) +{ + size_t EncPos=0,DecPos=0; + byte HighByte=EncPos=EncSize) + break; + Flags=EncName[EncPos++]; + FlagBits=8; + } + switch(Flags>>6) + { + case 0: + if (EncPos>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos++]; + break; + case 1: + if (EncPos>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8); + break; + case 2: + if (EncPos+1>=EncSize) + break; + NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8); + EncPos+=2; + break; + case 3: + { + if (EncPos>=EncSize) + break; + int Length=EncName[EncPos++]; + if ((Length & 0x80)!=0) + { + if (EncPos>=EncSize) + break; + byte Correction=EncName[EncPos++]; + for (Length=(Length&0x7f)+2;Length>0 && DecPos0 && DecPos1) + exit(RARX_USERBREAK); + // Otherwise return from signal handler and let Wait() function to close + // files and quit. We cannot use the same approach as in Windows, + // because Unix signal handler can block execution of our main code. +#endif + +#if defined(_WIN_ALL) && !defined(_MSC_VER) + // never reached, just to avoid a compiler warning + return TRUE; +#endif +} + + +void ErrorHandler::SetSignalHandlers(bool Enable) +{ + EnableBreak=Enable; +#ifdef _WIN_ALL + SetConsoleCtrlHandler(Enable ? ProcessSignal:NULL,TRUE); +#else + signal(SIGINT,Enable ? ProcessSignal:SIG_IGN); + signal(SIGTERM,Enable ? ProcessSignal:SIG_IGN); +#endif +} + + +void ErrorHandler::Throw(RAR_EXIT Code) +{ + if (Code==RARX_USERBREAK && !EnableBreak) + return; +#if !defined(SILENT) + // Do not write "aborted" when just displaying online help. + if (Code!=RARX_SUCCESS && Code!=RARX_USERERROR) + mprintf(L"\n%s\n",St(MProgAborted)); +#endif + SetErrorCode(Code); + throw Code; +} + + +void ErrorHandler::SysErrMsg() +{ +#if !defined(SFX_MODULE) && !defined(SILENT) +#ifdef _WIN_ALL + wchar *lpMsgBuf=NULL; + int ErrType=GetLastError(); + if (ErrType!=0 && FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL,ErrType,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf,0,NULL)) + { + wchar *CurMsg=lpMsgBuf; + while (CurMsg!=NULL) + { + while (*CurMsg=='\r' || *CurMsg=='\n') + CurMsg++; + if (*CurMsg==0) + break; + wchar *EndMsg=wcschr(CurMsg,'\r'); + if (EndMsg==NULL) + EndMsg=wcschr(CurMsg,'\n'); + if (EndMsg!=NULL) + { + *EndMsg=0; + EndMsg++; + } + uiMsg(UIERROR_SYSERRMSG,CurMsg); + CurMsg=EndMsg; + } + } + LocalFree( lpMsgBuf ); +#endif + +#if defined(_UNIX) || defined(_EMX) + if (errno!=0) + { + char *err=strerror(errno); + if (err!=NULL) + { + wchar Msg[1024]; + CharToWide(err,Msg,ASIZE(Msg)); + uiMsg(UIERROR_SYSERRMSG,Msg); + } + } +#endif + +#endif +} + + +int ErrorHandler::GetSystemErrorCode() +{ +#ifdef _WIN_ALL + return GetLastError(); +#else + return errno; +#endif +} + + +void ErrorHandler::SetSystemErrorCode(int Code) +{ +#ifdef _WIN_ALL + SetLastError(Code); +#else + errno=Code; +#endif +} diff --git a/reference/unrar/errhnd.hpp b/reference/unrar/errhnd.hpp new file mode 100644 index 00000000..26df96d5 --- /dev/null +++ b/reference/unrar/errhnd.hpp @@ -0,0 +1,70 @@ +#ifndef _RAR_ERRHANDLER_ +#define _RAR_ERRHANDLER_ + +enum RAR_EXIT // RAR exit code. +{ + RARX_SUCCESS = 0, + RARX_WARNING = 1, + RARX_FATAL = 2, + RARX_CRC = 3, + RARX_LOCK = 4, + RARX_WRITE = 5, + RARX_OPEN = 6, + RARX_USERERROR = 7, + RARX_MEMORY = 8, + RARX_CREATE = 9, + RARX_NOFILES = 10, + RARX_BADPWD = 11, + RARX_USERBREAK = 255 +}; + +class ErrorHandler +{ + private: + RAR_EXIT ExitCode; + uint ErrCount; + bool EnableBreak; + bool Silent; + bool DisableShutdown; // Shutdown is not suitable after last error. + public: + ErrorHandler(); + void Clean(); + void MemoryError(); + void OpenError(const wchar *FileName); + void CloseError(const wchar *FileName); + void ReadError(const wchar *FileName); + bool AskRepeatRead(const wchar *FileName); + void WriteError(const wchar *ArcName,const wchar *FileName); + void WriteErrorFAT(const wchar *FileName); + bool AskRepeatWrite(const wchar *FileName,bool DiskFull); + void SeekError(const wchar *FileName); + void GeneralErrMsg(const wchar *fmt,...); + void MemoryErrorMsg(); + void OpenErrorMsg(const wchar *FileName); + void OpenErrorMsg(const wchar *ArcName,const wchar *FileName); + void CreateErrorMsg(const wchar *FileName); + void CreateErrorMsg(const wchar *ArcName,const wchar *FileName); + void ReadErrorMsg(const wchar *FileName); + void ReadErrorMsg(const wchar *ArcName,const wchar *FileName); + void WriteErrorMsg(const wchar *ArcName,const wchar *FileName); + void ArcBrokenMsg(const wchar *ArcName); + void ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName); + void UnknownMethodMsg(const wchar *ArcName,const wchar *FileName); + void Exit(RAR_EXIT ExitCode); + void SetErrorCode(RAR_EXIT Code); + RAR_EXIT GetErrorCode() {return ExitCode;} + uint GetErrorCount() {return ErrCount;} + void SetSignalHandlers(bool Enable); + void Throw(RAR_EXIT Code); + void SetSilent(bool Mode) {Silent=Mode;}; + void SysErrMsg(); + int GetSystemErrorCode(); + void SetSystemErrorCode(int Code); + bool IsShutdownEnabled() {return !DisableShutdown;} + + bool UserBreak; // Ctrl+Break is pressed. + bool MainExit; // main() is completed. +}; + + +#endif diff --git a/reference/unrar/extinfo.cpp b/reference/unrar/extinfo.cpp new file mode 100644 index 00000000..5cb90a40 --- /dev/null +++ b/reference/unrar/extinfo.cpp @@ -0,0 +1,178 @@ +#include "rar.hpp" + +#include "hardlinks.cpp" +#include "win32stm.cpp" + +#ifdef _WIN_ALL +#include "win32acl.cpp" +#include "win32lnk.cpp" +#endif + +#ifdef _UNIX +#include "uowners.cpp" +#ifdef SAVE_LINKS +#include "ulinks.cpp" +#endif +#endif + + + +// RAR2 service header extra records. +#ifndef SFX_MODULE +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name) +{ + 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); + break; + case STREAM_HEAD: + ExtractStreams20(Arc,Name); + break; +#endif + } +} +#endif + + +// RAR3 and RAR5 service header extra records. +void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name) +{ +#ifdef _UNIX + if (!Cmd->Test && Cmd->ProcessOwners && Arc.Format==RARFMT15 && + Arc.SubHead.CmpName(SUBHEAD_TYPE_UOWNER)) + ExtractUnixOwner30(Arc,Name); +#endif +#ifdef _WIN_ALL + 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); +#endif +} + + +// Extra data stored directly in file header. +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name) +{ +#ifdef _UNIX + if (Cmd->ProcessOwners && Arc.Format==RARFMT50 && Arc.FileHead.UnixOwnerSet) + SetUnixOwner(Arc,Name); +#endif +} + + + + +// Calculate a number of path components except \. and \.. +static int CalcAllowedDepth(const wchar *Name) +{ + int AllowedDepth=0; + while (*Name!=0) + { + if (IsPathDiv(Name[0]) && Name[1]!=0 && !IsPathDiv(Name[1])) + { + bool Dot=Name[1]=='.' && (IsPathDiv(Name[2]) || Name[2]==0); + bool Dot2=Name[1]=='.' && Name[2]=='.' && (IsPathDiv(Name[3]) || Name[3]==0); + if (!Dot && !Dot2) + AllowedDepth++; + } + Name++; + } + return AllowedDepth; +} + + +// Check if all existing path components are directories and not links. +static bool LinkInPath(const wchar *Name) +{ + wchar Path[NM]; + if (wcslen(Name)>=ASIZE(Path)) + return true; // It should not be that long, skip. + wcsncpyz(Path,Name,ASIZE(Path)); + for (wchar *s=Path+wcslen(Path)-1;s>Path;s--) + if (IsPathDiv(*s)) + { + *s=0; + FindData FD; + if (FindFile::FastFind(Path,&FD,true) && (FD.IsLink || !FD.IsDir)) + return true; + } + return false; +} + + +bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName) +{ + // 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)) + return false; + + // Number of ".." in link target. + int UpLevels=0; + for (int Pos=0;*TargetName!=0;Pos++) + { + bool Dot2=TargetName[0]=='.' && TargetName[1]=='.' && + (IsPathDiv(TargetName[2]) || TargetName[2]==0) && + (Pos==0 || IsPathDiv(*(TargetName-1))); + if (Dot2) + UpLevels++; + TargetName++; + } + // If link target includes "..", it must not have another links + // in the 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. + if (UpLevels>0 && LinkInPath(PrepSrcName)) + return false; + + // We could check just prepared src name, but for extra safety + // we check both original (as from archive header) and prepared + // (after applying the destination path and -ep switches) names. + + int AllowedDepth=CalcAllowedDepth(SrcName); // Original name depth. + + // Remove the destination path from prepared name if any. We should not + // count the destination path depth, because the link target must point + // inside of this path, not outside of it. + size_t ExtrPathLength=wcslen(Cmd->ExtrPath); + if (ExtrPathLength>0 && wcsncmp(PrepSrcName,Cmd->ExtrPath,ExtrPathLength)==0) + { + PrepSrcName+=ExtrPathLength; + while (IsPathDiv(*PrepSrcName)) + PrepSrcName++; + } + int PrepAllowedDepth=CalcAllowedDepth(PrepSrcName); + + return AllowedDepth>=UpLevels && PrepAllowedDepth>=UpLevels; +} + + +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName) +{ +#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); + if (Arc.Format==RARFMT50) + return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead); +#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) + return CreateReparsePoint(Cmd,LinkName,&Arc.FileHead); +#endif + return false; +} diff --git a/reference/unrar/extinfo.hpp b/reference/unrar/extinfo.hpp new file mode 100644 index 00000000..2b0005da --- /dev/null +++ b/reference/unrar/extinfo.hpp @@ -0,0 +1,23 @@ +#ifndef _RAR_EXTINFO_ +#define _RAR_EXTINFO_ + +bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName); +bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName); +#ifdef _UNIX +void SetUnixOwner(Archive &Arc,const wchar *FileName); +#endif + +bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); + +void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize); + +#ifdef _WIN_ALL +bool SetPrivilege(LPCTSTR PrivName); +#endif + +void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name); +void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name); +void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name); + + +#endif diff --git a/reference/unrar/extract.cpp b/reference/unrar/extract.cpp new file mode 100644 index 00000000..f9461136 --- /dev/null +++ b/reference/unrar/extract.cpp @@ -0,0 +1,1167 @@ +#include "rar.hpp" + +CmdExtract::CmdExtract(CommandData *Cmd) +{ + CmdExtract::Cmd=Cmd; + + *ArcName=0; + + *DestFileName=0; + + TotalFileCount=0; + Unp=new Unpack(&DataIO); +#ifdef RAR_SMP + Unp->SetThreads(Cmd->Threads); +#endif +} + + +CmdExtract::~CmdExtract() +{ + delete Unp; +} + + +void CmdExtract::DoExtract() +{ +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + Fat32=NotFat32=false; +#endif + PasswordCancelled=false; + DataIO.SetCurrentCommand(Cmd->Command[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. + while (true) + { + EXTRACT_ARC_CODE Code=ExtractArchive(); + if (Code!=EXTRACT_ARC_REPEAT) + break; + } + if (FindFile::FastFind(ArcName,&FD)) + DataIO.ProcessedArcSize+=FD.Size; + } + + // Clean user entered password. Not really required, just for extra safety. + if (Cmd->ManualPassword) + Cmd->Password.Clean(); + + if (TotalFileCount==0 && Cmd->Command[0]!='I' && + ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password. + { + if (!PasswordCancelled) + uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName); + ErrHandler.SetErrorCode(RARX_NOFILES); + } + else + if (!Cmd->DisableDone) + if (Cmd->Command[0]=='I') + mprintf(St(MDone)); + else + if (ErrHandler.GetErrorCount()==0) + mprintf(St(MExtrAllOk)); + else + mprintf(St(MExtrTotalErr),ErrHandler.GetErrorCount()); +} + + +void CmdExtract::ExtractArchiveInit(Archive &Arc) +{ + DataIO.UnpArcSize=Arc.FileLength(); + + FileCount=0; + MatchedArgs=0; +#ifndef SFX_MODULE + FirstFile=true; +#endif + + PasswordAll=(Cmd->Password.IsSet()); + + DataIO.UnpVolume=false; + + PrevProcessed=false; + AllMatchesExact=true; + ReconstructDone=false; + AnySolidDataUnpackedWell=false; + + StartTime.SetCurrentTime(); +} + + +EXTRACT_ARC_CODE CmdExtract::ExtractArchive() +{ + Archive Arc(Cmd); + if (!Arc.WOpen(ArcName)) + return EXTRACT_ARC_NEXT; + + if (!Arc.IsArchive(true)) + { +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (CmpExt(ArcName,L"rev")) + { + wchar FirstVolName[NM]; + VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true); + + // If several volume names from same volume set are specified + // and current volume is not first in set and first volume is present + // and specified too, let's skip the current volume. + if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && + Cmd->ArcNames.Search(FirstVolName,false)) + return EXTRACT_ARC_NEXT; + RecVolumesTest(Cmd,NULL,ArcName); + TotalFileCount++; // Suppress "No files to extract" message. + return EXTRACT_ARC_NEXT; + } +#endif + + mprintf(St(MNotRAR),ArcName); + +#ifndef SFX_MODULE + if (CmpExt(ArcName,L"rar")) +#endif + ErrHandler.SetErrorCode(RARX_WARNING); + return EXTRACT_ARC_NEXT; + } + + if (Arc.FailedHeaderDecryption) // Bad archive password. + return EXTRACT_ARC_NEXT; + +#ifndef SFX_MODULE + if (Arc.Volume && !Arc.FirstVolume) + { + wchar FirstVolName[NM]; + VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering); + + // If several volume names from same volume set are specified + // and current volume is not first in set and first volume is present + // and specified too, let's skip the current volume. + if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && + Cmd->ArcNames.Search(FirstVolName,false)) + return EXTRACT_ARC_NEXT; + } +#endif + + int64 VolumeSetSize=0; // Total size of volumes after the current volume. + + if (Arc.Volume) + { + // Calculate the total size of all accessible volumes. + // This size is necessary to display the correct total progress indicator. + + wchar NextName[NM]; + wcscpy(NextName,Arc.FileName); + + while (true) + { + // 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); + FindData FD; + if (FindFile::FastFind(NextName,&FD)) + VolumeSetSize+=FD.Size; + else + break; + } + DataIO.TotalArcSize+=VolumeSetSize; + } + + ExtractArchiveInit(Arc); + + if (*Cmd->Command=='T' || *Cmd->Command=='I') + Cmd->Test=true; + + + if (*Cmd->Command=='I') + { + Cmd->DisablePercentage=true; + } + else + uiStartArchiveExtract(!Cmd->Test,ArcName); + + Arc.ViewComment(); + + + while (1) + { + size_t Size=Arc.ReadHeader(); + + + bool Repeat=false; + if (!ExtractCurrentFile(Arc,Size,Repeat)) + if (Repeat) + { + // If we started extraction from not first volume and need to + // restart it from first, we must correct DataIO.TotalArcSize + // for correct total progress display. We subtract the size + // of current volume and all volumes after it and add the size + // of new (first) volume. + FindData OldArc,NewArc; + if (FindFile::FastFind(Arc.FileName,&OldArc) && + FindFile::FastFind(ArcName,&NewArc)) + DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size; + return EXTRACT_ARC_REPEAT; + } + else + break; + } + + +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (Cmd->Test && Arc.Volume) + RecVolumesTest(Cmd,&Arc,ArcName); +#endif + + return EXTRACT_ARC_NEXT; +} + + +bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) +{ + // We can get negative sizes in corrupt archive and it is unacceptable + // for size comparisons in CmdExtract::UnstoreFile and ComprDataIO::UnpRead, + // where we cast sizes to size_t and can exceed another read or available + // size. We could fix it when reading an archive. But we prefer to do it + // here, because this function is called directly in unrar.dll, so we fix + // bad parameters passed to dll. Also we want to see real negative sizes + // in the listing of corrupt archive. + if (Arc.FileHead.PackSize<0) + Arc.FileHead.PackSize=0; + if (Arc.FileHead.UnpSize<0) + Arc.FileHead.UnpSize=0; + + wchar Command=Cmd->Command[0]; + if (HeaderSize==0) + if (DataIO.UnpVolume) + { +#ifdef NOVOLUME + return false; +#else + // Supposing we unpack an old RAR volume without end of archive record + // and last file is not split between volumes. + if (!MergeArchive(Arc,&DataIO,false,Command)) + { + ErrHandler.SetErrorCode(RARX_WARNING); + return false; + } +#endif + } + else + return false; + HEADER_TYPE HeaderType=Arc.GetHeaderType(); + if (HeaderType!=HEAD_FILE) + { +#ifndef SFX_MODULE + if (HeaderType==HEAD3_OLDSERVICE && PrevProcessed) + SetExtraInfo20(Cmd,Arc,DestFileName); +#endif + if (HeaderType==HEAD_SERVICE && PrevProcessed) + SetExtraInfo(Cmd,Arc,DestFileName); + if (HeaderType==HEAD_ENDARC) + if (Arc.EndArcHead.NextVolume) + { +#ifndef NOVOLUME + if (!MergeArchive(Arc,&DataIO,false,Command)) + { + ErrHandler.SetErrorCode(RARX_WARNING); + return false; + } +#endif + Arc.Seek(Arc.CurBlockPos,SEEK_SET); + return true; + } + else + return false; + Arc.SeekToNext(); + return true; + } + PrevProcessed=false; + + if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) + return false; + + int MatchType=MATCH_WILDSUBPATH; + + bool EqualNames=false; + wchar MatchedArg[NM]; + int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,MatchedArg,ASIZE(MatchedArg)); + bool MatchFound=MatchNumber!=0; +#ifndef SFX_MODULE + if (Cmd->ExclPath==EXCL_BASEPATH) + { + wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath)); + *PointToName(Cmd->ArcPath)=0; + if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here. + *Cmd->ArcPath=0; + } +#endif + if (MatchFound && !EqualNames) + AllMatchesExact=false; + + Arc.ConvertAttributes(); + +#if !defined(SFX_MODULE) && !defined(RARDLL) + if (Arc.FileHead.SplitBefore && FirstFile) + { + wchar CurVolName[NM]; + wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName)); + VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering); + + if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName)) + { + // If first volume name does not match the current name and if such + // volume name really exists, let's unpack from this first volume. + Repeat=true; + return false; + } +#ifndef RARDLL + if (!ReconstructDone) + { + ReconstructDone=true; + if (RecVolumesRestore(Cmd,Arc.FileName,true)) + { + Repeat=true; + return false; + } + } +#endif + wcsncpyz(ArcName,CurVolName,ASIZE(ArcName)); + } +#endif + + wchar ArcFileName[NM]; + ConvertPath(Arc.FileHead.FileName,ArcFileName); + + if (Arc.FileHead.Version) + { + if (Cmd->VersionControl!=1 && !EqualNames) + { + if (Cmd->VersionControl==0) + MatchFound=false; + int Version=ParseVersionFileName(ArcFileName,false); + if (Cmd->VersionControl-1==Version) + ParseVersionFileName(ArcFileName,true); + else + MatchFound=false; + } + } + else + if (!Arc.IsArcDir() && Cmd->VersionControl>1) + MatchFound=false; + + DataIO.UnpVolume=Arc.FileHead.SplitAfter; + DataIO.NextVolumeMissing=false; + + Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET); + + bool ExtrFile=false; + bool SkipSolid=false; + +#ifndef SFX_MODULE + if (FirstFile && (MatchFound || Arc.Solid) && Arc.FileHead.SplitBefore) + { + if (MatchFound) + { + uiMsg(UIERROR_NEEDPREVVOL,Arc.FileName,ArcFileName); +#ifdef RARDLL + Cmd->DllError=ERAR_BAD_DATA; +#endif + ErrHandler.SetErrorCode(RARX_OPEN); + } + MatchFound=false; + } + + FirstFile=false; +#endif + + if (MatchFound || (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 + // additional uiStartFileExtract calls with updated parameters. + if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) + return false; + + ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); + + // DestFileName can be set empty in case of excessive -ap switch. + ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; + + if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X')) + { + FindData FD; + if (FindFile::FastFind(DestFileName,&FD)) + { + 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 + // directory is older. + + if (!FD.IsDir || FD.mtimeFreshFiles) + ExtrFile=false; + } + + if (!CheckUnpVer(Arc,ArcFileName)) + { + ErrHandler.SetErrorCode(RARX_FATAL); +#ifdef RARDLL + Cmd->DllError=ERAR_UNKNOWN_FORMAT; +#endif + Arc.SeekToNext(); + return !Arc.Solid; // Can try extracting next file only in non-solid archive. + } + + while (true) // Repeat password prompt in case of wrong password here. + { + if (Arc.FileHead.Encrypted) + { + // Stop archive extracting if user cancelled a password prompt. +#ifdef RARDLL + if (!ExtrDllGetPassword()) + { + Cmd->DllError=ERAR_MISSING_PASSWORD; + return false; + } +#else + if (!ExtrGetPassword(Arc,ArcFileName)) + { + PasswordCancelled=true; + return false; + } +#endif + // Skip only the current encrypted file if empty password is entered. + // Actually our "cancel" code above intercepts empty passwords too now, + // so we keep the code below just in case we'll decide process empty + // and cancelled passwords differently sometimes. + if (!Cmd->Password.IsSet()) + { + ErrHandler.SetErrorCode(RARX_WARNING); +#ifdef RARDLL + Cmd->DllError=ERAR_MISSING_PASSWORD; +#endif + ExtrFile=false; + } + } + + + // Set a password before creating the file, so we can skip creating + // in case of wrong password. + SecPassword FilePassword=Cmd->Password; +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + ConvertDosPassword(Arc,FilePassword); +#endif + + byte PswCheck[SIZE_PSWCHECK]; + DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, + Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, + Arc.FileHead.InitV,Arc.FileHead.Lg2Count, + Arc.FileHead.HashKey,PswCheck); + + // If header is damaged, we cannot rely on password check value, + // because it can be damaged too. + if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && + memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && + !Arc.BrokenHeader) + { + uiMsg(UIWAIT_BADPSW,ArcFileName); + + if (!PasswordAll) // If entered manually and not through -p. + { + Cmd->Password.Clean(); + continue; // Request a password again. + } +#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; + } + +#ifdef RARDLL + if (*Cmd->DllDestName!=0) + wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); +#endif + + File CurFile; + + bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; + if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) + { + if (ExtrFile && Command!='P' && !Cmd->Test) + { + // Overwrite prompt for symbolic and hard links. + bool UserReject=false; + if (FileExist(DestFileName) && !UserReject) + FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + if (UserReject) + ExtrFile=false; + } + } + else + if (Arc.IsArcDir()) + { + if (!ExtrFile || Command=='P' || Command=='I' || Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) + return true; + TotalFileCount++; + ExtrCreateDir(Arc,ArcFileName); + // It is important to not increment MatchedArgs here, so we extract + // dir with its entire contents and not dir record only even if + // dir record precedes files. + return true; + } + else + if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY). + ExtrFile=ExtrCreateFile(Arc,CurFile); + + if (!ExtrFile && Arc.Solid) + { + SkipSolid=true; + ExtrFile=true; + + // We changed SkipSolid, so we need to call uiStartFileExtract + // 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. + if (!uiStartFileExtract(ArcFileName,false,false,true)) + return false; + } + if (ExtrFile) + { + // Set it in test mode, so we also test subheaders such as NTFS streams + // after tested file. + if (Cmd->Test) + PrevProcessed=true; + + bool TestMode=Cmd->Test || SkipSolid; // Unpack to memory, not to disk. + + if (!SkipSolid) + { + if (!TestMode && Command!='P' && CurFile.IsDevice()) + { + uiMsg(UIERROR_INVALIDNAME,Arc.FileName,DestFileName); + ErrHandler.WriteError(Arc.FileName,DestFileName); + } + TotalFileCount++; + } + FileCount++; + if (Command!='I') + if (SkipSolid) + mprintf(St(MExtrSkipFile),ArcFileName); + else + switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch. + { + case 'T': + mprintf(St(MExtrTestFile),ArcFileName); + break; +#ifndef SFX_MODULE + case 'P': + mprintf(St(MExtrPrinting),ArcFileName); + break; +#endif + case 'X': + case 'E': + mprintf(St(MExtrFile),DestFileName); + break; + } + if (!Cmd->DisablePercentage) + mprintf(L" "); + + DataIO.CurUnpRead=0; + DataIO.CurUnpWrite=0; + DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); + DataIO.PackedDataHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); + DataIO.SetPackedSizeToRead(Arc.FileHead.PackSize); + DataIO.SetFiles(&Arc,&CurFile); + DataIO.SetTestMode(TestMode); + DataIO.SetSkipUnpCRC(SkipSolid); + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + if (!TestMode && !Arc.BrokenHeader && + Arc.FileHead.UnpSize>0xffffffff && (Fat32 || !NotFat32)) + { + if (!Fat32) // Not detected yet. + NotFat32=!(Fat32=IsFAT(Cmd->ExtrPath)); + if (Fat32) + uiMsg(UIMSG_FAT32SIZE); // Inform user about FAT32 size limit. + } +#endif + + if (!TestMode && !Arc.BrokenHeader && + (Arc.FileHead.PackSize<<11)>Arc.FileHead.UnpSize && + (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize)) + CurFile.Prealloc(Arc.FileHead.UnpSize); + + CurFile.SetAllowDelete(!Cmd->KeepBroken); + + bool FileCreateMode=!TestMode && !SkipSolid && Command!='P'; + bool ShowChecksum=true; // Display checksum verification result. + + bool LinkSuccess=true; // Assume success for test mode. + if (LinkEntry) + { + FILE_SYSTEM_REDIRECT Type=Arc.FileHead.RedirType; + + if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) + { + wchar NameExisting[NM]; + ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting)); + if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. + if (Type==FSREDIR_HARDLINK) + LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting)); + else + LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); + } + else + if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) + { + if (FileCreateMode) + LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); + } + else + { + uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName); + LinkSuccess=false; + } + + if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode) + { + // RAR 5.x links have a valid data checksum even in case of + // failure, because they do not store any data. + // We do not want to display "OK" in this case. + // For 4.x symlinks we verify the checksum only when extracting, + // but not when testing an archive. + ShowChecksum=false; + } + PrevProcessed=FileCreateMode && LinkSuccess; + } + else + if (!Arc.FileHead.SplitBefore) + if (Arc.FileHead.Method==0) + UnstoreFile(DataIO,Arc.FileHead.UnpSize); + else + { + Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); + Unp->SetDestSize(Arc.FileHead.UnpSize); +#ifndef SFX_MODULE + if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) + Unp->DoUnpack(15,FileCount>1 && Arc.Solid); + else +#endif + Unp->DoUnpack(Arc.FileHead.UnpVer,Arc.FileHead.Solid); + } + + Arc.SeekToNext(); + + // We check for "split after" flag to detect partially extracted files + // from incomplete volume sets. For them file header contains packed + // data hash, which must not be compared against unpacked data hash + // to prevent accidental match. Moreover, for -m0 volumes packed data + // hash would match truncated unpacked data hash and lead to fake "OK" + // in incomplete volume set. + bool ValidCRC=!Arc.FileHead.SplitAfter && DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL); + + // We set AnySolidDataUnpackedWell to true if we found at least one + // valid non-zero solid file in preceding solid stream. If it is true + // and if current encrypted file is broken, we do not need to hint + // about a wrong password and can report CRC error only. + if (!Arc.FileHead.Solid) + AnySolidDataUnpackedWell=false; // Reset the flag, because non-solid file is found. + 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) + { + if (ValidCRC) + { + if (Command!='P' && Command!='I') + mprintf(L"%s%s ",Cmd->DisablePercentage ? L" ":L"\b\b\b\b\b ", + Arc.FileHead.FileHash.Type==HASH_NONE ? L" ?":St(MOk)); + } + else + { + if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || + Arc.BrokenHeader) && !AnySolidDataUnpackedWell) + uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName); + else + uiMsg(UIERROR_CHECKSUM,Arc.FileName,ArcFileName); + BrokenFile=true; + ErrHandler.SetErrorCode(RARX_CRC); +#ifdef RARDLL + // If we already have ERAR_EOPEN as result of missing volume + // or ERAR_BAD_PASSWORD for RAR5 wrong password, + // we should not replace it with less precise ERAR_BAD_DATA. + if (Cmd->DllError!=ERAR_EOPEN && Cmd->DllError!=ERAR_BAD_PASSWORD) + Cmd->DllError=ERAR_BAD_DATA; +#endif + } + } + else + mprintf(L"\b\b\b\b\b "); + + if (!TestMode && (Command=='X' || Command=='E') && + (!LinkEntry || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && + (!BrokenFile || Cmd->KeepBroken)) + { + // We could preallocate more space that really written to broken file. + if (BrokenFile) + CurFile.Truncate(); + +#if defined(_WIN_ALL) || defined(_EMX) + if (Cmd->ClearArc) + Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; +#endif + + + CurFile.SetOpenFileTime( + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + CurFile.Close(); +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + if (Cmd->SetCompressedAttr && + (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0) + SetFileCompression(CurFile.FileName,true); +#endif + SetFileHeaderExtra(Cmd,Arc,CurFile.FileName); + + CurFile.SetCloseFileTime( + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(CurFile.FileName,Arc.FileHead.FileAttr)) + uiMsg(UIERROR_FILEATTR,Arc.FileName,CurFile.FileName); + + PrevProcessed=true; + } + } + } + // It is important to increment it for files, but not dirs. So we extract + // dir with its entire contents, not just dir record only even if dir + // record precedes files. + if (MatchFound) + MatchedArgs++; + if (DataIO.NextVolumeMissing) + return false; + if (!ExtrFile) + if (!Arc.Solid) + Arc.SeekToNext(); + else + if (!SkipSolid) + return false; + return true; +} + + +void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize) +{ + // 512 KB and larger buffer reported to reduce performance on old XP + // computers with WDC WD2000JD HDD. According to test made by user + // 256 KB buffer is optimal. + Array Buffer(0x40000); + while (1) + { + uint Code=DataIO.UnpRead(&Buffer[0],Buffer.Size()); + if (Code==0 || (int)Code==-1) + break; + Code=Code=0) + DestUnpSize-=Code; + } +} + + +bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +{ + SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. + + File Existing; + if (!Existing.WOpen(NameExisting)) + { + uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); + uiMsg(UIERROR_FILECOPYHINT,ArcName); +#ifdef RARDLL + Cmd->DllError=ERAR_EREFERENCE; +#endif + return false; + } + + Array Buffer(0x100000); + int64 CopySize=0; + + while (true) + { + Wait(); + int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); + if (ReadSize==0) + break; + New.Write(&Buffer[0],ReadSize); + CopySize+=ReadSize; + } + + return true; +} + + +void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) +{ + wcsncpyz(DestName,Cmd->ExtrPath,DestSize); + + if (*Cmd->ExtrPath!=0) + { + wchar LastChar=*PointToLastChar(Cmd->ExtrPath); + // We need IsPathDiv check here to correctly handle Unix forward slash + // in the end of destination path in Windows: rar x arc dest/ + // IsDriveDiv is needed for current drive dir: rar x arc d: + if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar)) + { + // Destination path can be without trailing slash if it come from GUI shell. + AddEndSlash(DestName,DestSize); + } + } + +#ifndef SFX_MODULE + if (Cmd->AppendArcNameToPath) + { + wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize); + SetExt(DestName,NULL,DestSize); + AddEndSlash(DestName,DestSize); + } +#endif + +#ifndef SFX_MODULE + size_t ArcPathLength=wcslen(Cmd->ArcPath); + if (ArcPathLength>0) + { + size_t NameLength=wcslen(ArcFileName); + + // Earlier we compared lengths only here, but then noticed a cosmetic bug + // in WinRAR. When extracting a file reference from subfolder with + // "Extract relative paths", so WinRAR sets ArcPath, if reference target + // is missing, error message removed ArcPath both from reference and target + // names. If target was stored in another folder, its name looked wrong. + if (NameLength>=ArcPathLength && + wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 && + (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || + IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0)) + { + ArcFileName+=Min(ArcPathLength,NameLength); + while (IsPathDiv(*ArcFileName)) + ArcFileName++; + if (*ArcFileName==0) // Excessive -ap switch. + { + *DestName=0; + return; + } + } + } +#endif + + wchar Command=Cmd->Command[0]; + // Use -ep3 only in systems, where disk letters are exist, not in Unix. + bool AbsPaths=Cmd->ExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':'); + + // We do not use any user specified destination paths when extracting + // absolute paths in -ep3 mode. + if (AbsPaths) + *DestName=0; + + if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) + wcsncatz(DestName,PointToName(ArcFileName),DestSize); + else + wcsncatz(DestName,ArcFileName,DestSize); + +#ifdef _WIN_ALL + // Must do after Cmd->ArcPath processing above, so file name and arc path + // trailing spaces are in sync. + if (!Cmd->AllowIncompatNames) + MakeNameCompatible(DestName); +#endif + + wchar DiskLetter=toupperw(DestName[0]); + + if (AbsPaths) + { + if (DestName[1]=='_' && IsPathDiv(DestName[2]) && + DiskLetter>='A' && DiskLetter<='Z') + DestName[1]=':'; + else + if (DestName[0]=='_' && DestName[1]=='_') + { + // Convert __server\share to \\server\share. + DestName[0]=CPATHDIVIDER; + DestName[1]=CPATHDIVIDER; + } + } +} + + +#ifdef RARDLL +bool CmdExtract::ExtrDllGetPassword() +{ + if (!Cmd->Password.IsSet()) + { + if (Cmd->Callback!=NULL) + { + wchar PasswordW[MAXPASSWORD]; + *PasswordW=0; + if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1) + *PasswordW=0; + if (*PasswordW==0) + { + char PasswordA[MAXPASSWORD]; + *PasswordA=0; + if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) + *PasswordA=0; + GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); + cleandata(PasswordA,sizeof(PasswordA)); + } + Cmd->Password.Set(PasswordW); + cleandata(PasswordW,sizeof(PasswordW)); + Cmd->ManualPassword=true; + } + if (!Cmd->Password.IsSet()) + return false; + } + return true; +} +#endif + + +#ifndef RARDLL +bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) +{ + if (!Cmd->Password.IsSet()) + { + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password) || !Cmd->Password.IsSet()) + { + // Suppress "test is ok" message in GUI if user entered + // an empty password or cancelled a password prompt. + uiMsg(UIERROR_INCERRCOUNT); + + return false; + } + Cmd->ManualPassword=true; + } +#if !defined(SILENT) + else + if (!PasswordAll && !Arc.FileHead.Solid) + { + eprintf(St(MUseCurPsw),ArcFileName); + switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll))) + { + case -1: + ErrHandler.Exit(RARX_USERBREAK); + case 2: + if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) + return false; + break; + case 3: + PasswordAll=true; + break; + } + } +#endif + return true; +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd) +{ + if (Arc.Format==RARFMT15 && Arc.FileHead.HostOS==HOST_MSDOS) + { + // We need the password in OEM encoding if file was encrypted by + // native RAR/DOS (not extender based). Let's make the conversion. + wchar PlainPsw[MAXPASSWORD]; + Cmd->Password.Get(PlainPsw,ASIZE(PlainPsw)); + char PswA[MAXPASSWORD]; + CharToOemBuffW(PlainPsw,PswA,ASIZE(PswA)); + PswA[ASIZE(PswA)-1]=0; + CharToWide(PswA,PlainPsw,ASIZE(PlainPsw)); + DestPwd.Set(PlainPsw); + cleandata(PlainPsw,sizeof(PlainPsw)); + cleandata(PswA,sizeof(PswA)); + } +} +#endif + + +void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) +{ + if (Cmd->Test) + { + mprintf(St(MExtrTestFile),ArcFileName); + mprintf(L" %s",St(MOk)); + return; + } + + MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + bool DirExist=false; + if (MDCode!=MKDIR_SUCCESS) + { + DirExist=FileExist(DestFileName); + if (DirExist && !IsDir(GetFileAttr(DestFileName))) + { + // File with name same as this directory exists. Propose user + // to overwrite it. + bool UserReject; + FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); + DirExist=false; + } + if (!DirExist) + { + CreatePath(DestFileName,true); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); + if (MDCode!=MKDIR_SUCCESS) + { + wchar OrigName[ASIZE(DestFileName)]; + wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + MakeNameUsable(DestFileName,true); + CreatePath(DestFileName,true); + MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); +#ifndef SFX_MODULE + if (MDCode==MKDIR_SUCCESS) + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); +#endif + } + } + } + if (MDCode==MKDIR_SUCCESS) + { + mprintf(St(MCreatDir),DestFileName); + mprintf(L" %s",St(MOk)); + PrevProcessed=true; + } + else + if (DirExist) + { + if (!Cmd->IgnoreGeneralAttr) + SetFileAttr(DestFileName,Arc.FileHead.FileAttr); + PrevProcessed=true; + } + else + { + uiMsg(UIERROR_DIRCREATE,Arc.FileName,DestFileName); + ErrHandler.SysErrMsg(); +#ifdef RARDLL + Cmd->DllError=ERAR_ECREATE; +#endif + ErrHandler.SetErrorCode(RARX_CREATE); + } + if (PrevProcessed) + { +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + if (Cmd->SetCompressedAttr && + (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT()) + SetFileCompression(DestFileName,true); +#endif + SetFileHeaderExtra(Cmd,Arc,DestFileName); + SetDirTime(DestFileName, + Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, + Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, + Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); + } +} + + +bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) +{ + bool Success=true; + wchar Command=Cmd->Command[0]; +#if !defined(SFX_MODULE) + if (Command=='P') + CurFile.SetHandleType(FILE_HANDLESTD); +#endif + if ((Command=='E' || Command=='X') && !Cmd->Test) + { + bool UserReject; + // Specify "write only" mode to avoid OpenIndiana NAS problems + // with SetFileTime and read+write files. + if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + { + Success=false; + if (!UserReject) + { + ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); +#ifdef RARDLL + Cmd->DllError=ERAR_ECREATE; +#endif + if (!IsNameUsable(DestFileName)) + { + uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); + + wchar OrigName[ASIZE(DestFileName)]; + wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); + + MakeNameUsable(DestFileName,true); + + CreatePath(DestFileName,true); + if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) + { +#ifndef SFX_MODULE + uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); +#endif + Success=true; + } + else + ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); + } + } + } + } + return Success; +} + + +bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) +{ + bool WrongVer; + if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives. + WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5; + else + { +#ifdef SFX_MODULE // SFX can unpack only RAR 2.9 archives. + WrongVer=Arc.FileHead.UnpVer!=VER_UNPACK; +#else // All formats since 1.3 for RAR. + WrongVer=Arc.FileHead.UnpVer<13 || Arc.FileHead.UnpVer>VER_UNPACK; +#endif + } + + // We can unpack stored files regardless of compression version field. + if (Arc.FileHead.Method==0) + WrongVer=false; + + if (WrongVer) + { + ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName); + uiMsg(UIERROR_NEWERRAR,Arc.FileName); + } + return !WrongVer; +} diff --git a/reference/unrar/extract.hpp b/reference/unrar/extract.hpp new file mode 100644 index 00000000..85a21f53 --- /dev/null +++ b/reference/unrar/extract.hpp @@ -0,0 +1,62 @@ +#ifndef _RAR_EXTRACT_ +#define _RAR_EXTRACT_ + +enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT}; + +class CmdExtract +{ + private: + EXTRACT_ARC_CODE ExtractArchive(); + bool ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize); + void ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize); +#ifdef RARDLL + bool ExtrDllGetPassword(); +#else + bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName); +#endif +#if defined(_WIN_ALL) && !defined(SFX_MODULE) + void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd); +#endif + void ExtrCreateDir(Archive &Arc,const wchar *ArcFileName); + bool ExtrCreateFile(Archive &Arc,File &CurFile); + bool CheckUnpVer(Archive &Arc,const wchar *ArcFileName); + + RarTime StartTime; // time when extraction started + + CommandData *Cmd; + + ComprDataIO DataIO; + Unpack *Unp; + unsigned long TotalFileCount; + + unsigned long FileCount; + unsigned long MatchedArgs; + bool FirstFile; + bool AllMatchesExact; + bool ReconstructDone; + + // If any non-zero solid file was successfully unpacked before current. + // If true and if current encrypted file is broken, obviously + // the password is correct and we can report broken CRC without + // any wrong password hints. + bool AnySolidDataUnpackedWell; + + wchar ArcName[NM]; + + bool PasswordAll; + bool PrevProcessed; // If previous file was successfully extracted or tested. + wchar DestFileName[NM]; + bool PasswordCancelled; +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) + bool Fat32,NotFat32; +#endif + public: + CmdExtract(CommandData *Cmd); + ~CmdExtract(); + void DoExtract(); + void ExtractArchiveInit(Archive &Arc); + bool ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat); + static void UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize); +}; + +#endif diff --git a/reference/unrar/filcreat.cpp b/reference/unrar/filcreat.cpp new file mode 100644 index 00000000..a64a7d4d --- /dev/null +++ b/reference/unrar/filcreat.cpp @@ -0,0 +1,163 @@ +#include "rar.hpp" + +// If NewFile==NULL, we delete created file after user confirmation. +// It is useful we 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 *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly) +{ + if (UserReject!=NULL) + *UserReject=false; +#ifdef _WIN_ALL + bool ShortNameChanged=false; +#endif + while (FileExist(Name)) + { +#if defined(_WIN_ALL) + if (!ShortNameChanged) + { + // Avoid the infinite loop if UpdateExistingShortName returns + // the same name. + ShortNameChanged=true; + + // Maybe our long name matches the short name of existing file. + // Let's check if we can change the short name. + if (UpdateExistingShortName(Name)) + continue; + } + // Allow short name check again. It is necessary, because rename and + // autorename below can change the name, so we need to check it again. + ShortNameChanged=false; +#endif + UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0)); + + if (Choice==UIASKREP_R_REPLACE) + break; + if (Choice==UIASKREP_R_SKIP) + { + if (UserReject!=NULL) + *UserReject=true; + return false; + } + if (Choice==UIASKREP_R_CANCEL) + ErrHandler.Exit(RARX_USERBREAK); + } + + // Try to truncate the existing file first instead of delete, + // so we preserve existing file permissions such as NTFS permissions. + uint FileMode=WriteOnly ? FMF_WRITE|FMF_SHAREREAD:FMF_UPDATE|FMF_SHAREREAD; + if (NewFile!=NULL && NewFile->Create(Name,FileMode)) + return true; + + CreatePath(Name,true); + return NewFile!=NULL ? NewFile->Create(Name,FileMode):DelFile(Name); +} + + +bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize) +{ + wchar NewName[NM]; + size_t NameLength=wcslen(Name); + wchar *Ext=GetExt(Name); + if (Ext==NULL) + Ext=Name+NameLength; + for (uint FileVer=1;;FileVer++) + { + swprintf(NewName,ASIZE(NewName),L"%.*ls(%u)%ls",uint(Ext-Name),Name,FileVer,Ext); + if (!FileExist(NewName)) + { + wcsncpyz(Name,NewName,MaxNameSize); + break; + } + if (FileVer>=1000000) + return false; + } + return true; +} + + +#if defined(_WIN_ALL) +// If we find a file, which short name is equal to 'Name', we try to change +// its short name, while preserving the long name. It helps when unpacking +// an archived file, which long name is equal to short name of already +// existing file. Otherwise we would overwrite the already existing file, +// even though its long name does not match the name of unpacking file. +bool UpdateExistingShortName(const wchar *Name) +{ + wchar LongPathName[NM]; + DWORD Res=GetLongPathName(Name,LongPathName,ASIZE(LongPathName)); + if (Res==0 || Res>=ASIZE(LongPathName)) + return false; + wchar ShortPathName[NM]; + Res=GetShortPathName(Name,ShortPathName,ASIZE(ShortPathName)); + if (Res==0 || Res>=ASIZE(ShortPathName)) + return false; + wchar *LongName=PointToName(LongPathName); + wchar *ShortName=PointToName(ShortPathName); + + // We continue only if file has a short name, which does not match its + // long name, and this short name is equal to name of file which we need + // to create. + if (*ShortName==0 || wcsicomp(LongName,ShortName)==0 || + wcsicomp(PointToName(Name),ShortName)!=0) + return false; + + // Generate the temporary new name for existing file. + wchar NewName[NM]; + *NewName=0; + for (int I=0;I<10000 && *NewName==0;I+=123) + { + // Here we copy the path part of file to create. We'll make the temporary + // file in the same folder. + wcsncpyz(NewName,Name,ASIZE(NewName)); + + // Here we set the random name part. + swprintf(PointToName(NewName),ASIZE(NewName),L"rtmp%d",I); + + // If such file is already exist, try next random name. + if (FileExist(NewName)) + *NewName=0; + } + + // If we could not generate the name not used by any other file, we return. + if (*NewName==0) + return false; + + // FastFind returns the name without path, but we need the fully qualified + // name for renaming, so we use the path from file to create and long name + // from existing file. + wchar FullName[NM]; + wcsncpyz(FullName,Name,ASIZE(FullName)); + SetName(FullName,LongName,ASIZE(FullName)); + + // Rename the existing file to randomly generated name. Normally it changes + // the short name too. + if (!MoveFile(FullName,NewName)) + return false; + + // Now we need to create the temporary empty file with same name as + // short name of our already existing file. We do it to occupy its previous + // short name and not allow to use it again when renaming the file back to + // its original long name. + File KeepShortFile; + bool Created=false; + if (!FileExist(Name)) + Created=KeepShortFile.Create(Name,FMF_WRITE|FMF_SHAREREAD); + + // Now we rename the existing file from temporary name to original long name. + // Since its previous short name is occupied by another file, it should + // get another short name. + MoveFile(NewName,FullName); + + if (Created) + { + // Delete the temporary zero length file occupying the short name, + KeepShortFile.Close(); + KeepShortFile.Delete(); + } + // We successfully changed the short name. Maybe sometimes we'll simplify + // this function by use of SetFileShortName Windows API call. + // But SetFileShortName is not available in older Windows. + return true; +} +#endif diff --git a/reference/unrar/filcreat.hpp b/reference/unrar/filcreat.hpp new file mode 100644 index 00000000..44f801d4 --- /dev/null +++ b/reference/unrar/filcreat.hpp @@ -0,0 +1,14 @@ +#ifndef _RAR_FILECREATE_ +#define _RAR_FILECREATE_ + +bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize, + bool *UserReject,int64 FileSize=INT64NDF, + RarTime *FileTime=NULL,bool WriteOnly=false); + +bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize); + +#if defined(_WIN_ALL) +bool UpdateExistingShortName(const wchar *Name); +#endif + +#endif diff --git a/reference/unrar/file.cpp b/reference/unrar/file.cpp new file mode 100644 index 00000000..5e6235a7 --- /dev/null +++ b/reference/unrar/file.cpp @@ -0,0 +1,729 @@ +#include "rar.hpp" + +File::File() +{ + hFile=FILE_BAD_HANDLE; + *FileName=0; + NewFile=false; + LastWrite=false; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + IgnoreReadErrors=false; + ErrorType=FILE_SUCCESS; + OpenShared=false; + AllowDelete=true; + AllowExceptions=true; +#ifdef _WIN_ALL + NoSequentialRead=false; + CreateMode=FMF_UNDEFINED; +#endif +} + + +File::~File() +{ + if (hFile!=FILE_BAD_HANDLE && !SkipClose) + if (NewFile) + Delete(); + else + Close(); +} + + +void File::operator = (File &SrcFile) +{ + hFile=SrcFile.hFile; + NewFile=SrcFile.NewFile; + LastWrite=SrcFile.LastWrite; + HandleType=SrcFile.HandleType; + wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName)); + SrcFile.SkipClose=true; +} + + +bool File::Open(const wchar *Name,uint Mode) +{ + ErrorType=FILE_SUCCESS; + FileHandle hNewFile; + bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0; + bool UpdateMode=(Mode & FMF_UPDATE)!=0; + bool WriteMode=(Mode & FMF_WRITE)!=0; +#ifdef _WIN_ALL + uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ; + if (UpdateMode) + Access|=GENERIC_WRITE; + uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ; + if (OpenShared) + ShareMode|=FILE_SHARE_WRITE; + uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN; + hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + + DWORD LastError; + if (hNewFile==FILE_BAD_HANDLE) + { + LastError=GetLastError(); + + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + { + hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); + + // For archive names longer than 260 characters first CreateFile + // (without \\?\) fails and sets LastError to 3 (access denied). + // We need the correct "file not found" error code to decide + // if we create a new archive or quit with "cannot create" error. + // So we need to check the error code after \\?\ CreateFile again, + // otherwise we'll fail to create new archives with long names. + // But we cannot simply assign the new code to LastError, + // because it would break "..\arcname.rar" relative names processing. + // First CreateFile returns the correct "file not found" code for such + // names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating + // dots as a directory name. So we check only for "file not found" + // error here and for other errors use the first CreateFile result. + if (GetLastError()==ERROR_FILE_NOT_FOUND) + LastError=ERROR_FILE_NOT_FOUND; + } + } + if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND) + ErrorType=FILE_NOTFOUND; + +#else + int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY); +#ifdef O_BINARY + flags|=O_BINARY; +#if defined(_AIX) && defined(_LARGE_FILE_API) + flags|=O_LARGEFILE; +#endif +#endif + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + + int handle=open(NameA,flags); +#ifdef LOCK_EX + +#ifdef _OSF_SOURCE + extern "C" int flock(int, int); +#endif + + if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1) + { + close(handle); + return false; + } +#endif + if (handle==-1) + hNewFile=FILE_BAD_HANDLE; + else + { +#ifdef FILE_USE_OPEN + hNewFile=handle; +#else + hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY); +#endif + } + if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT) + ErrorType=FILE_NOTFOUND; +#endif + NewFile=false; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + bool Success=hNewFile!=FILE_BAD_HANDLE; + if (Success) + { + hFile=hNewFile; + wcsncpyz(FileName,Name,ASIZE(FileName)); + } + return Success; +} + + +#if !defined(SFX_MODULE) +void File::TOpen(const wchar *Name) +{ + if (!WOpen(Name)) + ErrHandler.Exit(RARX_OPEN); +} +#endif + + +bool File::WOpen(const wchar *Name) +{ + if (Open(Name)) + return true; + ErrHandler.OpenErrorMsg(Name); + return false; +} + + +bool File::Create(const wchar *Name,uint Mode) +{ + // OpenIndiana based NAS and CIFS shares fail to set the file time if file + // was created in read+write mode and some data was written and not flushed + // before SetFileTime call. So we should use the write only mode if we plan + // SetFileTime call and do not need to read from file. + bool WriteMode=(Mode & FMF_WRITE)!=0; + bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared; +#ifdef _WIN_ALL + CreateMode=Mode; + uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE; + DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0; + + // Windows automatically removes dots and spaces in the end of file name, + // So we detect such names and process them with \\?\ prefix. + wchar *LastChar=PointToLastChar(Name); + bool Special=*LastChar=='.' || *LastChar==' '; + + if (Special && (Mode & FMF_STANDARDNAMES)==0) + hFile=FILE_BAD_HANDLE; + else + hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + + if (hFile==FILE_BAD_HANDLE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); + } + +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); +#ifdef FILE_USE_OPEN + hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666); +#else + hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY); +#endif +#endif + NewFile=true; + HandleType=FILE_HANDLENORMAL; + SkipClose=false; + wcsncpyz(FileName,Name,ASIZE(FileName)); + return hFile!=FILE_BAD_HANDLE; +} + + +#if !defined(SFX_MODULE) +void File::TCreate(const wchar *Name,uint Mode) +{ + if (!WCreate(Name,Mode)) + ErrHandler.Exit(RARX_FATAL); +} +#endif + + +bool File::WCreate(const wchar *Name,uint Mode) +{ + if (Create(Name,Mode)) + return true; + ErrHandler.CreateErrorMsg(Name); + return false; +} + + +bool File::Close() +{ + bool Success=true; + + if (hFile!=FILE_BAD_HANDLE) + { + if (!SkipClose) + { +#ifdef _WIN_ALL + // We use the standard system handle for stdout in Windows + // and it must not be closed here. + if (HandleType==FILE_HANDLENORMAL) + Success=CloseHandle(hFile)==TRUE; +#else +#ifdef FILE_USE_OPEN + Success=close(hFile)!=-1; +#else + Success=fclose(hFile)!=EOF; +#endif +#endif + } + hFile=FILE_BAD_HANDLE; + } + HandleType=FILE_HANDLENORMAL; + if (!Success && AllowExceptions) + ErrHandler.CloseError(FileName); + return Success; +} + + +bool File::Delete() +{ + if (HandleType!=FILE_HANDLENORMAL) + return false; + if (hFile!=FILE_BAD_HANDLE) + Close(); + if (!AllowDelete) + return false; + return DelFile(FileName); +} + + +bool File::Rename(const wchar *NewName) +{ + // No need to rename if names are already same. + bool Success=wcscmp(FileName,NewName)==0; + + if (!Success) + Success=RenameFile(FileName,NewName); + + if (Success) + wcscpy(FileName,NewName); + + return Success; +} + + +bool File::Write(const void *Data,size_t Size) +{ + if (Size==0) + return true; + if (HandleType==FILE_HANDLESTD) + { +#ifdef _WIN_ALL + hFile=GetStdHandle(STD_OUTPUT_HANDLE); +#else + // Cannot use the standard stdout here, because it already has wide orientation. + if (hFile==FILE_BAD_HANDLE) + { +#ifdef FILE_USE_OPEN + hFile=dup(STDOUT_FILENO); // Open new stdout stream. +#else + hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream. +#endif + } +#endif + } + bool Success; + while (1) + { + Success=false; +#ifdef _WIN_ALL + DWORD Written=0; + if (HandleType!=FILE_HANDLENORMAL) + { + // writing to stdout can fail in old Windows if data block is too large + const size_t MaxSize=0x4000; + for (size_t I=0;ISize && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff) + ErrHandler.WriteErrorFAT(FileName); +#endif + if (ErrHandler.AskRepeatWrite(FileName,false)) + { +#if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN) + clearerr(hFile); +#endif + if (Written0) + Seek(Tell()-Written,SEEK_SET); + continue; + } + ErrHandler.WriteError(NULL,FileName); + } + break; + } + LastWrite=true; + return Success; // It can return false only if AllowExceptions is disabled. +} + + +int File::Read(void *Data,size_t Size) +{ + int64 FilePos=0; // Initialized only to suppress some compilers warning. + + if (IgnoreReadErrors) + FilePos=Tell(); + int ReadSize; + while (true) + { + ReadSize=DirectRead(Data,Size); + if (ReadSize==-1) + { + ErrorType=FILE_READERROR; + if (AllowExceptions) + if (IgnoreReadErrors) + { + ReadSize=0; + for (size_t I=0;IMaxDeviceRead) +// Size=MaxDeviceRead; + hFile=GetStdHandle(STD_INPUT_HANDLE); +#else +#ifdef FILE_USE_OPEN + hFile=STDIN_FILENO; +#else + hFile=stdin; +#endif +#endif + } +#ifdef _WIN_ALL + // For pipes like 'type file.txt | rar -si arcname' ReadFile may return + // data in small ~4KB blocks. It may slightly reduce the compression ratio. + DWORD Read; + if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL)) + { + if (IsDevice() && Size>MaxDeviceRead) + return DirectRead(Data,MaxDeviceRead); + if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE) + return 0; + + // We had a bug report about failure to archive 1C database lock file + // 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB + // permanently locked. If our first read request uses too large buffer + // and if we are in -dh mode, so we were able to open the file, + // we'll fail with "Read error". So now we use try a smaller buffer size + // in case of lock error. + if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead && + GetLastError()==ERROR_LOCK_VIOLATION) + return DirectRead(Data,MaxLockedRead); + + return -1; + } + return Read; +#else +#ifdef FILE_USE_OPEN + ssize_t ReadSize=read(hFile,Data,Size); + if (ReadSize==-1) + return -1; + return (int)ReadSize; +#else + if (LastWrite) + { + fflush(hFile); + LastWrite=false; + } + clearerr(hFile); + size_t ReadSize=fread(Data,1,Size,hFile); + if (ferror(hFile)) + return -1; + return (int)ReadSize; +#endif +#endif +} + + +void File::Seek(int64 Offset,int Method) +{ + if (!RawSeek(Offset,Method) && AllowExceptions) + ErrHandler.SeekError(FileName); +} + + +bool File::RawSeek(int64 Offset,int Method) +{ + if (hFile==FILE_BAD_HANDLE) + return true; + if (Offset<0 && Method!=SEEK_SET) + { + Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset; + Method=SEEK_SET; + } +#ifdef _WIN_ALL + LONG HighDist=(LONG)(Offset>>32); + if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff && + GetLastError()!=NO_ERROR) + return false; +#else + LastWrite=false; +#ifdef FILE_USE_OPEN + if (lseek(hFile,(off_t)Offset,Method)==-1) + return false; +#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS) + if (fseeko(hFile,Offset,Method)!=0) + return false; +#else + if (fseek(hFile,(long)Offset,Method)!=0) + return false; +#endif +#endif + return true; +} + + +int64 File::Tell() +{ + if (hFile==FILE_BAD_HANDLE) + if (AllowExceptions) + ErrHandler.SeekError(FileName); + else + return -1; +#ifdef _WIN_ALL + LONG HighDist=0; + uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT); + if (LowDist==0xffffffff && GetLastError()!=NO_ERROR) + if (AllowExceptions) + ErrHandler.SeekError(FileName); + else + return -1; + return INT32TO64(HighDist,LowDist); +#else +#ifdef FILE_USE_OPEN + return lseek(hFile,0,SEEK_CUR); +#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) + return ftello(hFile); +#else + return ftell(hFile); +#endif +#endif +} + + +void File::Prealloc(int64 Size) +{ +#ifdef _WIN_ALL + if (RawSeek(Size,SEEK_SET)) + { + Truncate(); + Seek(0,SEEK_SET); + } +#endif + +#if defined(_UNIX) && defined(USE_FALLOCATE) + // fallocate is rather new call. Only latest kernels support it. + // So we are not using it by default yet. + int fd = GetFD(); + if (fd >= 0) + fallocate(fd, 0, 0, Size); +#endif +} + + +byte File::GetByte() +{ + byte Byte=0; + Read(&Byte,1); + return Byte; +} + + +void File::PutByte(byte Byte) +{ + Write(&Byte,1); +} + + +bool File::Truncate() +{ +#ifdef _WIN_ALL + return SetEndOfFile(hFile)==TRUE; +#else + return ftruncate(GetFD(),(off_t)Tell())==0; +#endif +} + + +void File::Flush() +{ +#ifdef _WIN_ALL + FlushFileBuffers(hFile); +#else +#ifndef FILE_USE_OPEN + fflush(hFile); +#endif + fsync(GetFD()); +#endif +} + + +void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#ifdef _WIN_ALL + // Workaround for OpenIndiana NAS time bug. If we cannot create a file + // in write only mode, we need to flush the write buffer before calling + // SetFileTime or file time will not be changed. + if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0) + FlushFileBuffers(hFile); + + bool sm=ftm!=NULL && ftm->IsSet(); + bool sc=ftc!=NULL && ftc->IsSet(); + bool sa=fta!=NULL && fta->IsSet(); + FILETIME fm,fc,fa; + if (sm) + ftm->GetWinFT(&fm); + if (sc) + ftc->GetWinFT(&fc); + if (sa) + fta->GetWinFT(&fa); + SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL); +#endif +} + + +void File::SetCloseFileTime(RarTime *ftm,RarTime *fta) +{ +// Android APP_PLATFORM := android-14 does not support futimens and futimes. +// Newer platforms support futimens, but fail on Android 4.2. +// We have to use utime for Android. +// Also we noticed futimens fail to set timestamps on NTFS partition +// mounted to virtual Linux x86 machine, but utimensat worked correctly. +// So we set timestamps for already closed files in Unix. +#ifdef _UNIX + SetCloseFileTimeByName(FileName,ftm,fta); +#endif +} + + +void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) +{ +#ifdef _UNIX + bool setm=ftm!=NULL && ftm->IsSet(); + bool seta=fta!=NULL && fta->IsSet(); + if (setm || seta) + { + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + +#ifdef UNIX_TIME_NS + timespec times[2]; + times[0].tv_sec=seta ? fta->GetUnix() : 0; + times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW; + times[1].tv_sec=setm ? ftm->GetUnix() : 0; + times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW; + utimensat(AT_FDCWD,NameA,times,0); +#else + utimbuf ut; + if (setm) + ut.modtime=ftm->GetUnix(); + else + ut.modtime=fta->GetUnix(); // Need to set something, cannot left it 0. + if (seta) + ut.actime=fta->GetUnix(); + else + ut.actime=ut.modtime; // Need to set something, cannot left it 0. + utime(NameA,&ut); +#endif + } +#endif +} + + +void File::GetOpenFileTime(RarTime *ft) +{ +#ifdef _WIN_ALL + FILETIME FileTime; + GetFileTime(hFile,NULL,NULL,&FileTime); + ft->SetWinFT(&FileTime); +#endif +#if defined(_UNIX) || defined(_EMX) + struct stat st; + fstat(GetFD(),&st); + ft->SetUnix(st.st_mtime); +#endif +} + + +int64 File::FileLength() +{ + SaveFilePos SavePos(*this); + Seek(0,SEEK_END); + return Tell(); +} + + +bool File::IsDevice() +{ + if (hFile==FILE_BAD_HANDLE) + return false; +#ifdef _WIN_ALL + uint Type=GetFileType(hFile); + return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE; +#else + return isatty(GetFD()); +#endif +} + + +#ifndef SFX_MODULE +int64 File::Copy(File &Dest,int64 Length) +{ + Array Buffer(0x40000); + int64 CopySize=0; + bool CopyAll=(Length==INT64NDF); + + while (CopyAll || Length>0) + { + Wait(); + size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size(); + char *Buf=&Buffer[0]; + int ReadSize=Read(Buf,SizeToRead); + if (ReadSize==0) + break; + size_t WriteSize=ReadSize; +#ifdef _WIN_ALL + // For FAT32 USB flash drives in Windows if first write is 4 KB or more, + // write caching is disabled and "write through" is enabled, resulting + // in bad performance, especially for many small files. It happens when + // we create SFX archive on USB drive, because SFX module is written first. + // So we split the first write to small 1 KB followed by rest of data. + if (CopySize==0 && WriteSize>=4096) + { + const size_t FirstWrite=1024; + Dest.Write(Buf,FirstWrite); + Buf+=FirstWrite; + WriteSize-=FirstWrite; + } +#endif + Dest.Write(Buf,WriteSize); + CopySize+=ReadSize; + if (!CopyAll) + Length-=ReadSize; + } + return CopySize; +} +#endif diff --git a/reference/unrar/file.hpp b/reference/unrar/file.hpp new file mode 100644 index 00000000..8a7631e3 --- /dev/null +++ b/reference/unrar/file.hpp @@ -0,0 +1,126 @@ +#ifndef _RAR_FILE_ +#define _RAR_FILE_ + +#define FILE_USE_OPEN + +#ifdef _WIN_ALL + typedef HANDLE FileHandle; + #define FILE_BAD_HANDLE INVALID_HANDLE_VALUE +#elif defined(FILE_USE_OPEN) + typedef off_t FileHandle; + #define FILE_BAD_HANDLE -1 +#else + typedef FILE* FileHandle; + #define FILE_BAD_HANDLE NULL +#endif + +class RAROptions; + +enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD}; + +enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR}; + +enum FILE_MODE_FLAGS { + // Request read only access to file. Default for Open. + FMF_READ=0, + + // Request both read and write access to file. Default for Create. + FMF_UPDATE=1, + + // Request write only access to file. + FMF_WRITE=2, + + // Open files which are already opened for write by other programs. + FMF_OPENSHARED=4, + + // Open files only if no other program is opened it even in shared mode. + FMF_OPENEXCLUSIVE=8, + + // Provide read access to created file for other programs. + FMF_SHAREREAD=16, + + // Use standard NTFS names without trailing dots and spaces. + FMF_STANDARDNAMES=32, + + // Mode flags are not defined yet. + FMF_UNDEFINED=256 +}; + + +class File +{ + private: + FileHandle hFile; + bool LastWrite; + FILE_HANDLETYPE HandleType; + bool SkipClose; + bool IgnoreReadErrors; + bool NewFile; + bool AllowDelete; + bool AllowExceptions; +#ifdef _WIN_ALL + bool NoSequentialRead; + uint CreateMode; +#endif + protected: + bool OpenShared; // Set by 'Archive' class. + public: + wchar FileName[NM]; + + FILE_ERRORTYPE ErrorType; + public: + File(); + virtual ~File(); + void operator = (File &SrcFile); + virtual bool Open(const wchar *Name,uint Mode=FMF_READ); + void TOpen(const wchar *Name); + bool WOpen(const wchar *Name); + bool Create(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + void TCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + bool WCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD); + bool Close(); + bool Delete(); + bool Rename(const wchar *NewName); + bool Write(const void *Data,size_t Size); + virtual int Read(void *Data,size_t Size); + int DirectRead(void *Data,size_t Size); + virtual void Seek(int64 Offset,int Method); + bool RawSeek(int64 Offset,int Method); + virtual int64 Tell(); + void Prealloc(int64 Size); + byte GetByte(); + void PutByte(byte Byte); + bool Truncate(); + void Flush(); + 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); + bool IsOpened() {return hFile!=FILE_BAD_HANDLE;}; + int64 FileLength(); + void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;} + FILE_HANDLETYPE GetHandleType() {return HandleType;} + bool IsDevice(); + static bool RemoveCreated(); + FileHandle GetHandle() {return hFile;} + void SetHandle(FileHandle Handle) {Close();hFile=Handle;} + void SetIgnoreReadErrors(bool Mode) {IgnoreReadErrors=Mode;} + int64 Copy(File &Dest,int64 Length=INT64NDF); + void SetAllowDelete(bool Allow) {AllowDelete=Allow;} + void SetExceptions(bool Allow) {AllowExceptions=Allow;} +#ifdef _WIN_ALL + void RemoveSequentialFlag() {NoSequentialRead=true;} +#endif +#ifdef _UNIX + int GetFD() + { +#ifdef FILE_USE_OPEN + return hFile; +#else + return fileno(hFile); +#endif + } +#endif +}; + +#endif diff --git a/reference/unrar/filefn.cpp b/reference/unrar/filefn.cpp new file mode 100644 index 00000000..4eb7b9b2 --- /dev/null +++ b/reference/unrar/filefn.cpp @@ -0,0 +1,510 @@ +#include "rar.hpp" + +MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr) +{ +#ifdef _WIN_ALL + // Windows automatically removes dots and spaces in the end of directory + // name. So we detect such names and process them with \\?\ prefix. + wchar *LastChar=PointToLastChar(Name); + bool Special=*LastChar=='.' || *LastChar==' '; + BOOL RetCode=Special ? FALSE : CreateDirectory(Name,NULL); + if (RetCode==0 && !FileExist(Name)) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + RetCode=CreateDirectory(LongName,NULL); + } + if (RetCode!=0) // Non-zero return code means success for CreateDirectory. + { + if (SetAttr) + SetFileAttr(Name,Attr); + return MKDIR_SUCCESS; + } + int ErrCode=GetLastError(); + if (ErrCode==ERROR_FILE_NOT_FOUND || ErrCode==ERROR_PATH_NOT_FOUND) + return MKDIR_BADPATH; + return MKDIR_ERROR; +#elif defined(_UNIX) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + mode_t uattr=SetAttr ? (mode_t)Attr:0777; + int ErrCode=mkdir(NameA,uattr); + if (ErrCode==-1) + return errno==ENOENT ? MKDIR_BADPATH:MKDIR_ERROR; + return MKDIR_SUCCESS; +#else + return MKDIR_ERROR; +#endif +} + + +bool CreatePath(const wchar *Path,bool SkipLastName) +{ + if (Path==NULL || *Path==0) + return false; + +#if defined(_WIN_ALL) || defined(_EMX) + uint DirAttr=0; +#else + uint DirAttr=0777; +#endif + + bool Success=true; + + for (const wchar *s=Path;*s!=0;s++) + { + wchar DirName[NM]; + if (s-Path>=ASIZE(DirName)) + break; + + // Process all kinds of path separators, so user can enter Unix style + // path in Windows or Windows in Unix. s>Path check avoids attempting + // creating an empty directory for paths starting from path separator. + if (IsPathDiv(*s) && s>Path) + { +#ifdef _WIN_ALL + // We must not attempt to create "D:" directory, because first + // CreateDirectory will fail, so we'll use \\?\D:, which forces Wine + // to create "D:" directory. + if (s==Path+2 && Path[1]==':') + continue; +#endif + wcsncpy(DirName,Path,s-Path); + DirName[s-Path]=0; + + Success=MakeDir(DirName,true,DirAttr)==MKDIR_SUCCESS; + if (Success) + { + mprintf(St(MCreatDir),DirName); + mprintf(L" %s",St(MOk)); + } + } + } + if (!SkipLastName && !IsPathDiv(*PointToLastChar(Path))) + Success=MakeDir(Path,true,DirAttr)==MKDIR_SUCCESS; + return Success; +} + + +void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta) +{ +#if defined(_WIN_ALL) + bool sm=ftm!=NULL && ftm->IsSet(); + bool sc=ftc!=NULL && ftc->IsSet(); + bool sa=fta!=NULL && fta->IsSet(); + + uint DirAttr=GetFileAttr(Name); + bool ResetAttr=(DirAttr!=0xffffffff && (DirAttr & FILE_ATTRIBUTE_READONLY)!=0); + if (ResetAttr) + SetFileAttr(Name,0); + + HANDLE hFile=CreateFile(Name,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); + if (hFile==INVALID_HANDLE_VALUE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); + } + + if (hFile==INVALID_HANDLE_VALUE) + return; + FILETIME fm,fc,fa; + if (sm) + ftm->GetWinFT(&fm); + if (sc) + ftc->GetWinFT(&fc); + if (sa) + fta->GetWinFT(&fa); + SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL); + CloseHandle(hFile); + if (ResetAttr) + SetFileAttr(Name,DirAttr); +#endif +#if defined(_UNIX) || defined(_EMX) + File::SetCloseFileTimeByName(Name,ftm,fta); +#endif +} + + +bool IsRemovable(const wchar *Name) +{ +#if defined(_WIN_ALL) + wchar Root[NM]; + GetPathRoot(Name,Root,ASIZE(Root)); + int Type=GetDriveType(*Root!=0 ? Root:NULL); + return Type==DRIVE_REMOVABLE || Type==DRIVE_CDROM; +#else + return false; +#endif +} + + +#ifndef SFX_MODULE +int64 GetFreeDisk(const wchar *Name) +{ +#ifdef _WIN_ALL + wchar Root[NM]; + GetFilePath(Name,Root,ASIZE(Root)); + + ULARGE_INTEGER uiTotalSize,uiTotalFree,uiUserFree; + uiUserFree.u.LowPart=uiUserFree.u.HighPart=0; + if (GetDiskFreeSpaceEx(*Root!=0 ? Root:NULL,&uiUserFree,&uiTotalSize,&uiTotalFree) && + uiUserFree.u.HighPart<=uiTotalFree.u.HighPart) + return INT32TO64(uiUserFree.u.HighPart,uiUserFree.u.LowPart); + return 0; +#elif defined(_UNIX) + wchar Root[NM]; + GetFilePath(Name,Root,ASIZE(Root)); + char RootA[NM]; + WideToChar(Root,RootA,ASIZE(RootA)); + struct statvfs sfs; + if (statvfs(*RootA!=0 ? RootA:".",&sfs)!=0) + return 0; + int64 FreeSize=sfs.f_bsize; + FreeSize=FreeSize*sfs.f_bavail; + return FreeSize; +#else + return 0; +#endif +} +#endif + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) +// Return 'true' for FAT and FAT32, so we can adjust the maximum supported +// file size to 4 GB for these file systems. +bool IsFAT(const wchar *Name) +{ + wchar Root[NM]; + GetPathRoot(Name,Root,ASIZE(Root)); + wchar FileSystem[MAX_PATH+1]; + if (GetVolumeInformation(Root,NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem))) + return wcscmp(FileSystem,L"FAT")==0 || wcscmp(FileSystem,L"FAT32")==0; + return false; +} +#endif + + +bool FileExist(const wchar *Name) +{ +#ifdef _WIN_ALL + return GetFileAttr(Name)!=0xffffffff; +#elif defined(ENABLE_ACCESS) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + return access(NameA,0)==0; +#else + FindData FD; + return FindFile::FastFind(Name,&FD); +#endif +} + + +bool WildFileExist(const wchar *Name) +{ + if (IsWildcard(Name)) + { + FindFile Find; + Find.SetMask(Name); + FindData fd; + return Find.Next(&fd); + } + return FileExist(Name); +} + + +bool IsDir(uint Attr) +{ +#ifdef _WIN_ALL + return Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_DIRECTORY)!=0; +#endif +#if defined(_UNIX) + return (Attr & 0xF000)==0x4000; +#endif +} + + +bool IsUnreadable(uint Attr) +{ +#if defined(_UNIX) && defined(S_ISFIFO) && defined(S_ISSOCK) && defined(S_ISCHR) + return S_ISFIFO(Attr) || S_ISSOCK(Attr) || S_ISCHR(Attr); +#endif + return false; +} + + +bool IsLink(uint Attr) +{ +#ifdef _UNIX + return (Attr & 0xF000)==0xA000; +#elif defined(_WIN_ALL) + return (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0; +#else + return false; +#endif +} + + + + + + +bool IsDeleteAllowed(uint FileAttr) +{ +#ifdef _WIN_ALL + return (FileAttr & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))==0; +#else + return (FileAttr & (S_IRUSR|S_IWUSR))==(S_IRUSR|S_IWUSR); +#endif +} + + +void PrepareToDelete(const wchar *Name) +{ +#if defined(_WIN_ALL) || defined(_EMX) + SetFileAttr(Name,0); +#endif +#ifdef _UNIX + if (Name!=NULL) + { + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + chmod(NameA,S_IRUSR|S_IWUSR|S_IXUSR); + } +#endif +} + + +uint GetFileAttr(const wchar *Name) +{ +#ifdef _WIN_ALL + DWORD Attr=GetFileAttributes(Name); + if (Attr==0xffffffff) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Attr=GetFileAttributes(LongName); + } + return Attr; +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + struct stat st; + if (stat(NameA,&st)!=0) + return 0; + return st.st_mode; +#endif +} + + +bool SetFileAttr(const wchar *Name,uint Attr) +{ +#ifdef _WIN_ALL + bool Success=SetFileAttributes(Name,Attr)!=0; + if (!Success) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Success=SetFileAttributes(LongName,Attr)!=0; + } + return Success; +#elif defined(_UNIX) + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + return chmod(NameA,(mode_t)Attr)==0; +#else + return false; +#endif +} + + +#if 0 +wchar *MkTemp(wchar *Name,size_t MaxSize) +{ + size_t Length=wcslen(Name); + + RarTime CurTime; + CurTime.SetCurrentTime(); + + // We cannot use CurTime.GetWin() as is, because its lowest bits can + // have low informational value, like being a zero or few fixed numbers. + uint Random=(uint)(CurTime.GetWin()/100000); + + // Using PID we guarantee that different RAR copies use different temp names + // even if started in exactly the same time. + uint PID=0; +#ifdef _WIN_ALL + PID=(uint)GetCurrentProcessId(); +#elif defined(_UNIX) + PID=(uint)getpid(); +#endif + + for (uint Attempt=0;;Attempt++) + { + uint Ext=Random%50000+Attempt; + wchar RndText[50]; + swprintf(RndText,ASIZE(RndText),L"%u.%03u",PID,Ext); + if (Length+wcslen(RndText)>=MaxSize || Attempt==1000) + return NULL; + wcscpy(Name+Length,RndText); + if (!FileExist(Name)) + break; + } + return Name; +} +#endif + + +#if !defined(SFX_MODULE) +void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size,uint Flags) +{ + SaveFilePos SavePos(*SrcFile); +#ifndef SILENT + int64 FileLength=Size==INT64NDF ? SrcFile->FileLength() : Size; +#endif + + if ((Flags & (CALCFSUM_SHOWTEXT|CALCFSUM_SHOWPERCENT))!=0) + uiMsg(UIEVENT_FILESUMSTART); + + if ((Flags & CALCFSUM_CURPOS)==0) + SrcFile->Seek(0,SEEK_SET); + + const size_t BufSize=0x100000; + Array Data(BufSize); + + + DataHash HashCRC,HashBlake2; + HashCRC.Init(HASH_CRC32,Threads); + HashBlake2.Init(HASH_BLAKE2,Threads); + + int64 BlockCount=0; + int64 TotalRead=0; + while (true) + { + size_t SizeToRead; + if (Size==INT64NDF) // If we process the entire file. + SizeToRead=BufSize; // Then always attempt to read the entire buffer. + else + SizeToRead=(size_t)Min((int64)BufSize,Size); + int ReadSize=SrcFile->Read(&Data[0],SizeToRead); + if (ReadSize==0) + break; + TotalRead+=ReadSize; + + if ((++BlockCount & 0xf)==0) + { +#ifndef SILENT + if ((Flags & CALCFSUM_SHOWPROGRESS)!=0) + uiExtractProgress(TotalRead,FileLength,TotalRead,FileLength); + else + { + if ((Flags & CALCFSUM_SHOWPERCENT)!=0) + uiMsg(UIEVENT_FILESUMPROGRESS,ToPercent(TotalRead,FileLength)); + } +#endif + Wait(); + } + + if (CRC32!=NULL) + HashCRC.Update(&Data[0],ReadSize); + if (Blake2!=NULL) + HashBlake2.Update(&Data[0],ReadSize); + + if (Size!=INT64NDF) + Size-=ReadSize; + } + if ((Flags & CALCFSUM_SHOWPERCENT)!=0) + uiMsg(UIEVENT_FILESUMEND); + + if (CRC32!=NULL) + *CRC32=HashCRC.GetCRC32(); + if (Blake2!=NULL) + { + HashValue Result; + HashBlake2.Result(&Result); + memcpy(Blake2,Result.Digest,sizeof(Result.Digest)); + } +} +#endif + + +bool RenameFile(const wchar *SrcName,const wchar *DestName) +{ +#ifdef _WIN_ALL + bool Success=MoveFile(SrcName,DestName)!=0; + if (!Success) + { + wchar LongName1[NM],LongName2[NM]; + if (GetWinLongPath(SrcName,LongName1,ASIZE(LongName1)) && + GetWinLongPath(DestName,LongName2,ASIZE(LongName2))) + Success=MoveFile(LongName1,LongName2)!=0; + } + return Success; +#else + char SrcNameA[NM],DestNameA[NM]; + WideToChar(SrcName,SrcNameA,ASIZE(SrcNameA)); + WideToChar(DestName,DestNameA,ASIZE(DestNameA)); + bool Success=rename(SrcNameA,DestNameA)==0; + return Success; +#endif +} + + +bool DelFile(const wchar *Name) +{ +#ifdef _WIN_ALL + bool Success=DeleteFile(Name)!=0; + if (!Success) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + Success=DeleteFile(LongName)!=0; + } + return Success; +#else + char NameA[NM]; + WideToChar(Name,NameA,ASIZE(NameA)); + bool Success=remove(NameA)==0; + return Success; +#endif +} + + + + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool SetFileCompression(const wchar *Name,bool State) +{ + HANDLE hFile=CreateFile(Name,FILE_READ_DATA|FILE_WRITE_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); + if (hFile==INVALID_HANDLE_VALUE) + { + wchar LongName[NM]; + if (GetWinLongPath(Name,LongName,ASIZE(LongName))) + hFile=CreateFile(LongName,FILE_READ_DATA|FILE_WRITE_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL); + } + if (hFile==INVALID_HANDLE_VALUE) + return false; + SHORT NewState=State ? COMPRESSION_FORMAT_DEFAULT:COMPRESSION_FORMAT_NONE; + DWORD Result; + int RetCode=DeviceIoControl(hFile,FSCTL_SET_COMPRESSION,&NewState, + sizeof(NewState),NULL,0,&Result,NULL); + CloseHandle(hFile); + return RetCode!=0; +} +#endif + + + + + + + + + + diff --git a/reference/unrar/filefn.hpp b/reference/unrar/filefn.hpp new file mode 100644 index 00000000..1bda9b58 --- /dev/null +++ b/reference/unrar/filefn.hpp @@ -0,0 +1,50 @@ +#ifndef _RAR_FILEFN_ +#define _RAR_FILEFN_ + +enum MKDIR_CODE {MKDIR_SUCCESS,MKDIR_ERROR,MKDIR_BADPATH}; + +MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr); +bool CreatePath(const wchar *Path,bool SkipLastName); +void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta); +bool IsRemovable(const wchar *Name); + +#ifndef SFX_MODULE +int64 GetFreeDisk(const wchar *Name); +#endif + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) +bool IsFAT(const wchar *Root); +#endif + +bool FileExist(const wchar *Name); +bool WildFileExist(const wchar *Name); +bool IsDir(uint Attr); +bool IsUnreadable(uint Attr); +bool IsLink(uint Attr); +void SetSFXMode(const wchar *FileName); +void EraseDiskContents(const wchar *FileName); +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}; + +void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size=INT64NDF,uint Flags=0); + +bool RenameFile(const wchar *SrcName,const wchar *DestName); +bool DelFile(const wchar *Name); +bool DelDir(const wchar *Name); + +#if defined(_WIN_ALL) && !defined(SFX_MODULE) +bool SetFileCompression(const wchar *Name,bool State); +#endif + + + + + +#endif diff --git a/reference/unrar/filestr.cpp b/reference/unrar/filestr.cpp new file mode 100644 index 00000000..4a53132f --- /dev/null +++ b/reference/unrar/filestr.cpp @@ -0,0 +1,162 @@ +#include "rar.hpp" + +bool ReadTextFile( + const wchar *Name, + StringList *List, + bool Config, + bool AbortOnError, + RAR_CHARSET SrcCharset, + bool Unquote, + bool SkipComments, + bool ExpandEnvStr) +{ + wchar FileName[NM]; + *FileName=0; + + if (Name!=NULL) + if (Config) + GetConfigName(Name,FileName,ASIZE(FileName),true,false); + else + wcsncpyz(FileName,Name,ASIZE(FileName)); + + File SrcFile; + if (*FileName!=0) + { + bool OpenCode=AbortOnError ? SrcFile.WOpen(FileName):SrcFile.Open(FileName,0); + + if (!OpenCode) + { + if (AbortOnError) + ErrHandler.Exit(RARX_OPEN); + return false; + } + } + else + SrcFile.SetHandleType(FILE_HANDLESTD); + + uint DataSize=0,ReadSize; + const int ReadBlock=4096; + + Array Data(ReadBlock); + while ((ReadSize=SrcFile.Read(&Data[DataSize],ReadBlock))!=0) + { + DataSize+=ReadSize; + Data.Add(ReadSize); // Always have ReadBlock available for next data. + } + // Set to really read size, so we can zero terminate it correctly. + Data.Alloc(DataSize); + + int LowEndian=DataSize>=2 && Data[0]==255 && Data[1]==254 ? 1:0; + int BigEndian=DataSize>=2 && Data[0]==254 && Data[1]==255 ? 1:0; + bool Utf8=DataSize>=3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf; + + if (SrcCharset==RCH_DEFAULT) + { + if (LowEndian || BigEndian) + for (size_t I=2;I DataW; + + if (SrcCharset==RCH_DEFAULT || SrcCharset==RCH_OEM || SrcCharset==RCH_ANSI) + { + Data.Push(0); // Zero terminate. +#if defined(_WIN_ALL) + if (SrcCharset==RCH_OEM) + OemToCharA((char *)&Data[0],(char *)&Data[0]); +#endif + DataW.Alloc(Data.Size()); + CharToWide((char *)&Data[0],&DataW[0],DataW.Size()); + } + + if (SrcCharset==RCH_UNICODE) + { + size_t Start=2; // Skip byte order mark. + if (!LowEndian && !BigEndian) // No byte order mask. + { + Start=0; + LowEndian=1; + } + + DataW.Alloc(Data.Size()/2+1); + size_t End=Data.Size() & ~1; // We need even bytes number for UTF-16. + for (size_t I=Start;I=CurStr;SpacePtr--) + { + if (*SpacePtr!=' ' && *SpacePtr!='\t') + break; + *SpacePtr=0; + } + + if (Unquote && *CurStr=='\"') + { + size_t Length=wcslen(CurStr); + if (CurStr[Length-1]=='\"') + { + CurStr[Length-1]=0; + CurStr++; + } + } + + bool Expanded=false; +#if defined(_WIN_ALL) + if (ExpandEnvStr && *CurStr=='%') // Expand environment variables in Windows. + { + wchar ExpName[NM]; + *ExpName=0; + DWORD Result=ExpandEnvironmentStrings(CurStr,ExpName,ASIZE(ExpName)); + Expanded=Result!=0 && ResultAddString(ExpName); + } +#endif + if (!Expanded && *CurStr!=0) + List->AddString(CurStr); + + if (Done) + break; + CurStr=NextStr+1; + while (*CurStr=='\r' || *CurStr=='\n') + CurStr++; + } + return true; +} diff --git a/reference/unrar/filestr.hpp b/reference/unrar/filestr.hpp new file mode 100644 index 00000000..8dfdf64b --- /dev/null +++ b/reference/unrar/filestr.hpp @@ -0,0 +1,15 @@ +#ifndef _RAR_FILESTR_ +#define _RAR_FILESTR_ + +bool ReadTextFile( + const wchar *Name, + StringList *List, + bool Config, + bool AbortOnError=false, + RAR_CHARSET SrcCharset=RCH_DEFAULT, + bool Unquote=false, + bool SkipComments=false, + bool ExpandEnvStr=false +); + +#endif diff --git a/reference/unrar/find.cpp b/reference/unrar/find.cpp new file mode 100644 index 00000000..812af6b3 --- /dev/null +++ b/reference/unrar/find.cpp @@ -0,0 +1,218 @@ +#include "rar.hpp" + +FindFile::FindFile() +{ + *FindMask=0; + FirstCall=true; +#ifdef _WIN_ALL + hFind=INVALID_HANDLE_VALUE; +#else + dirp=NULL; +#endif +} + + +FindFile::~FindFile() +{ +#ifdef _WIN_ALL + if (hFind!=INVALID_HANDLE_VALUE) + FindClose(hFind); +#else + if (dirp!=NULL) + closedir(dirp); +#endif +} + + +void FindFile::SetMask(const wchar *Mask) +{ + wcscpy(FindMask,Mask); + FirstCall=true; +} + + +bool FindFile::Next(FindData *fd,bool GetSymLink) +{ + fd->Error=false; + if (*FindMask==0) + 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; +#else + if (FirstCall) + { + wchar DirName[NM]; + wcsncpyz(DirName,FindMask,ASIZE(DirName)); + RemoveNameFromPath(DirName); + if (*DirName==0) + wcscpy(DirName,L"."); + char DirNameA[NM]; + WideToChar(DirName,DirNameA,ASIZE(DirNameA)); + if ((dirp=opendir(DirNameA))==NULL) + { + fd->Error=(errno!=ENOENT); + return false; + } + } + while (1) + { + struct dirent *ent=readdir(dirp); + if (ent==NULL) + return false; + if (strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0) + continue; + wchar Name[NM]; + if (!CharToWide(ent->d_name,Name,ASIZE(Name))) + uiMsg(UIERROR_INVALIDNAME,UINULL,Name); + + if (CmpName(FindMask,Name,MATCH_NAMES)) + { + wchar FullName[NM]; + wcscpy(FullName,FindMask); + *PointToName(FullName)=0; + if (wcslen(FullName)+wcslen(Name)>=ASIZE(FullName)-1) + { + uiMsg(UIERROR_PATHTOOLONG,FullName,L"",Name); + return false; + } + wcscat(FullName,Name); + if (!FastFind(FullName,fd,GetSymLink)) + { + ErrHandler.OpenErrorMsg(FullName); + continue; + } + wcscpy(fd->Name,FullName); + break; + } + } +#endif + fd->Flags=0; + fd->IsDir=IsDir(fd->FileAttr); + fd->IsLink=IsLink(fd->FileAttr); + + FirstCall=false; + wchar *NameOnly=PointToName(fd->Name); + if (wcscmp(NameOnly,L".")==0 || wcscmp(NameOnly,L"..")==0) + return Next(fd); + return true; +} + + +bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink) +{ + fd->Error=false; +#ifndef _UNIX + if (IsWildcard(FindMask)) + return false; +#endif +#ifdef _WIN_ALL + HANDLE hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd); + if (hFind==INVALID_HANDLE_VALUE) + return false; + FindClose(hFind); +#else + char FindMaskA[NM]; + WideToChar(FindMask,FindMaskA,ASIZE(FindMaskA)); + + struct stat st; + if (GetSymLink) + { +#ifdef SAVE_LINKS + if (lstat(FindMaskA,&st)!=0) +#else + if (stat(FindMaskA,&st)!=0) +#endif + { + fd->Error=(errno!=ENOENT); + return false; + } + } + else + if (stat(FindMaskA,&st)!=0) + { + fd->Error=(errno!=ENOENT); + return false; + } + fd->FileAttr=st.st_mode; + fd->Size=st.st_size; + +#ifdef UNIX_TIME_NS + 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); +#else + fd->mtime.SetUnix(st.st_mtime); + fd->atime.SetUnix(st.st_atime); + fd->ctime.SetUnix(st.st_ctime); +#endif + + wcsncpyz(fd->Name,FindMask,ASIZE(fd->Name)); +#endif + fd->Flags=0; + fd->IsDir=IsDir(fd->FileAttr); + fd->IsLink=IsLink(fd->FileAttr); + + return true; +} + + +#ifdef _WIN_ALL +HANDLE FindFile::Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd) +{ + WIN32_FIND_DATA FindData; + if (hFind==INVALID_HANDLE_VALUE) + { + hFind=FindFirstFile(Mask,&FindData); + if (hFind==INVALID_HANDLE_VALUE) + { + wchar LongMask[NM]; + if (GetWinLongPath(Mask,LongMask,ASIZE(LongMask))) + hFind=FindFirstFile(LongMask,&FindData); + } + if (hFind==INVALID_HANDLE_VALUE) + { + int SysErr=GetLastError(); + // We must not issue an error for "file not found" and "path not found", + // because it is normal to not find anything for wildcard mask when + // archiving. Also searching for non-existent file is normal in some + // other modules, like WinRAR scanning for winrar_theme_description.txt + // to check if any themes are available. + fd->Error=SysErr!=ERROR_FILE_NOT_FOUND && + SysErr!=ERROR_PATH_NOT_FOUND && + SysErr!=ERROR_NO_MORE_FILES; + } + } + else + if (!FindNextFile(hFind,&FindData)) + { + hFind=INVALID_HANDLE_VALUE; + fd->Error=GetLastError()!=ERROR_NO_MORE_FILES; + } + + if (hFind!=INVALID_HANDLE_VALUE) + { + wcsncpyz(fd->Name,Mask,ASIZE(fd->Name)); + SetName(fd->Name,FindData.cFileName,ASIZE(fd->Name)); + 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; +} +#endif + diff --git a/reference/unrar/find.hpp b/reference/unrar/find.hpp new file mode 100644 index 00000000..250637f8 --- /dev/null +++ b/reference/unrar/find.hpp @@ -0,0 +1,49 @@ +#ifndef _RAR_FINDDATA_ +#define _RAR_FINDDATA_ + +enum FINDDATA_FLAGS { + FDDF_SECONDDIR=1 // Second encounter of same directory in SCAN_GETDIRSTWICE ScanTree mode. +}; + +struct FindData +{ + wchar Name[NM]; + uint64 Size; + uint FileAttr; + bool IsDir; + bool IsLink; + RarTime mtime; + RarTime ctime; + RarTime atime; +#ifdef _WIN_ALL + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; +#endif + uint Flags; + bool Error; +}; + +class FindFile +{ + private: +#ifdef _WIN_ALL + static HANDLE Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd); +#endif + + wchar FindMask[NM]; + bool FirstCall; +#ifdef _WIN_ALL + HANDLE hFind; +#else + DIR *dirp; +#endif + public: + FindFile(); + ~FindFile(); + void SetMask(const wchar *Mask); + bool Next(FindData *fd,bool GetSymLink=false); + static bool FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink=false); +}; + +#endif diff --git a/reference/unrar/getbits.cpp b/reference/unrar/getbits.cpp new file mode 100644 index 00000000..e4db2695 --- /dev/null +++ b/reference/unrar/getbits.cpp @@ -0,0 +1,52 @@ +#include "rar.hpp" + +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 + // 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; + InBuf=new byte[BufSize]; + + // Ensure that we get predictable results when accessing bytes in area + // not filled with read data. + memset(InBuf,0,BufSize); + } + else + InBuf=NULL; +} + + +BitInput::~BitInput() +{ + if (!ExternalBuffer) + delete[] InBuf; +} + + +void BitInput::faddbits(uint Bits) +{ + // Function wrapped version of inline addbits to save code size. + addbits(Bits); +} + + +uint BitInput::fgetbits() +{ + // Function wrapped version of inline getbits to save code size. + return getbits(); +} + + +void BitInput::SetExternalBuffer(byte *Buf) +{ + if (InBuf!=NULL && !ExternalBuffer) + delete[] InBuf; + InBuf=Buf; + ExternalBuffer=true; +} + diff --git a/reference/unrar/getbits.hpp b/reference/unrar/getbits.hpp new file mode 100644 index 00000000..2e151da9 --- /dev/null +++ b/reference/unrar/getbits.hpp @@ -0,0 +1,68 @@ +#ifndef _RAR_GETBITS_ +#define _RAR_GETBITS_ + +class BitInput +{ + public: + enum BufferSize {MAX_SIZE=0x8000}; // Size of input buffer. + + int InAddr; // Curent byte position in the buffer. + int InBit; // Current bit position in the current byte. + + bool ExternalBuffer; + public: + BitInput(bool AllocBuffer); + ~BitInput(); + + byte *InBuf; // Dynamically allocated input buffer. + + void InitBitInput() + { + InAddr=InBit=0; + } + + // Move forward by 'Bits' bits. + void addbits(uint Bits) + { + Bits+=InBit; + 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() + { + uint BitField=(uint)InBuf[InAddr] << 16; + BitField|=(uint)InBuf[InAddr+1] << 8; + BitField|=(uint)InBuf[InAddr+2]; + BitField >>= (8-InBit); + 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() + { + uint BitField=(uint)InBuf[InAddr] << 24; + BitField|=(uint)InBuf[InAddr+1] << 16; + BitField|=(uint)InBuf[InAddr+2] << 8; + BitField|=(uint)InBuf[InAddr+3]; + BitField <<= InBit; + BitField|=(uint)InBuf[InAddr+4] >> (8-InBit); + return BitField & 0xffffffff; + } + + void faddbits(uint Bits); + uint fgetbits(); + + // Check if buffer has enough space for IncPtr bytes. Returns 'true' + // if buffer will be overflown. + bool Overflow(uint IncPtr) + { + return InAddr+IncPtr>=MAX_SIZE; + } + + void SetExternalBuffer(byte *Buf); +}; +#endif diff --git a/reference/unrar/global.cpp b/reference/unrar/global.cpp new file mode 100644 index 00000000..3975813a --- /dev/null +++ b/reference/unrar/global.cpp @@ -0,0 +1,7 @@ +#define INCLUDEGLOBAL + +#if defined(__BORLANDC__) || defined(_MSC_VER) +#pragma hdrstop +#endif + +#include "rar.hpp" diff --git a/reference/unrar/global.hpp b/reference/unrar/global.hpp new file mode 100644 index 00000000..35c6cf91 --- /dev/null +++ b/reference/unrar/global.hpp @@ -0,0 +1,14 @@ +#ifndef _RAR_GLOBAL_ +#define _RAR_GLOBAL_ + +#ifdef INCLUDEGLOBAL + #define EXTVAR +#else + #define EXTVAR extern +#endif + +EXTVAR ErrorHandler ErrHandler; + + + +#endif diff --git a/reference/unrar/hardlinks.cpp b/reference/unrar/hardlinks.cpp new file mode 100644 index 00000000..cf0b25a9 --- /dev/null +++ b/reference/unrar/hardlinks.cpp @@ -0,0 +1,34 @@ +bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) +{ + SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. + + if (!FileExist(NameExisting)) + return false; + CreatePath(NameNew,true); + +#ifdef _WIN_ALL + bool Success=CreateHardLink(NameNew,NameExisting,NULL)!=0; + if (!Success) + { + uiMsg(UIERROR_HLINKCREATE,NameNew); + ErrHandler.SysErrMsg(); + ErrHandler.SetErrorCode(RARX_CREATE); + } + return Success; +#elif defined(_UNIX) + char NameExistingA[NM],NameNewA[NM]; + WideToChar(NameExisting,NameExistingA,ASIZE(NameExistingA)); + WideToChar(NameNew,NameNewA,ASIZE(NameNewA)); + bool Success=link(NameExistingA,NameNewA)==0; + if (!Success) + { + uiMsg(UIERROR_HLINKCREATE,NameNew); + ErrHandler.SysErrMsg(); + ErrHandler.SetErrorCode(RARX_CREATE); + } + return Success; +#else + return false; +#endif +} + diff --git a/reference/unrar/hash.cpp b/reference/unrar/hash.cpp new file mode 100644 index 00000000..42791f4f --- /dev/null +++ b/reference/unrar/hash.cpp @@ -0,0 +1,135 @@ +#include "rar.hpp" + +void HashValue::Init(HASH_TYPE Type) +{ + HashValue::Type=Type; + + // Zero length data CRC32 is 0. It is important to set it when creating + // headers with no following data like directories or symlinks. + if (Type==HASH_RAR14 || Type==HASH_CRC32) + CRC32=0; + if (Type==HASH_BLAKE2) + { + // dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f + // is BLAKE2sp hash of empty data. We init the structure to this value, + // so if we create a file or service header with no following data like + // "file copy" or "symlink", we set the checksum to proper value avoiding + // additional header type or size checks when extracting. + static byte EmptyHash[32]={ + 0xdd, 0x0e, 0x89, 0x17, 0x76, 0x93, 0x3f, 0x43, + 0xc7, 0xd0, 0x32, 0xb0, 0x8a, 0x91, 0x7e, 0x25, + 0x74, 0x1f, 0x8a, 0xa9, 0xa1, 0x2c, 0x12, 0xe1, + 0xca, 0xc8, 0x80, 0x15, 0x00, 0xf2, 0xca, 0x4f + }; + memcpy(Digest,EmptyHash,sizeof(Digest)); + } +} + + +bool HashValue::operator == (const HashValue &cmp) +{ + if (Type==HASH_NONE || cmp.Type==HASH_NONE) + return true; + if (Type==HASH_RAR14 && cmp.Type==HASH_RAR14 || + Type==HASH_CRC32 && cmp.Type==HASH_CRC32) + return CRC32==cmp.CRC32; + if (Type==HASH_BLAKE2 && cmp.Type==HASH_BLAKE2) + return memcmp(Digest,cmp.Digest,sizeof(Digest))==0; + return false; +} + + +DataHash::DataHash() +{ + blake2ctx=NULL; + HashType=HASH_NONE; +#ifdef RAR_SMP + ThPool=NULL; + MaxThreads=0; +#endif +} + + +DataHash::~DataHash() +{ +#ifdef RAR_SMP + DestroyThreadPool(ThPool); +#endif + cleandata(&CurCRC32, sizeof(CurCRC32)); + if (blake2ctx!=NULL) + { + cleandata(blake2ctx, sizeof(blake2sp_state)); + delete blake2ctx; + } +} + + +void DataHash::Init(HASH_TYPE Type,uint MaxThreads) +{ + if (blake2ctx==NULL) + blake2ctx=new blake2sp_state; + HashType=Type; + if (Type==HASH_RAR14) + CurCRC32=0; + if (Type==HASH_CRC32) + CurCRC32=0xffffffff; // Initial CRC32 value. + if (Type==HASH_BLAKE2) + blake2sp_init(blake2ctx); +#ifdef RAR_SMP + DataHash::MaxThreads=Min(MaxThreads,MaxHashThreads); +#endif +} + + +void DataHash::Update(const void *Data,size_t DataSize) +{ +#ifndef SFX_MODULE + if (HashType==HASH_RAR14) + CurCRC32=Checksum14((ushort)CurCRC32,Data,DataSize); +#endif + if (HashType==HASH_CRC32) + CurCRC32=CRC32(CurCRC32,Data,DataSize); + + if (HashType==HASH_BLAKE2) + { +#ifdef RAR_SMP + if (MaxThreads>1 && ThPool==NULL) + ThPool=CreateThreadPool(); + blake2ctx->ThPool=ThPool; + blake2ctx->MaxThreads=MaxThreads; +#endif + blake2sp_update( blake2ctx, (byte *)Data, DataSize); + } +} + + +void DataHash::Result(HashValue *Result) +{ + Result->Type=HashType; + if (HashType==HASH_RAR14) + Result->CRC32=CurCRC32; + if (HashType==HASH_CRC32) + Result->CRC32=CurCRC32^0xffffffff; + if (HashType==HASH_BLAKE2) + { + // Preserve the original context, so we can continue hashing if necessary. + blake2sp_state res=*blake2ctx; + blake2sp_final(&res,Result->Digest); + } +} + + +uint DataHash::GetCRC32() +{ + return HashType==HASH_CRC32 ? CurCRC32^0xffffffff : 0; +} + + +bool DataHash::Cmp(HashValue *CmpValue,byte *Key) +{ + HashValue Final; + Result(&Final); + if (Key!=NULL) + ConvertHashToMAC(&Final,Key); + return Final==*CmpValue; +} diff --git a/reference/unrar/hash.hpp b/reference/unrar/hash.hpp new file mode 100644 index 00000000..b7d879f6 --- /dev/null +++ b/reference/unrar/hash.hpp @@ -0,0 +1,52 @@ +#ifndef _RAR_DATAHASH_ +#define _RAR_DATAHASH_ + +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);} + + HASH_TYPE Type; + union + { + uint CRC32; + byte Digest[SHA256_DIGEST_SIZE]; + }; +}; + + +#ifdef RAR_SMP +class ThreadPool; +class DataHash; +#endif + + +class DataHash +{ + private: + HASH_TYPE HashType; + uint CurCRC32; + blake2sp_state *blake2ctx; + +#ifdef RAR_SMP + ThreadPool *ThPool; + + uint MaxThreads; + // Upper limit for maximum threads to prevent wasting threads in pool. + static const uint MaxHashThreads=8; +#endif + public: + DataHash(); + ~DataHash(); + void Init(HASH_TYPE Type,uint MaxThreads); + void Update(const void *Data,size_t DataSize); + void Result(HashValue *Result); + uint GetCRC32(); + bool Cmp(HashValue *CmpValue,byte *Key); + HASH_TYPE Type() {return HashType;} +}; + +#endif diff --git a/reference/unrar/headers.cpp b/reference/unrar/headers.cpp new file mode 100644 index 00000000..b042dc39 --- /dev/null +++ b/reference/unrar/headers.cpp @@ -0,0 +1,61 @@ +#include "rar.hpp" + +void FileHeader::Reset(size_t SubDataSize) +{ + SubData.Alloc(SubDataSize); + BaseBlock::Reset(); + FileHash.Init(HASH_NONE); + mtime.Reset(); + atime.Reset(); + ctime.Reset(); + SplitBefore=false; + SplitAfter=false; + + UnknownUnpSize=0; + + SubFlags=0; // Important for RAR 3.0 subhead. + + CryptMethod=CRYPT_NONE; + Encrypted=false; + SaltSet=false; + UsePswCheck=false; + UseHashKey=false; + Lg2Count=0; + + Solid=false; + Dir=false; + WinSize=0; + Inherited=false; + SubBlock=false; + CommentInHeader=false; + Version=false; + LargeFile=false; + + RedirType=FSREDIR_NONE; + DirTarget=false; + UnixOwnerSet=false; +} + + +FileHeader& FileHeader::operator = (FileHeader &hd) +{ + SubData.Reset(); + memcpy(this,&hd,sizeof(*this)); + SubData.CleanData(); + SubData=hd.SubData; + return *this; +} + + +void MainHeader::Reset() +{ + HighPosAV=0; + PosAV=0; + CommentInHeader=false; + PackComment=false; + Locator=false; + QOpenOffset=0; + QOpenMaxSize=0; + RROffset=0; + RRMaxSize=0; +} diff --git a/reference/unrar/headers.hpp b/reference/unrar/headers.hpp new file mode 100644 index 00000000..94864789 --- /dev/null +++ b/reference/unrar/headers.hpp @@ -0,0 +1,380 @@ +#ifndef _RAR_HEADERS_ +#define _RAR_HEADERS_ + +#define SIZEOF_MARKHEAD3 7 // Size of RAR 4.x archive mark header. +#define SIZEOF_MAINHEAD14 7 // Size of RAR 1.4 main archive header. +#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_LONGBLOCKHEAD 11 +#define SIZEOF_SUBBLOCKHEAD 14 +#define SIZEOF_COMMHEAD 13 +#define SIZEOF_PROTECTHEAD 26 +#define SIZEOF_AVHEAD 14 +#define SIZEOF_SIGNHEAD 15 +#define SIZEOF_UOHEAD 18 +#define SIZEOF_MACHEAD 22 +#define SIZEOF_EAHEAD 24 +#define SIZEOF_BEEAHEAD 24 +#define SIZEOF_STREAMHEAD 26 + +#define VER_PACK 29 +#define VER_PACK5 50 // It is stored as 0, but we subtract 50 when saving an archive. +#define VER_UNPACK 29 +#define VER_UNPACK5 50 // It is stored as 0, but we add 50 when reading an archive. + +#define MHD_VOLUME 0x0001U + +// Old style main archive comment embed into main archive header. Must not +// be used in new archives anymore. +#define MHD_COMMENT 0x0002U + +#define MHD_LOCK 0x0004U +#define MHD_SOLID 0x0008U +#define MHD_PACK_COMMENT 0x0010U +#define MHD_NEWNUMBERING 0x0010U +#define MHD_AV 0x0020U +#define MHD_PROTECT 0x0040U +#define MHD_PASSWORD 0x0080U +#define MHD_FIRSTVOLUME 0x0100U + +#define LHD_SPLIT_BEFORE 0x0001U +#define LHD_SPLIT_AFTER 0x0002U +#define LHD_PASSWORD 0x0004U + +// Old style file comment embed into file header. Must not be used +// in new archives anymore. +#define LHD_COMMENT 0x0008U + +// For non-file subheaders it denotes 'subblock having a parent file' flag. +#define LHD_SOLID 0x0010U + + +#define LHD_WINDOWMASK 0x00e0U +#define LHD_WINDOW64 0x0000U +#define LHD_WINDOW128 0x0020U +#define LHD_WINDOW256 0x0040U +#define LHD_WINDOW512 0x0060U +#define LHD_WINDOW1024 0x0080U +#define LHD_WINDOW2048 0x00a0U +#define LHD_WINDOW4096 0x00c0U +#define LHD_DIRECTORY 0x00e0U + +#define LHD_LARGE 0x0100U +#define LHD_UNICODE 0x0200U +#define LHD_SALT 0x0400U +#define LHD_VERSION 0x0800U +#define LHD_EXTTIME 0x1000U + +#define SKIP_IF_UNKNOWN 0x4000U +#define LONG_BLOCK 0x8000U + +#define EARC_NEXT_VOLUME 0x0001U // Not last volume. +#define EARC_DATACRC 0x0002U // Store CRC32 of RAR archive (now is used only in volumes). +#define EARC_REVSPACE 0x0004U // Reserve space for end of REV file 7 byte record. +#define EARC_VOLNUMBER 0x0008U // Store a number of current volume. + +enum HEADER_TYPE { + // RAR 5.0 header types. + HEAD_MARK=0x00, HEAD_MAIN=0x01, HEAD_FILE=0x02, HEAD_SERVICE=0x03, + HEAD_CRYPT=0x04, HEAD_ENDARC=0x05, HEAD_UNKNOWN=0xff, + + // RAR 1.5 - 4.x header types. + HEAD3_MARK=0x72,HEAD3_MAIN=0x73,HEAD3_FILE=0x74,HEAD3_CMT=0x75, + HEAD3_AV=0x76,HEAD3_OLDSERVICE=0x77,HEAD3_PROTECT=0x78,HEAD3_SIGN=0x79, + HEAD3_SERVICE=0x7a,HEAD3_ENDARC=0x7b +}; + +enum { EA_HEAD=0x100,UO_HEAD=0x101,MAC_HEAD=0x102,BEEA_HEAD=0x103, + NTACL_HEAD=0x104,STREAM_HEAD=0x105 }; + + +// Internal implementation, depends on archive format version. +enum HOST_SYSTEM { + // RAR 5.0 host OS + HOST5_WINDOWS=0,HOST5_UNIX=1, + + // RAR 3.0 host OS. + HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4, + HOST_BEOS=5,HOST_MAX +}; + +// Unified archive format independent implementation. +enum HOST_SYSTEM_TYPE { + HSYS_WINDOWS, HSYS_UNIX, HSYS_UNKNOWN +}; + + +// We also use these values in extra field, so do not modify them. +enum FILE_SYSTEM_REDIRECT { + FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION, + FSREDIR_HARDLINK, FSREDIR_FILECOPY +}; + + +#define SUBHEAD_TYPE_CMT L"CMT" +#define SUBHEAD_TYPE_QOPEN L"QO" +#define SUBHEAD_TYPE_ACL L"ACL" +#define SUBHEAD_TYPE_STREAM L"STM" +#define SUBHEAD_TYPE_UOWNER L"UOW" +#define SUBHEAD_TYPE_AV L"AV" +#define SUBHEAD_TYPE_RR L"RR" +#define SUBHEAD_TYPE_OS2EA L"EA2" + +/* new file inherits a subblock when updating a host file */ +#define SUBHEAD_FLAGS_INHERITED 0x80000000 + +#define SUBHEAD_FLAGS_CMT_UNICODE 0x00000001 + + +struct MarkHeader +{ + byte Mark[8]; + + // Following fields are virtual and not present in real blocks. + uint HeadSize; +}; + + +struct BaseBlock +{ + uint HeadCRC; // 'ushort' for RAR 1.5. + HEADER_TYPE HeaderType; // 1 byte for RAR 1.5. + uint Flags; // 'ushort' for RAR 1.5. + uint HeadSize; // 'ushort' for RAR 1.5, up to 2 MB for RAR 5.0. + + bool SkipIfUnknown; + + void Reset() + { + SkipIfUnknown=false; + } +}; + + +struct BlockHeader:BaseBlock +{ + uint DataSize; +}; + + +struct MainHeader:BaseBlock +{ + ushort HighPosAV; + uint PosAV; + bool CommentInHeader; + 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. + void Reset(); +}; + + +struct FileHeader:BlockHeader +{ + byte HostOS; + byte UnpVer; + byte Method; + union { + uint FileAttr; + uint SubFlags; + }; + wchar FileName[NM]; + + Array SubData; + + RarTime mtime; + RarTime ctime; + RarTime atime; + + int64 PackSize; + int64 UnpSize; + int64 MaxSize; // Reserve size bytes for vint of this size. + + HashValue FileHash; + + uint FileFlags; + + bool SplitBefore; + bool SplitAfter; + + bool UnknownUnpSize; + + bool Encrypted; + CRYPT_METHOD CryptMethod; + bool SaltSet; + byte Salt[SIZE_SALT50]; + byte InitV[SIZE_INITV]; + bool UsePswCheck; + byte PswCheck[SIZE_PSWCHECK]; + + // Use HMAC calculated from HashKey and checksum instead of plain checksum. + bool UseHashKey; + + // Key to convert checksum to HMAC. Derived from password with PBKDF2 + // using additional iterations. + byte HashKey[SHA256_DIGEST_SIZE]; + + uint Lg2Count; // Log2 of PBKDF2 repetition count. + + bool Solid; + bool Dir; + bool CommentInHeader; // RAR 2.0 file comment. + bool Version; // name.ext;ver file name containing the version number. + size_t WinSize; + bool Inherited; // New file inherits a subblock when updating a host file (for subblocks only). + + // 'true' if file sizes use 8 bytes instead of 4. Not used in RAR 5.0. + 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. + bool SubBlock; + + HOST_SYSTEM_TYPE HSType; + + FILE_SYSTEM_REDIRECT RedirType; + wchar RedirName[NM]; + bool DirTarget; + + bool UnixOwnerSet,UnixOwnerNumeric,UnixGroupNumeric; + char UnixOwnerName[256],UnixGroupName[256]; +#ifdef _UNIX + uid_t UnixOwnerID; + gid_t UnixGroupID; +#else // Need these Unix fields in Windows too for 'list' command. + uint UnixOwnerID; + uint UnixGroupID; +#endif + + void Reset(size_t SubDataSize=0); + + bool CmpName(const wchar *Name) + { + return(wcscmp(FileName,Name)==0); + } + + FileHeader& operator = (FileHeader &hd); +}; + + +struct EndArcHeader:BaseBlock +{ + // Optional CRC32 of entire archive up to start of EndArcHeader block. + // Present in RAR 4.x archives if EARC_DATACRC flag is set. + uint ArcDataCRC; + + uint VolNumber; // Optional number of current volume. + + // 7 additional zero bytes can be stored here if EARC_REVSPACE is set. + + bool NextVolume; // Not last volume. + bool DataCRC; + bool RevSpace; + bool StoreVolNumber; + void Reset() + { + BaseBlock::Reset(); + NextVolume=false; + DataCRC=false; + RevSpace=false; + StoreVolNumber=false; + } +}; + + +struct CryptHeader:BaseBlock +{ + bool UsePswCheck; + uint Lg2Count; // Log2 of PBKDF2 repetition count. + byte Salt[SIZE_SALT50]; + byte PswCheck[SIZE_PSWCHECK]; +}; + + +// SubBlockHeader and its successors were used in RAR 2.x format. +// RAR 4.x uses FileHeader with HEAD_SERVICE HeaderType for subblocks. +struct SubBlockHeader:BlockHeader +{ + ushort SubType; + byte Level; +}; + + +struct CommentHeader:BaseBlock +{ + ushort UnpSize; + byte UnpVer; + byte Method; + ushort CommCRC; +}; + + +struct ProtectHeader:BlockHeader +{ + byte Version; + ushort RecSectors; + uint TotalBlocks; + byte Mark[8]; +}; + + +struct AVHeader:BaseBlock +{ + byte UnpVer; + byte Method; + byte AVVer; + uint AVInfoCRC; +}; + + +struct SignHeader:BaseBlock +{ + uint CreationTime; + ushort ArcNameSize; + ushort UserNameSize; +}; + + +struct UnixOwnersHeader:SubBlockHeader +{ + ushort OwnerNameSize; + ushort GroupNameSize; +/* dummy */ + char OwnerName[256]; + char GroupName[256]; +}; + + +struct EAHeader:SubBlockHeader +{ + uint UnpSize; + byte UnpVer; + byte Method; + uint EACRC; +}; + + +struct StreamHeader:SubBlockHeader +{ + uint UnpSize; + byte UnpVer; + byte Method; + uint StreamCRC; + ushort StreamNameSize; + char StreamName[260]; +}; + + +struct MacFInfoHeader:SubBlockHeader +{ + uint fileType; + uint fileCreator; +}; + + +#endif diff --git a/reference/unrar/headers5.hpp b/reference/unrar/headers5.hpp new file mode 100644 index 00000000..9ea8d979 --- /dev/null +++ b/reference/unrar/headers5.hpp @@ -0,0 +1,100 @@ +#ifndef _RAR_HEADERS5_ +#define _RAR_HEADERS5_ + +#define SIZEOF_MARKHEAD5 8 // RAR 5.0 signature length. +#define SIZEOF_SHORTBLOCKHEAD5 7 // Smallest RAR 5.0 block size. + +// RAR 5.0 block flags common for all blocks. + +// Additional extra area is present in the end of block header. +#define HFL_EXTRA 0x0001 +// Additional data area is present in the end of block header. +#define HFL_DATA 0x0002 +// Unknown blocks with this flag must be skipped when updating an archive. +#define HFL_SKIPIFUNKNOWN 0x0004 +// Data area of this block is continuing from previous volume. +#define HFL_SPLITBEFORE 0x0008 +// Data area of this block is continuing in next volume. +#define HFL_SPLITAFTER 0x0010 +// Block depends on preceding file block. +#define HFL_CHILD 0x0020 +// Preserve a child block if host is modified. +#define HFL_INHERITED 0x0040 + +// RAR 5.0 main archive header specific flags. +#define MHFL_VOLUME 0x0001 // Volume. +#define MHFL_VOLNUMBER 0x0002 // Volume number field is present. True for all volumes except first. +#define MHFL_SOLID 0x0004 // Solid archive. +#define MHFL_PROTECT 0x0008 // Recovery record is present. +#define MHFL_LOCK 0x0010 // Locked archive. + +// RAR 5.0 file header specific flags. +#define FHFL_DIRECTORY 0x0001 // Directory. +#define FHFL_UTIME 0x0002 // Time field in Unix format is present. +#define FHFL_CRC32 0x0004 // CRC32 field is present. +#define FHFL_UNPUNKNOWN 0x0008 // Unknown unpacked size. + +// RAR 5.0 end of archive header specific flags. +#define EHFL_NEXTVOLUME 0x0001 // Not last volume. + +// RAR 5.0 archive encryption header specific flags. +#define CHFL_CRYPT_PSWCHECK 0x0001 // Password check data is present. + + +// RAR 5.0 file compression flags. +#define FCI_ALGO_BIT0 0x0001 // Version of compression algorithm. +#define FCI_ALGO_BIT1 0x0002 // 0 .. 63. +#define FCI_ALGO_BIT2 0x0004 +#define FCI_ALGO_BIT3 0x0008 +#define FCI_ALGO_BIT4 0x0010 +#define FCI_ALGO_BIT5 0x0020 +#define FCI_SOLID 0x0040 // Solid flag. +#define FCI_METHOD_BIT0 0x0080 // Compression method. +#define FCI_METHOD_BIT1 0x0100 // 0 .. 5 (6 and 7 are not used). +#define FCI_METHOD_BIT2 0x0200 +#define FCI_DICT_BIT0 0x0400 // Dictionary size. +#define FCI_DICT_BIT1 0x0800 // 128 KB .. 4 GB. +#define FCI_DICT_BIT2 0x1000 +#define FCI_DICT_BIT3 0x2000 + +// Main header extra field values. +#define MHEXTRA_LOCATOR 0x01 // Position of quick list and other blocks. + +// Flags for MHEXTRA_LOCATOR. +#define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present. +#define MHEXTRA_LOCATOR_RR 0x02 // Recovery record offset is present. + +// File and service header extra field values. +#define FHEXTRA_CRYPT 0x01 // Encryption parameters. +#define FHEXTRA_HASH 0x02 // File hash. +#define FHEXTRA_HTIME 0x03 // High precision file time. +#define FHEXTRA_VERSION 0x04 // File version information. +#define FHEXTRA_REDIR 0x05 // File system redirection (links, etc.). +#define FHEXTRA_UOWNER 0x06 // Unix owner and group information. +#define FHEXTRA_SUBDATA 0x07 // Service header subdata array. + + +// Hash type values for FHEXTRA_HASH. +#define FHEXTRA_HASH_BLAKE2 0x00 + +// Flags for FHEXTRA_HTIME. +#define FHEXTRA_HTIME_UNIXTIME 0x01 // Use Unix time_t format. +#define FHEXTRA_HTIME_MTIME 0x02 // mtime is present. +#define FHEXTRA_HTIME_CTIME 0x04 // ctime is present. +#define FHEXTRA_HTIME_ATIME 0x08 // atime is present. +#define FHEXTRA_HTIME_UNIX_NS 0x10 // Unix format with nanosecond precision. + +// Flags for FHEXTRA_CRYPT. +#define FHEXTRA_CRYPT_PSWCHECK 0x01 // Store password check data. +#define FHEXTRA_CRYPT_HASHMAC 0x02 // Use MAC for unpacked data checksums. + +// Flags for FHEXTRA_REDIR. +#define FHEXTRA_REDIR_DIR 0x01 // Link target is directory. + +// Flags for FHEXTRA_UOWNER. +#define FHEXTRA_UOWNER_UNAME 0x01 // User name string is present. +#define FHEXTRA_UOWNER_GNAME 0x02 // Group name string is present. +#define FHEXTRA_UOWNER_NUMUID 0x04 // Numeric user ID is present. +#define FHEXTRA_UOWNER_NUMGID 0x08 // Numeric group ID is present. + +#endif diff --git a/reference/unrar/isnt.cpp b/reference/unrar/isnt.cpp new file mode 100644 index 00000000..6fadec04 --- /dev/null +++ b/reference/unrar/isnt.cpp @@ -0,0 +1,24 @@ +#include "rar.hpp" + +#ifdef _WIN_ALL +DWORD WinNT() +{ + static int dwPlatformId=-1; + static DWORD dwMajorVersion,dwMinorVersion; + if (dwPlatformId==-1) + { + OSVERSIONINFO WinVer; + WinVer.dwOSVersionInfoSize=sizeof(WinVer); + GetVersionEx(&WinVer); + dwPlatformId=WinVer.dwPlatformId; + dwMajorVersion=WinVer.dwMajorVersion; + dwMinorVersion=WinVer.dwMinorVersion; + } + DWORD Result=0; + if (dwPlatformId==VER_PLATFORM_WIN32_NT) + Result=dwMajorVersion*0x100+dwMinorVersion; + + + return Result; +} +#endif diff --git a/reference/unrar/isnt.hpp b/reference/unrar/isnt.hpp new file mode 100644 index 00000000..85790da4 --- /dev/null +++ b/reference/unrar/isnt.hpp @@ -0,0 +1,13 @@ +#ifndef _RAR_ISNT_ +#define _RAR_ISNT_ + +enum WINNT_VERSION { + WNT_NONE=0,WNT_NT351=0x0333,WNT_NT4=0x0400,WNT_W2000=0x0500, + WNT_WXP=0x0501,WNT_W2003=0x0502,WNT_VISTA=0x0600,WNT_W7=0x0601, + WNT_W8=0x0602,WNT_W81=0x0603,WNT_W10=0x0a00 +}; + +DWORD WinNT(); + + +#endif diff --git a/reference/unrar/license.txt b/reference/unrar/license.txt new file mode 100644 index 00000000..0811276a --- /dev/null +++ b/reference/unrar/license.txt @@ -0,0 +1,42 @@ + ****** ***** ****** UnRAR - free utility for RAR archives + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ****** ******* ****** License for use and distribution of + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ** ** ** ** ** ** FREE portable version + ~~~~~~~~~~~~~~~~~~~~~ + + The source code of UnRAR utility is freeware. This means: + + 1. All copyrights to RAR and the utility UnRAR are exclusively + owned by the author - Alexander Roshal. + + 2. UnRAR source code may be used in any software to handle + RAR archives without limitations free of charge, but cannot be + used to develop RAR (WinRAR) compatible archiver and to + re-create RAR compression algorithm, which is proprietary. + Distribution of modified UnRAR source code in separate form + or as a part of other software is permitted, provided that + full text of this paragraph, starting from "UnRAR source code" + words, is included in license, or in documentation if license + is not available, and in source code comments of resulting package. + + 3. The UnRAR utility may be freely distributed. It is allowed + to distribute UnRAR inside of other software packages. + + 4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS". + NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT + YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, + DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING + OR MISUSING THIS SOFTWARE. + + 5. Installing and using the UnRAR utility signifies acceptance of + these terms and conditions of the license. + + 6. If you don't agree with terms of the license you must remove + UnRAR files from your storage devices and cease to use the + utility. + + Thank you for your interest in RAR and UnRAR. + + + Alexander L. Roshal diff --git a/reference/unrar/list.cpp b/reference/unrar/list.cpp new file mode 100644 index 00000000..561122b4 --- /dev/null +++ b/reference/unrar/list.cpp @@ -0,0 +1,472 @@ +#include "rar.hpp" + +static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare); +static void ListSymLink(Archive &Arc); +static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize); +static void ListOldSubHeader(Archive &Arc); +static void ListNewSubHeader(CommandData *Cmd,Archive &Arc); + +void ListArchive(CommandData *Cmd) +{ + 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'); + + wchar ArcName[NM]; + while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) + { + if (Cmd->ManualPassword) + Cmd->Password.Clean(); // Clean user entered password before processing next archive. + + Archive Arc(Cmd); +#ifdef _WIN_ALL + Arc.RemoveSequentialFlag(); +#endif + if (!Arc.WOpen(ArcName)) + continue; + bool FileMatched=true; + while (1) + { + int64 TotalPackSize=0,TotalUnpSize=0; + uint FileCount=0; + if (Arc.IsArchive(true)) + { + bool TitleShown=false; + if (!Bare) + { + 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"); + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", Fmt); + if (Arc.Solid) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSolid)); + if (Arc.SFXSize>0) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSFX)); + if (Arc.Volume) + if (Arc.Format==RARFMT50) + { + // RAR 5.0 archives store the volume number in main header, + // so it is already available now. + if (SetCount++ > 0) + mprintf(L", "); + mprintf(St(MVolumeNumber),Arc.VolNumber+1); + } + else + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListVolume)); + if (Arc.Protected) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListRR)); + if (Arc.Locked) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListLock)); + if (Arc.Encrypted) + mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListEncHead)); + mprintf(L"\n"); + } + + wchar VolNumText[50]; + *VolNumText=0; + while(Arc.ReadHeader()>0) + { + HEADER_TYPE HeaderType=Arc.GetHeaderType(); + if (HeaderType==HEAD_ENDARC) + { +#ifndef SFX_MODULE + // Only RAR 1.5 archives store the volume number in end record. + if (Arc.EndArcHead.StoreVolNumber && Arc.Format==RARFMT15) + swprintf(VolNumText,ASIZE(VolNumText),L"%.10ls %u",St(MListVolume),Arc.VolNumber+1); +#endif + if (Technical && ShowService) + { + mprintf(L"\n%12ls: %ls",St(MListService),L"EOF"); + if (*VolNumText!=0) + mprintf(L"\n%12ls: %ls",St(MListFlags),VolNumText); + mprintf(L"\n"); + } + break; + } + switch(HeaderType) + { + case HEAD_FILE: + FileMatched=Cmd->IsProcessFile(Arc.FileHead)!=0; + if (FileMatched) + { + ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare); + if (!Arc.FileHead.SplitBefore) + { + TotalUnpSize+=Arc.FileHead.UnpSize; + FileCount++; + } + TotalPackSize+=Arc.FileHead.PackSize; + } + break; + case HEAD_SERVICE: + if (FileMatched && !Bare) + { + if (Technical && ShowService) + ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false); + } + break; + } + Arc.SeekToNext(); + } + if (!Bare && !Technical) + if (TitleShown) + { + wchar UnpSizeText[20]; + itoa(TotalUnpSize,UnpSizeText,ASIZE(UnpSizeText)); + + wchar PackSizeText[20]; + itoa(TotalPackSize,PackSizeText,ASIZE(PackSizeText)); + + if (Verbose) + { + mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); + mprintf(L"\n%21ls %9ls %3d%% %-27ls %u",UnpSizeText, + PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize), + VolNumText,FileCount); + } + else + { + mprintf(L"\n----------- --------- ---------- ----- ----"); + mprintf(L"\n%21ls %-16ls %u",UnpSizeText,VolNumText,FileCount); + } + + SumFileCount+=FileCount; + SumUnpSize+=TotalUnpSize; + SumPackSize+=TotalPackSize; + mprintf(L"\n"); + } + else + mprintf(St(MListNoFiles)); + + ArcCount++; + +#ifndef NOVOLUME + if (Cmd->VolSize!=0 && (Arc.FileHead.SplitAfter || + Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) && + MergeArchive(Arc,NULL,false,Cmd->Command[0])) + Arc.Seek(0,SEEK_SET); + else +#endif + break; + } + else + { + if (Cmd->ArcNames.ItemsCount()<2 && !Bare) + mprintf(St(MNotRAR),Arc.FileName); + break; + } + } + } + + // Clean user entered password. Not really required, just for extra safety. + if (Cmd->ManualPassword) + Cmd->Password.Clean(); + + if (ArcCount>1 && !Bare && !Technical) + { + wchar UnpSizeText[20],PackSizeText[20]; + itoa(SumUnpSize,UnpSizeText,ASIZE(UnpSizeText)); + itoa(SumPackSize,PackSizeText,ASIZE(PackSizeText)); + + if (Verbose) + mprintf(L"%21ls %9ls %3d%% %28ls %u",UnpSizeText,PackSizeText, + ToPercentUnlim(SumPackSize,SumUnpSize),L"",SumFileCount); + else + mprintf(L"%21ls %18s %lu",UnpSizeText,L"",SumFileCount); + } +} + + +enum LISTCOL_TYPE { + LCOL_NAME,LCOL_ATTR,LCOL_SIZE,LCOL_PACKED,LCOL_RATIO,LCOL_CSUM,LCOL_ENCR +}; + + +void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare) +{ + wchar *Name=hd.FileName; + RARFORMAT Format=Arc.Format; + + if (Bare) + { + mprintf(L"%s\n",Name); + return; + } + + if (!TitleShown && !Technical) + { + if (Verbose) + { + mprintf(L"\n%ls",St(MListTitleV)); + mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----"); + } + else + { + mprintf(L"\n%ls",St(MListTitleL)); + mprintf(L"\n----------- --------- ---------- ----- ----"); + } + TitleShown=true; + } + + wchar UnpSizeText[30],PackSizeText[30]; + if (hd.UnpSize==INT64NDF) + wcscpy(UnpSizeText,L"?"); + else + itoa(hd.UnpSize,UnpSizeText,ASIZE(UnpSizeText)); + itoa(hd.PackSize,PackSizeText,ASIZE(PackSizeText)); + + wchar AttrStr[30]; + if (hd.HeaderType==HEAD_SERVICE) + swprintf(AttrStr,ASIZE(AttrStr),L"%cB",hd.Inherited ? 'I' : '.'); + else + ListFileAttr(hd.FileAttr,hd.HSType,AttrStr,ASIZE(AttrStr)); + + wchar RatioStr[10]; + + if (hd.SplitBefore && hd.SplitAfter) + wcscpy(RatioStr,L"<->"); + else + if (hd.SplitBefore) + wcscpy(RatioStr,L"<--"); + else + if (hd.SplitAfter) + wcscpy(RatioStr,L"-->"); + else + swprintf(RatioStr,ASIZE(RatioStr),L"%d%%",ToPercentUnlim(hd.PackSize,hd.UnpSize)); + + wchar DateStr[50]; + hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical); + + if (Technical) + { + mprintf(L"\n%12s: %s",St(MListName),Name); + + bool FileBlock=hd.HeaderType==HEAD_FILE; + + if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM)) + { + mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream)); + wchar StreamName[NM]; + GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName)); + mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName); + } + else + { + const wchar *Type=St(FileBlock ? (hd.Dir ? MListDir:MListFile):MListService); + + if (hd.RedirType!=FSREDIR_NONE) + switch(hd.RedirType) + { + case FSREDIR_UNIXSYMLINK: + Type=St(MListUSymlink); break; + case FSREDIR_WINSYMLINK: + Type=St(MListWSymlink); break; + case FSREDIR_JUNCTION: + Type=St(MListJunction); break; + case FSREDIR_HARDLINK: + Type=St(MListHardlink); break; + case FSREDIR_FILECOPY: + Type=St(MListCopy); break; + } + mprintf(L"\n%12ls: %ls",St(MListType),Type); + if (hd.RedirType!=FSREDIR_NONE) + if (Format==RARFMT15) + { + char LinkTargetA[NM]; + if (Arc.FileHead.Encrypted) + { + // Link data are encrypted. We would need to ask for password + // and initialize decryption routine to display the link target. + strncpyz(LinkTargetA,"*<-?->",ASIZE(LinkTargetA)); + } + else + { + int DataSize=(int)Min(hd.PackSize,ASIZE(LinkTargetA)-1); + Arc.Read(LinkTargetA,DataSize); + LinkTargetA[DataSize > 0 ? DataSize : 0] = 0; + } + wchar LinkTarget[NM]; + CharToWide(LinkTargetA,LinkTarget,ASIZE(LinkTarget)); + mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget); + } + else + mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName); + } + if (!hd.Dir) + { + mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText); + mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText); + mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr); + } + if (hd.mtime.IsSet()) + mprintf(L"\n%12ls: %ls",St(MListMtime),DateStr); + if (hd.ctime.IsSet()) + { + hd.ctime.GetText(DateStr,ASIZE(DateStr),true); + mprintf(L"\n%12ls: %ls",St(MListCtime),DateStr); + } + if (hd.atime.IsSet()) + { + hd.atime.GetText(DateStr,ASIZE(DateStr),true); + mprintf(L"\n%12ls: %ls",St(MListAtime),DateStr); + } + mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr); + if (hd.FileHash.Type==HASH_CRC32) + mprintf(L"\n%12ls: %8.8X", + hd.UseHashKey ? L"CRC32 MAC":hd.SplitAfter ? L"Pack-CRC32":L"CRC32", + hd.FileHash.CRC32); + if (hd.FileHash.Type==HASH_BLAKE2) + { + wchar BlakeStr[BLAKE2_DIGEST_SIZE*2+1]; + BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,NULL,BlakeStr,ASIZE(BlakeStr)); + mprintf(L"\n%12ls: %ls", + hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2", + BlakeStr); + } + + const wchar *HostOS=L""; + if (Format==RARFMT50 && hd.HSType!=HSYS_UNKNOWN) + HostOS=hd.HSType==HSYS_WINDOWS ? L"Windows":L"Unix"; + if (Format==RARFMT15) + { + static const wchar *RarOS[]={ + L"DOS",L"OS/2",L"Windows",L"Unix",L"Mac OS",L"BeOS",L"WinCE",L"",L"",L"" + }; + if (hd.HostOS=0x100000 ? hd.WinSize/0x100000:hd.WinSize/0x400, + hd.WinSize>=0x100000 ? L"M":L"K"); + + if (hd.Solid || hd.Encrypted) + { + mprintf(L"\n%12ls: ",St(MListFlags)); + if (hd.Solid) + mprintf(L"%ls ",St(MListSolid)); + if (hd.Encrypted) + mprintf(L"%ls ",St(MListEnc)); + } + + if (hd.Version) + { + uint Version=ParseVersionFileName(Name,false); + if (Version!=0) + mprintf(L"\n%12ls: %u",St(MListFileVer),Version); + } + + if (hd.UnixOwnerSet) + { + mprintf(L"\n%12ls: ",L"Unix owner"); + if (*hd.UnixOwnerName!=0) + mprintf(L"%ls:",GetWide(hd.UnixOwnerName)); + if (*hd.UnixGroupName!=0) + mprintf(L"%ls",GetWide(hd.UnixGroupName)); + if ((*hd.UnixOwnerName!=0 || *hd.UnixGroupName!=0) && (hd.UnixOwnerNumeric || hd.UnixGroupNumeric)) + mprintf(L" "); + if (hd.UnixOwnerNumeric) + mprintf(L"#%d:",hd.UnixOwnerID); + if (hd.UnixGroupNumeric) + mprintf(L"#%d:",hd.UnixGroupID); + } + + mprintf(L"\n"); + return; + } + + mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText); + + if (Verbose) + mprintf(L"%9ls %4ls ",PackSizeText,RatioStr); + + mprintf(L" %ls ",DateStr); + + if (Verbose) + { + if (hd.FileHash.Type==HASH_CRC32) + mprintf(L"%8.8X ",hd.FileHash.CRC32); + else + if (hd.FileHash.Type==HASH_BLAKE2) + { + byte *S=hd.FileHash.Digest; + mprintf(L"%02x%02x..%02x ",S[0],S[1],S[31]); + } + else + mprintf(L"???????? "); + } + mprintf(L"%ls",Name); +} + +/* +void ListSymLink(Archive &Arc) +{ + if (Arc.FileHead.HSType==HSYS_UNIX && (Arc.FileHead.FileAttr & 0xF000)==0xA000) + if (Arc.FileHead.Encrypted) + { + // Link data are encrypted. We would need to ask for password + // and initialize decryption routine to display the link target. + mprintf(L"\n%22ls %ls",L"-->",L"*<-?->"); + } + else + { + char FileName[NM]; + uint DataSize=(uint)Min(Arc.FileHead.PackSize,sizeof(FileName)-1); + Arc.Read(FileName,DataSize); + FileName[DataSize]=0; + mprintf(L"\n%22ls %ls",L"-->",GetWide(FileName)); + } +} +*/ + +void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize) +{ + switch(HostType) + { + case HSYS_WINDOWS: + swprintf(AttrStr,AttrSize,L"%c%c%c%c%c%c%c", + (A & 0x2000)!=0 ? 'I' : '.', // Not content indexed. + (A & 0x0800)!=0 ? 'C' : '.', // Compressed. + (A & 0x0020)!=0 ? 'A' : '.', // Archive. + (A & 0x0010)!=0 ? 'D' : '.', // Directory. + (A & 0x0004)!=0 ? 'S' : '.', // System. + (A & 0x0002)!=0 ? 'H' : '.', // Hidden. + (A & 0x0001)!=0 ? 'R' : '.'); // Read-only. + break; + case HSYS_UNIX: + switch (A & 0xF000) + { + case 0x4000: + AttrStr[0]='d'; + break; + case 0xA000: + AttrStr[0]='l'; + break; + default: + AttrStr[0]='-'; + break; + } + swprintf(AttrStr+1,AttrSize-1,L"%c%c%c%c%c%c%c%c%c", + (A & 0x0100) ? 'r' : '-', + (A & 0x0080) ? 'w' : '-', + (A & 0x0040) ? ((A & 0x0800)!=0 ? 's':'x'):((A & 0x0800)!=0 ? 'S':'-'), + (A & 0x0020) ? 'r' : '-', + (A & 0x0010) ? 'w' : '-', + (A & 0x0008) ? ((A & 0x0400)!=0 ? 's':'x'):((A & 0x0400)!=0 ? 'S':'-'), + (A & 0x0004) ? 'r' : '-', + (A & 0x0002) ? 'w' : '-', + (A & 0x0001) ? ((A & 0x200)!=0 ? 't' : 'x') : '-'); + break; + case HSYS_UNKNOWN: + wcscpy(AttrStr,L"?"); + break; + } +} diff --git a/reference/unrar/list.hpp b/reference/unrar/list.hpp new file mode 100644 index 00000000..7721ae52 --- /dev/null +++ b/reference/unrar/list.hpp @@ -0,0 +1,6 @@ +#ifndef _RAR_LIST_ +#define _RAR_LIST_ + +void ListArchive(CommandData *Cmd); + +#endif diff --git a/reference/unrar/loclang.hpp b/reference/unrar/loclang.hpp new file mode 100644 index 00000000..427128b4 --- /dev/null +++ b/reference/unrar/loclang.hpp @@ -0,0 +1,381 @@ +#define MYesNo L"_Yes_No" +#define MYesNoAll L"_Yes_No_All" +#define MYesNoAllQ L"_Yes_No_All_nEver_Quit" +#define MYesNoAllRenQ L"_Yes_No_All_nEver_Rename_Quit" +#define MContinueQuit L"_Continue_Quit" +#define MRetryAbort L"_Retry_Abort" +#define MCopyright L"\nRAR %s Copyright (c) 1993-%d Alexander Roshal %d %s %d" +#define MRegTo L"\nRegistered to %s\n" +#define MShare L"\nTrial version Type 'rar -?' for help\n" +#define MRegKeyWarning L"\nAvailable license key is valid only for %s\n" +#define MUCopyright L"\nUNRAR %s freeware Copyright (c) 1993-%d Alexander Roshal\n" +#define MBeta L"beta" +#define Mx86 L"x86" +#define Mx64 L"x64" +#define MMonthJan L"Jan" +#define MMonthFeb L"Feb" +#define MMonthMar L"Mar" +#define MMonthApr L"Apr" +#define MMonthMay L"May" +#define MMonthJun L"Jun" +#define MMonthJul L"Jul" +#define MMonthAug L"Aug" +#define MMonthSep L"Sep" +#define MMonthOct L"Oct" +#define MMonthNov L"Nov" +#define MMonthDec L"Dec" +#define MRARTitle1 L"\nUsage: rar - - " +#define MUNRARTitle1 L"\nUsage: unrar - - " +#define MRARTitle2 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" +#define MCHelpCmdCH L"\n ch Change archive parameters" +#define MCHelpCmdCW L"\n cw Write archive comment to file" +#define MCHelpCmdD L"\n d Delete files from archive" +#define MCHelpCmdE L"\n e Extract files without archived paths" +#define MCHelpCmdF L"\n f Freshen files in archive" +#define MCHelpCmdI L"\n i[par]= Find string in archives" +#define MCHelpCmdK L"\n k Lock archive" +#define MCHelpCmdL L"\n l[t[a],b] List archive contents [technical[all], bare]" +#define MCHelpCmdM L"\n m[f] Move to archive [files only]" +#define MCHelpCmdP L"\n p Print file to stdout" +#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 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" +#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 MCHelpCmdX L"\n x Extract files with full path" +#define MCHelpSw L"\n\n" +#define MCHelpSwm L"\n - Stop switches scanning" +#define MCHelpSwAT L"\n @[+] Disable [enable] file lists" +#define MCHelpSwAC L"\n ac Clear Archive attribute after compression or extraction" +#define MCHelpSwAD L"\n ad Append archive name to destination path" +#define MCHelpSwAG L"\n ag[format] Generate archive name using the current date" +#define MCHelpSwAI L"\n ai Ignore file attributes" +#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" +#define MCHelpSwCm L"\n c- Disable comments show" +#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 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[+] Set file exclude and include attributes" +#define MCHelpSwED L"\n ed Do not add empty directories" +#define MCHelpSwEN L"\n en Do not put 'end of archive' block" +#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" +#define MCHelpSwEP3 L"\n ep3 Expand paths to full including the drive letter" +#define MCHelpSwF L"\n f Freshen files" +#define MCHelpSwHP L"\n hp[password] Encrypt both file data and headers" +#define MCHelpSwHT L"\n ht[b|c] Select hash type [BLAKE2,CRC32] for file checksum" +#define MCHelpSwIDP L"\n id[c,d,p,q] Disable messages" +#define MCHelpSwIEML L"\n ieml[addr] Send archive by email" +#define MCHelpSwIERR L"\n ierr Send all messages to stderr" +#define MCHelpSwILOG L"\n ilog[name] Log errors to file (registered versions only)" +#define MCHelpSwINUL L"\n inul Disable all messages" +#define MCHelpSwIOFF L"\n ioff Turn PC off after completing an operation" +#define MCHelpSwISND L"\n isnd Enable sound" +#define MCHelpSwIVER L"\n iver Display the version number" +#define MCHelpSwK L"\n k Lock archive" +#define MCHelpSwKB L"\n kb Keep broken extracted files" +#define MCHelpSwLog L"\n log[f][=name] Write names to log file" +#define MCHelpSwMn L"\n m<0..5> Set compression level (0-store...3-default...5-maximal)" +#define MCHelpSwMA L"\n ma[4|5] Specify a version of archiving format" +#define MCHelpSwMC L"\n mc Set advanced compression parameters" +#define MCHelpSwMD L"\n md[k,m,g] Dictionary size in KB, MB or GB" +#define MCHelpSwMS L"\n ms[ext;ext] Specify file types to store" +#define MCHelpSwMT L"\n mt Set the number of threads" +#define MCHelpSwN L"\n n Additionally filter included files" +#define MCHelpSwNa L"\n n@ Read additional filter masks from stdin" +#define MCHelpSwNal L"\n n@ Read additional filter masks from list file" +#define MCHelpSwO L"\n o[+|-] Set the overwrite mode" +#define MCHelpSwOC L"\n oc Set NTFS Compressed attribute" +#define MCHelpSwOH L"\n oh Save hard links as the link instead of the file" +#define MCHelpSwOI L"\n oi[0-4][:min] Save identical files as references" +#define MCHelpSwOL L"\n ol[a] Process symbolic links as the link [absolute paths]" +#define MCHelpSwONI L"\n oni Allow potentially incompatible names" +#define MCHelpSwOR L"\n or Rename files automatically" +#define MCHelpSwOS L"\n os Save NTFS streams" +#define MCHelpSwOW L"\n ow Save or restore file owner and group" +#define MCHelpSwP L"\n p[password] Set password" +#define MCHelpSwPm L"\n p- Do not query password" +#define MCHelpSwQO L"\n qo[-|+] Add quick open information [none|force]" +#define MCHelpSwR L"\n r Recurse subdirectories" +#define MCHelpSwRm L"\n r- Disable recursion" +#define MCHelpSwR0 L"\n r0 Recurse subdirectories for wildcard names only" +#define MCHelpSwRI L"\n ri

[:] Set priority (0-default,1-min..15-max) and sleep time in ms" +#define MCHelpSwRR L"\n rr[N] Add data recovery record" +#define MCHelpSwRV L"\n rv[N] Create recovery volumes" +#define MCHelpSwS L"\n s[,v[-],e] Create solid archive" +#define MCHelpSwSm L"\n s- Disable solid archiving" +#define MCHelpSwSC L"\n sc[obj] Specify the character set" +#define MCHelpSwSFX L"\n sfx[name] Create SFX archive" +#define MCHelpSwSI L"\n si[name] Read data from standard input (stdin)" +#define MCHelpSwSL L"\n sl Process files with size less than specified" +#define MCHelpSwSM L"\n sm Process files with size more than specified" +#define MCHelpSwT L"\n t Test files after archiving" +#define MCHelpSwTK L"\n tk Keep original archive time" +#define MCHelpSwTL L"\n tl Set archive time to latest file" +#define MCHelpSwTN L"\n tn

/// Entry is split among multiple volumes /// - public abstract bool IsSplit { get; } + public abstract bool IsSplitAfter { get; } /// public override string ToString() diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index 0120b782..4697ff8c 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -6,15 +6,15 @@ namespace SharpCompress.Common { public class EntryStream : Stream { - public IReader Reader { get; } - private readonly Stream stream; - private bool completed; - private bool isDisposed; + private readonly IReader _reader; + private readonly Stream _stream; + private bool _completed; + private bool _isDisposed; internal EntryStream(IReader reader, Stream stream) { - Reader = reader; - this.stream = stream; + _reader = reader; + _stream = stream; } /// @@ -22,26 +22,23 @@ namespace SharpCompress.Common /// public void SkipEntry() { - var buffer = new byte[4096]; - while (Read(buffer, 0, buffer.Length) > 0) - { - } - completed = true; + this.Skip(); + _completed = true; } protected override void Dispose(bool disposing) { - if (!(completed || Reader.Cancelled)) + if (!(_completed || _reader.Cancelled)) { SkipEntry(); } - if (isDisposed) + if (_isDisposed) { return; } - isDisposed = true; + _isDisposed = true; base.Dispose(disposing); - stream.Dispose(); + _stream.Dispose(); } public override bool CanRead => true; @@ -55,16 +52,16 @@ namespace SharpCompress.Common throw new NotSupportedException(); } - public override long Length => throw new NotSupportedException(); + public override long Length => _stream.Length; public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) { - int read = stream.Read(buffer, offset, count); + int read = _stream.Read(buffer, offset, count); if (read <= 0) { - completed = true; + _completed = true; } return read; } diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 6c80ddff..849d4741 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -36,7 +36,7 @@ namespace SharpCompress.Common.GZip public override bool IsDirectory => false; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index ac15828c..e8f13a30 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -12,7 +12,7 @@ namespace SharpCompress.Common string Key { get; } bool IsDirectory { get; } bool IsEncrypted { get; } - bool IsSplit { get; } + bool IsSplitAfter { get; } DateTime? LastAccessedTime { get; } DateTime? LastModifiedTime { get; } long Size { get; } diff --git a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs index acb878ba..73c1f3bd 100644 --- a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs @@ -2,19 +2,28 @@ namespace SharpCompress.Common.Rar.Headers { - internal class SignHeader : RarHeader + internal class AVHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) + public AVHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Av) { - CreationTime = reader.ReadInt32(); - ArcNameSize = reader.ReadInt16(); - UserNameSize = reader.ReadInt16(); + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - internal int CreationTime { get; private set; } + protected override void ReadFinish(MarkingBinaryReader reader) + { + UnpackVersion = reader.ReadByte(); + Method = reader.ReadByte(); + AVVersion = reader.ReadByte(); + AVInfoCRC = reader.ReadInt32(); + } - internal short ArcNameSize { get; private set; } + internal int AVInfoCRC { get; private set; } - internal short UserNameSize { get; private set; } + internal byte UnpackVersion { get; private set; } + + internal byte Method { get; private set; } + + internal byte AVVersion { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs new file mode 100644 index 00000000..0ffaed01 --- /dev/null +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs @@ -0,0 +1,57 @@ +using SharpCompress.IO; + +namespace SharpCompress.Common.Rar.Headers +{ + internal class ArchiveCryptHeader : RarHeader + { + + private const int CRYPT_VERSION = 0; // Supported encryption version. + private const int SIZE_SALT50 = 16; + private const int SIZE_SALT30 = 8; + private const int SIZE_INITV = 16; + private const int SIZE_PSWCHECK = 8; + private const int SIZE_PSWCHECK_CSUM = 4; + private const int CRYPT5_KDF_LG2_COUNT = 15; // LOG2 of PDKDF2 iteration count. + private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count. + + + private bool UsePswCheck; + private uint Lg2Count; // Log2 of PBKDF2 repetition count. + private byte[] Salt; + private byte[] PswCheck; + private byte[] PswCheckCsm; + + public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Crypt) + { + } + + protected override void ReadFinish(MarkingBinaryReader reader) + { + var cryptVersion = reader.ReadRarVIntUInt32(); + if (cryptVersion > CRYPT_VERSION) + { + //error? + return; + } + var encryptionFlags = reader.ReadRarVIntUInt32(); + UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + Lg2Count = reader.ReadRarVIntByte(1); + + + //UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + if (Lg2Count > CRYPT5_KDF_LG2_COUNT_MAX) + { + //error? + return; + } + + Salt = reader.ReadBytes(SIZE_SALT50); + if (UsePswCheck) + { + PswCheck = reader.ReadBytes(SIZE_PSWCHECK); + PswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM); + } + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 214a25c0..bc4955d0 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -4,27 +4,78 @@ namespace SharpCompress.Common.Rar.Headers { internal class ArchiveHeader : RarHeader { - internal const short mainHeaderSizeWithEnc = 7; - internal const short mainHeaderSize = 6; - - protected override void ReadFromReader(MarkingBinaryReader reader) + public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Archive) { - HighPosAv = reader.ReadInt16(); - PosAv = reader.ReadInt32(); - if (ArchiveHeaderFlags.HasFlag(ArchiveFlags.ENCRYPTVER)) + } + + protected override void ReadFinish(MarkingBinaryReader reader) + { + if (IsRar5) { - EncryptionVersion = reader.ReadByte(); + Flags = reader.ReadRarVIntUInt16(); + if (HasFlag(ArchiveFlagsV5.HasVolumeNumber)) + { + VolumeNumber = (int)reader.ReadRarVIntUInt32(); + } + // later: we may have a locator record if we need it + //if (ExtraSize != 0) { + // ReadLocator(reader); + //} + } + else + { + Flags = HeaderFlags; + HighPosAv = reader.ReadInt16(); + PosAv = reader.ReadInt32(); + if (HasFlag(ArchiveFlagsV4.EncryptVer)) + { + EncryptionVersion = reader.ReadByte(); + } } } - internal ArchiveFlags ArchiveHeaderFlags => (ArchiveFlags)Flags; + private void ReadLocator(MarkingBinaryReader reader) { + var size = reader.ReadRarVIntUInt16(); + var type = reader.ReadRarVIntUInt16(); + if (type != 1) throw new InvalidFormatException("expected locator record"); + var flags = reader.ReadRarVIntUInt16(); + const ushort hasQuickOpenOffset = 0x01; + const ushort hasRecoveryOffset = 0x02; + ulong quickOpenOffset = 0; + if ((flags & hasQuickOpenOffset) == hasQuickOpenOffset) { + quickOpenOffset = reader.ReadRarVInt(); + } + ulong recoveryOffset = 0; + if ((flags & hasRecoveryOffset) == hasRecoveryOffset) { + recoveryOffset = reader.ReadRarVInt(); + } + } - internal short HighPosAv { get; private set; } + private ushort Flags { get; set; } - internal int PosAv { get; private set; } + private bool HasFlag(ushort flag) + { + return (Flags & flag) == flag; + } - internal byte EncryptionVersion { get; private set; } + internal int? VolumeNumber { get; private set; } - public bool HasPassword => ArchiveHeaderFlags.HasFlag(ArchiveFlags.PASSWORD); + internal short? HighPosAv { get; private set; } + + internal int? PosAv { get; private set; } + + private byte? EncryptionVersion { get; set; } + + public bool? IsEncrypted => IsRar5 ? (bool?)null : HasFlag(ArchiveFlagsV4.Password); + + public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NewNumbering); + + public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.Volume : ArchiveFlagsV4.Volume); + + // RAR5: Volume number field is present. True for all volumes except first. + public bool IsFirstVolume => IsRar5 ? VolumeNumber == null : HasFlag(ArchiveFlagsV4.FirstVolume); + + public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.Solid : ArchiveFlagsV4.Solid); } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs index c9943bde..d15a1b8e 100644 --- a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs @@ -4,7 +4,13 @@ namespace SharpCompress.Common.Rar.Headers { internal class CommentHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) + protected CommentHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Comment) + { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); + } + + protected override void ReadFinish(MarkingBinaryReader reader) { UnpSize = reader.ReadInt16(); UnpVersion = reader.ReadByte(); diff --git a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs index b2f62039..07d1d95e 100644 --- a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs @@ -4,21 +4,39 @@ namespace SharpCompress.Common.Rar.Headers { internal class EndArchiveHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) + public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.EndArchive) { - if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_DATACRC)) + } + + protected override void ReadFinish(MarkingBinaryReader reader) + { + if (IsRar5) { - ArchiveCRC = reader.ReadInt32(); + Flags = reader.ReadRarVIntUInt16(); } - if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_VOLNUMBER)) + else { - VolumeNumber = reader.ReadInt16(); + Flags = HeaderFlags; + if (HasFlag(EndArchiveFlagsV4.DataCrc)) + { + ArchiveCrc = reader.ReadInt32(); + } + if (HasFlag(EndArchiveFlagsV4.VolumeNumber)) + { + VolumeNumber = reader.ReadInt16(); + } } } - internal EndArchiveFlags EndArchiveFlags => (EndArchiveFlags)Flags; + private ushort Flags { get; set; } - internal int? ArchiveCRC { get; private set; } + private bool HasFlag(ushort flag) + { + return (Flags & flag) == flag; + } + + internal int? ArchiveCrc { get; private set; } internal short? VolumeNumber { get; private set; } } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 1b539eb7..39b238ab 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -1,35 +1,235 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif + using SharpCompress.IO; using System; using System.IO; +using System.Text; namespace SharpCompress.Common.Rar.Headers { internal class FileHeader : RarHeader { - private const byte SALT_SIZE = 8; + private uint fileCrc; - private const byte NEWLHD_SIZE = 32; - - protected override void ReadFromReader(MarkingBinaryReader reader) + public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) + : base(header, reader, headerType) { + } + + protected override void ReadFinish(MarkingBinaryReader reader) + { + if (IsRar5) + { + ReadFromReaderV5(reader); + } + else + { + ReadFromReaderV4(reader); + } + } + + private void ReadFromReaderV5(MarkingBinaryReader reader) + { + Flags = reader.ReadRarVIntUInt16(); + + var lvalue = checked((long)reader.ReadRarVInt()); + + // long.MaxValue causes the unpack code to finish when the input stream is exhausted + UncompressedSize = HasFlag(FileFlagsV5.UnpackedSizeUnknown) ? long.MaxValue : lvalue; + + FileAttributes = reader.ReadRarVIntUInt32(); + + if (HasFlag(FileFlagsV5.HasModTime)) { + FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32()); + } + + if (HasFlag(FileFlagsV5.HasCrc32)) { + FileCrc = reader.ReadUInt32(); + } + + var compressionInfo = reader.ReadRarVIntUInt16(); + + // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. + // "+ 50" to not mix with old RAR format algorithms. For example, + // we may need to use the compression algorithm 15 in the future, + // but it was already used in RAR 1.5 and Unpack needs to distinguish + // them. + CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50); + + // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. + // It can be set only for file headers and is never set for service headers. + IsSolid = (compressionInfo & 0x40) == 0x40; + + // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. + CompressionMethod = (byte)((compressionInfo >> 7) & 0x7); + + // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. + WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo>>10) & 0xf); + + HostOs = reader.ReadRarVIntByte(); + + var nameSize = reader.ReadRarVIntUInt16(); + + // Variable length field containing Name length bytes in UTF-8 format without trailing zero. + // For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names. + // Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field. + // + // TODO: not sure if anything needs to be done to handle the following: + // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8 + // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character + // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting. + // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not + // portable and can be correctly unpacked only on the same system where they were created. + // + // For service header this field contains a name of service header. Now the following names are used: + // CMT Archive comment + // QO Archive quick open data + // ACL NTFS file permissions + // STM NTFS alternate data stream + // RR Recovery record + var b = reader.ReadBytes(nameSize); + FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length)); + + // extra size seems to be redudant since we know the total header size + if (ExtraSize != RemainingHeaderBytes(reader)) + { + throw new InvalidFormatException("rar5 header size / extra size inconsistency"); + } + + while (RemainingHeaderBytes(reader) > 0) { + var size = reader.ReadRarVIntUInt16(); + int n = RemainingHeaderBytes(reader); + var type = reader.ReadRarVIntUInt16(); + switch (type) { +//TODO +// case 1: // file encryption +// { +// var version = reader.ReadRarVIntByte(); +// if (version != 0) throw new InvalidFormatException("unknown encryption algorithm "+ version); +// +// } +// break; +// case 2: // file hash +// { +// +// } +// break; + case 3: // file time + { + ushort flags = reader.ReadRarVIntUInt16(); + var isWindowsTime = (flags & 1) == 0; + if ((flags & 0x2) == 0x2) { + FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x4) == 0x4) { + FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x8) == 0x8) { + FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + } + break; +//TODO +// case 4: // file version +// { +// +// } +// break; +// case 5: // file system redirection +// { +// +// } +// break; +// case 6: // unix owner +// { +// +// } +// break; +// case 7: // service data +// { +// +// } +// break; + + default: + // skip unknown record types to allow new record types to be added in the future + break; + } + // drain any trailing bytes of extra record + int did = n - RemainingHeaderBytes(reader); + int drain = size - did; + if (drain > 0) + { + reader.ReadBytes(drain); + } + } + + if (AdditionalDataSize != 0) { + CompressedSize = AdditionalDataSize; + } + } + + + private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime) + { + if (isWindowsTime) + { + return DateTime.FromFileTime(reader.ReadInt64()); + } + else + { + return Utility.UnixTimeToDateTime(reader.ReadUInt32()); + } + } + + private static string ConvertPathV5(string path) + { +#if NO_FILE + // not sure what to do here + throw new NotImplementedException("TODO"); +#else + if (Path.DirectorySeparatorChar == '\\') + { + // replace embedded \\ with valid filename char + return path.Replace('\\', '-').Replace('/', '\\'); + } + return path; +#endif + } + + + private void ReadFromReaderV4(MarkingBinaryReader reader) + { + Flags = HeaderFlags; + IsSolid = HasFlag(FileFlagsV4.Solid); + WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WindowMask) >> 5); + uint lowUncompressedSize = reader.ReadUInt32(); - HostOS = (HostOS)reader.ReadByte(); + HostOs = reader.ReadByte(); - FileCRC = reader.ReadUInt32(); + FileCrc = reader.ReadUInt32(); - FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadInt32()); + FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); - RarVersion = reader.ReadByte(); - PackingMethod = reader.ReadByte(); + CompressionAlgorithm = reader.ReadByte(); + CompressionMethod = (byte)(reader.ReadByte() - 0x30); short nameSize = reader.ReadInt16(); - FileAttributes = reader.ReadInt32(); + FileAttributes = reader.ReadUInt32(); uint highCompressedSize = 0; uint highUncompressedkSize = 0; - if (FileFlags.HasFlag(FileFlags.LARGE)) + if (HasFlag(FileFlagsV4.Large)) { highCompressedSize = reader.ReadUInt32(); highUncompressedkSize = reader.ReadUInt32(); @@ -42,18 +242,21 @@ namespace SharpCompress.Common.Rar.Headers highUncompressedkSize = int.MaxValue; } } - CompressedSize = UInt32To64(highCompressedSize, AdditionalSize); + CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize)); UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; byte[] fileNameBytes = reader.ReadBytes(nameSize); - switch (HeaderType) + const int saltSize = 8; + const int newLhdSize = 32; + + switch (HeaderCode) { - case HeaderType.FileHeader: + case HeaderCodeV.Rar4FileHeader: { - if (FileFlags.HasFlag(FileFlags.UNICODE)) + if (HasFlag(FileFlagsV4.Unicode)) { int length = 0; while (length < fileNameBytes.Length @@ -75,15 +278,15 @@ namespace SharpCompress.Common.Rar.Headers { FileName = ArchiveEncoding.Decode(fileNameBytes); } - FileName = ConvertPath(FileName, HostOS); + FileName = ConvertPathV4(FileName); } break; - case HeaderType.NewSubHeader: + case HeaderCodeV.Rar4NewSubHeader: { - int datasize = HeaderSize - NEWLHD_SIZE - nameSize; - if (FileFlags.HasFlag(FileFlags.SALT)) + int datasize = HeaderSize - newLhdSize - nameSize; + if (HasFlag(FileFlagsV4.Salt)) { - datasize -= SALT_SIZE; + datasize -= saltSize; } if (datasize > 0) { @@ -99,34 +302,33 @@ namespace SharpCompress.Common.Rar.Headers break; } - if (FileFlags.HasFlag(FileFlags.SALT)) + if (HasFlag(FileFlagsV4.Salt)) { - Salt = reader.ReadBytes(SALT_SIZE); + R4Salt = reader.ReadBytes(saltSize); } - if (FileFlags.HasFlag(FileFlags.EXTTIME)) + if (HasFlag(FileFlagsV4.ExtTime)) { // verify that the end of the header hasn't been reached before reading the Extended Time. // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. - if (ReadBytes + reader.CurrentReadByteCount <= HeaderSize - 2) + if (RemainingHeaderBytes(reader) >= 2) { ushort extendedFlags = reader.ReadUInt16(); - FileLastModifiedTime = ProcessExtendedTime(extendedFlags, FileLastModifiedTime, reader, 0); - FileCreatedTime = ProcessExtendedTime(extendedFlags, null, reader, 1); - FileLastAccessedTime = ProcessExtendedTime(extendedFlags, null, reader, 2); - FileArchivedTime = ProcessExtendedTime(extendedFlags, null, reader, 3); + FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0); + FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1); + FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2); + FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3); } } } - private long UInt32To64(uint x, uint y) + private static long UInt32To64(uint x, uint y) { long l = x; l <<= 32; return l + y; } - private static DateTime? ProcessExtendedTime(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, - int i) + private static DateTime? ProcessExtendedTimeV4(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, int i) { uint rmode = (uint)extendedFlags >> (3 - i) * 4; if ((rmode & 8) == 0) @@ -154,7 +356,7 @@ namespace SharpCompress.Common.Rar.Headers return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4)); } - private static string ConvertPath(string path, HostOS os) + private static string ConvertPathV4(string path) { #if NO_FILE return path.Replace('\\', '/'); @@ -171,12 +373,71 @@ namespace SharpCompress.Common.Rar.Headers #endif } + public override string ToString() + { + return FileName; + } + + private ushort Flags { get; set; } + + private bool HasFlag(ushort flag) + { + return (Flags & flag) == flag; + } + + internal uint FileCrc + { + get { + if (IsRar5 && !HasFlag(FileFlagsV5.HasCrc32)) { +//!!! rar5: + throw new InvalidOperationException("TODO rar5"); + } + return this.fileCrc; + } + private set { this.fileCrc = value; } + } + + // 0 - storing + // 1 - fastest compression + // 2 - fast compression + // 3 - normal compression + // 4 - good compression + // 5 - best compression + internal byte CompressionMethod { get; private set; } + internal bool IsStored { get { return CompressionMethod == 0; } } + + // eg (see DoUnpack()) + //case 15: // rar 1.5 compression + //case 20: // rar 2.x compression + //case 26: // files larger than 2GB + //case 29: // rar 3.x compression + //case 50: // RAR 5.0 compression algorithm. + internal byte CompressionAlgorithm { get; private set; } + + public bool IsSolid { get; private set; } + + // unused for UnpackV1 implementation (limitation) + internal size_t WindowSize { get; private set; } + + internal byte[] R4Salt { get; private set; } + + private byte HostOs { get; set; } + internal uint FileAttributes { get; private set; } + internal long CompressedSize { get; private set; } + internal long UncompressedSize { get; private set; } + internal string FileName { get; private set; } + internal byte[] SubData { get; private set; } + internal int RecoverySectors { get; private set; } internal long DataStartPosition { get; set; } + public Stream PackedStream { get; set; } - internal HostOS HostOS { get; private set; } + public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SplitAfter) : HasFlag(FileFlagsV4.SplitAfter); - internal uint FileCRC { get; private set; } + public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.Directory : FileFlagsV4.Directory); +//!!! TODO rar5 + public bool IsEncrypted => HasFlag(FileFlagsV4.Password); + internal DateTime? FileLastModifiedTime { get; private set; } internal DateTime? FileCreatedTime { get; private set; } @@ -184,32 +445,5 @@ namespace SharpCompress.Common.Rar.Headers internal DateTime? FileLastAccessedTime { get; private set; } internal DateTime? FileArchivedTime { get; private set; } - - internal byte RarVersion { get; private set; } - - internal byte PackingMethod { get; private set; } - - internal int FileAttributes { get; private set; } - - internal FileFlags FileFlags => (FileFlags)Flags; - - internal long CompressedSize { get; private set; } - - internal long UncompressedSize { get; private set; } - - internal string FileName { get; private set; } - - internal byte[] SubData { get; private set; } - - internal int RecoverySectors { get; private set; } - - internal byte[] Salt { get; private set; } - - public override string ToString() - { - return FileName; - } - - public Stream PackedStream { get; set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index a2dbd550..488b3c5b 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -1,84 +1,149 @@ -using System; - namespace SharpCompress.Common.Rar.Headers { - internal enum HeaderType + internal enum HeaderType : byte { - MarkHeader = 0x72, - ArchiveHeader = 0x73, - FileHeader = 0x74, - CommHeader = 0x75, - AvHeader = 0x76, - SubHeader = 0x77, - ProtectHeader = 0x78, - SignHeader = 0x79, - NewSubHeader = 0x7a, - EndArchiveHeader = 0x7b + Null, + Mark, + Archive, + File, + Service, + Comment, + Av, + Protect, + Sign, + NewSub, + EndArchive, + Crypt } - internal enum HeaderFlags : short + internal static class HeaderCodeV { - LONG_BLOCK = -0x8000 + public const byte Rar4MarkHeader = 0x72; + public const byte Rar4ArchiveHeader = 0x73; + public const byte Rar4FileHeader = 0x74; + public const byte Rar4CommentHeader = 0x75; + public const byte Rar4AvHeader = 0x76; + public const byte Rar4SubHeader = 0x77; + public const byte Rar4ProtectHeader = 0x78; + public const byte Rar4SignHeader = 0x79; + public const byte Rar4NewSubHeader = 0x7a; + public const byte Rar4EndArchiveHeader = 0x7b; + + public const byte Rar5ArchiveHeader = 0x01; + public const byte Rar5FileHeader = 0x02; + public const byte Rar5ServiceHeader = 0x03; + public const byte Rar5ArchiveEncryptionHeader = 0x04; + public const byte Rar5EndArchiveHeader = 0x05; } - [Flags] - internal enum ArchiveFlags + internal static class HeaderFlagsV4 { - VOLUME = 0x0001, - COMMENT = 0x0002, - LOCK = 0x0004, - SOLID = 0x0008, - NEWNUMBERING = 0x0010, - AV = 0x0020, - PROTECT = 0x0040, - PASSWORD = 0x0080, - FIRSTVOLUME = 0x0100, - ENCRYPTVER = 0x0200 + public const ushort HasData = 0x8000; } - internal enum HostOS - { - MSDOS = 0, - OS2 = 1, - Win32 = 2, - Unix = 3, - MacOS = 4, - BeOS = 5 + internal static class EncryptionFlagsV5 + { + // RAR 5.0 archive encryption header specific flags. + public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present. + + public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present. + public const uint FHEXTRA_CRYPT_HASHMAC = 0x02; } - [Flags] - internal enum FileFlags : ushort + internal static class HeaderFlagsV5 { - SPLIT_BEFORE = 0x0001, - SPLIT_AFTER = 0x0002, - PASSWORD = 0x0004, - COMMENT = 0x0008, - SOLID = 0x0010, - - WINDOWMASK = 0x00e0, - WINDOW64 = 0x0000, - WINDOW128 = 0x0020, - WINDOW256 = 0x0040, - WINDOW512 = 0x0060, - WINDOW1024 = 0x0080, - WINDOW2048 = 0x00a0, - WINDOW4096 = 0x00c0, - DIRECTORY = 0x00e0, - - LARGE = 0x0100, - UNICODE = 0x0200, - SALT = 0x0400, - VERSION = 0x0800, - EXTTIME = 0x1000, - EXTFLAGS = 0x2000 + public const ushort HasExtra = 0x0001; + public const ushort HasData = 0x0002; + public const ushort Keep = 0x0004; // block must be kept during an update + public const ushort SplitBefore = 0x0008; + public const ushort SplitAfter = 0x0010; + public const ushort Child = 0x0020; // ??? Block depends on preceding file block. + public const ushort PreserveChild = 0x0040; // ???? Preserve a child block if host block is modified } - [Flags] - internal enum EndArchiveFlags + internal static class ArchiveFlagsV4 { - EARC_NEXT_VOLUME = 0x0001, - EARC_DATACRC = 0x0002, - EARC_REVSPACE = 0x0004, - EARC_VOLNUMBER = 0x0008 + public const ushort Volume = 0x0001; + public const ushort Comment = 0x0002; + public const ushort Lock = 0x0004; + public const ushort Solid = 0x0008; + public const ushort NewNumbering = 0x0010; + public const ushort AV = 0x0020; + public const ushort Protect = 0x0040; + public const ushort Password = 0x0080; + public const ushort FirstVolume = 0x0100; + public const ushort EncryptVer = 0x0200; + } + + internal static class ArchiveFlagsV5 + { + public const ushort Volume = 0x0001; + public const ushort HasVolumeNumber = 0x0002; + public const ushort Solid = 0x0004; + public const ushort Protect = 0x0008; + public const ushort Lock = 0x0010; + } + + internal static class HostOsV4 + { + public const byte MsDos = 0; + public const byte Os2 = 1; + public const byte Win32 = 2; + public const byte Unix = 3; + public const byte MacOs = 4; + public const byte BeOs = 5; + } + + internal static class HostOsV5 + { + public const byte Windows = 0; + public const byte Unix = 1; + } + + internal static class FileFlagsV4 + { + public const ushort SplitBefore = 0x0001; + public const ushort SplitAfter = 0x0002; + public const ushort Password = 0x0004; + public const ushort Comment = 0x0008; + public const ushort Solid = 0x0010; + + public const ushort WindowMask = 0x00e0; + public const ushort Window64 = 0x0000; + public const ushort Window128 = 0x0020; + public const ushort Window256 = 0x0040; + public const ushort Window512 = 0x0060; + public const ushort Window1024 = 0x0080; + public const ushort Window2048 = 0x00a0; + public const ushort Window4096 = 0x00c0; + public const ushort Directory = 0x00e0; + + public const ushort Large = 0x0100; + public const ushort Unicode = 0x0200; + public const ushort Salt = 0x0400; + public const ushort Version = 0x0800; + public const ushort ExtTime = 0x1000; + public const ushort ExtFlags = 0x2000; + } + + internal static class FileFlagsV5 + { + public const ushort Directory = 0x0001; + public const ushort HasModTime = 0x0002; + public const ushort HasCrc32 = 0x0004; + public const ushort UnpackedSizeUnknown = 0x0008; + } + + internal static class EndArchiveFlagsV4 + { + public const ushort NextVolume = 0x0001; + public const ushort DataCrc = 0x0002; + public const ushort RevSpace = 0x0004; + public const ushort VolumeNumber = 0x0008; + } + + internal static class EndArchiveFlagsV5 + { + public const ushort HasNextVolume = 0x0001; } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs new file mode 100644 index 00000000..bbc03593 --- /dev/null +++ b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs @@ -0,0 +1,7 @@ +namespace SharpCompress.Common.Rar.Headers +{ + internal interface IRarHeader + { + HeaderType HeaderType { get; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index f5c61274..c7b94149 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -1,26 +1,98 @@ -using SharpCompress.IO; +using System; +using System.IO; namespace SharpCompress.Common.Rar.Headers { - internal class MarkHeader : RarHeader + internal class MarkHeader : IRarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) - { + private const int MaxSfxSize = 0x80000 - 16; //archive.cpp line 136 + + internal bool OldNumberingFormat { get; private set; } + + public bool IsRar5 { get; } + + private MarkHeader(bool isRar5) + { + IsRar5 = isRar5; } - internal bool IsValid() - { - // Rar old signature: 52 45 7E 5E (not supported) - - // Rar4 signature: 52 61 72 21 1A 07 00 - return HeadCRC == 0x6152 && - HeaderType == HeaderType.MarkHeader && - Flags == 0x1A21 && - HeaderSize == 0x07; - - // Rar5 signature: 52 61 72 21 1A 07 01 00 (not supported yet) + public HeaderType HeaderType { + get { return HeaderType.Mark; } } - internal bool OldFormat { get; private set; } + private static byte GetByte(Stream stream) + { + var b = stream.ReadByte(); + if (b != -1) + { + return (byte)b; + } + throw new EndOfStreamException(); + } + + public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) + { + int maxScanIndex = lookForHeader ? MaxSfxSize : 0; + try + { + int start = -1; + var b = GetByte(stream); start++; + while (start <= maxScanIndex) + { + // Rar old signature: 52 45 7E 5E + // Rar4 signature: 52 61 72 21 1A 07 00 + // Rar5 signature: 52 61 72 21 1A 07 01 00 + if (b == 0x52) + { + b = GetByte(stream); start++; + if (b == 0x61) + { + b = GetByte(stream); start++; + if (b != 0x72) continue; + b = GetByte(stream); start++; + if (b != 0x21) continue; + b = GetByte(stream); start++; + if (b != 0x1a) continue; + b = GetByte(stream); start++; + if (b != 0x07) continue; + + b = GetByte(stream); start++; + if (b == 1) + { + b = GetByte(stream); start++; + if (b != 0) continue; + return new MarkHeader(true); // Rar5 + } + else if (b == 0) + { + return new MarkHeader(false); // Rar4 + } + } + else if (b == 0x45) + { + b = GetByte(stream); start++; + if (b != 0x7e) continue; + b = GetByte(stream); start++; + if (b != 0x5e) continue; + throw new InvalidFormatException("Rar format version pre-4 is unsupported."); + } + } + else + { + b = GetByte(stream); start++; + } + } + } + catch (Exception e) + { + if (!leaveStreamOpen) + { + stream.Dispose(); + } + throw new InvalidFormatException("Error trying to read rar signature.", e); + } + + throw new InvalidFormatException("Rar signature not found"); + } } } diff --git a/src/SharpCompress/Common/Rar/Headers/NewSubHeader.cs b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs similarity index 96% rename from src/SharpCompress/Common/Rar/Headers/NewSubHeader.cs rename to src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs index 6218f123..d4b2a77b 100644 --- a/src/SharpCompress/Common/Rar/Headers/NewSubHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs @@ -47,13 +47,9 @@ namespace SharpCompress.Common.Rar.Headers return true; } - #region IEquatable Members - public bool Equals(NewSubHeaderType other) { return Equals(other.bytes); } - - #endregion } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index e2f78c32..f7f0e8ba 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -5,7 +5,13 @@ namespace SharpCompress.Common.Rar.Headers // ProtectHeader is part of the Recovery Record feature internal class ProtectHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) + public ProtectHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Protect) + { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); + } + + protected override void ReadFinish(MarkingBinaryReader reader) { Version = reader.ReadByte(); RecSectors = reader.ReadUInt16(); @@ -13,7 +19,7 @@ namespace SharpCompress.Common.Rar.Headers Mark = reader.ReadBytes(8); } - internal uint DataSize => AdditionalSize; + internal uint DataSize => checked((uint)AdditionalDataSize); internal byte Version { get; private set; } internal ushort RecSectors { get; private set; } internal uint TotalBlocks { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 30753aee..a3fe6a91 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -1,38 +1,21 @@ using System; using System.IO; using SharpCompress.IO; -using System.Text; namespace SharpCompress.Common.Rar.Headers { - internal class RarHeader + // http://www.forensicswiki.org/w/images/5/5b/RARFileStructure.txt + // https://www.rarlab.com/technote.htm + internal class RarHeader : IRarHeader { - internal const short BaseBlockSize = 7; - internal const short LONG_BLOCK = -0x8000; + private readonly HeaderType headerType; + private readonly bool isRar5; - private void FillBase(RarHeader baseHeader) - { - HeadCRC = baseHeader.HeadCRC; - HeaderType = baseHeader.HeaderType; - Flags = baseHeader.Flags; - HeaderSize = baseHeader.HeaderSize; - AdditionalSize = baseHeader.AdditionalSize; - ReadBytes = baseHeader.ReadBytes; - ArchiveEncoding = baseHeader.ArchiveEncoding; - } - - internal static RarHeader Create(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding) + internal static RarHeader TryReadBase(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) { try { - RarHeader header = new RarHeader(); - - header.ArchiveEncoding = archiveEncoding; - reader.Mark(); - header.ReadStartFromReader(reader); - header.ReadBytes += reader.CurrentReadByteCount; - - return header; + return new RarHeader(reader, isRar5, archiveEncoding); } catch (EndOfStreamException) { @@ -40,82 +23,108 @@ namespace SharpCompress.Common.Rar.Headers } } - private void ReadStartFromReader(RarCrcBinaryReader reader) + private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) { - HeadCRC = reader.ReadUInt16(); - reader.ResetCrc(); - HeaderType = (HeaderType)(reader.ReadByte() & 0xff); - Flags = reader.ReadInt16(); - HeaderSize = reader.ReadInt16(); - if (FlagUtility.HasFlag(Flags, LONG_BLOCK)) + this.headerType = HeaderType.Null; + this.isRar5 = isRar5; + ArchiveEncoding = archiveEncoding; + if (IsRar5) { - AdditionalSize = reader.ReadUInt32(); - } - } + HeaderCrc = reader.ReadUInt32(); + reader.ResetCrc(); + HeaderSize = (int)reader.ReadRarVIntUInt32(3); + reader.Mark(); + HeaderCode = reader.ReadRarVIntByte(); + HeaderFlags = reader.ReadRarVIntUInt16(2); - protected virtual void ReadFromReader(MarkingBinaryReader reader) - { - throw new NotImplementedException(); - } - - internal T PromoteHeader(RarCrcBinaryReader reader) - where T : RarHeader, new() - { - T header = new T(); - header.FillBase(this); - - reader.Mark(); - header.ReadFromReader(reader); - header.ReadBytes += reader.CurrentReadByteCount; - - int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes; - - if (headerSizeDiff > 0) - { - reader.ReadBytes(headerSizeDiff); - } - - VerifyHeaderCrc(reader.GetCrc()); - - return header; - } - - private void VerifyHeaderCrc(ushort crc) - { - if (HeaderType != HeaderType.MarkHeader) - { - if (crc != HeadCRC) + if (HasHeaderFlag(HeaderFlagsV5.HasExtra)) { - throw new InvalidFormatException("rar header crc mismatch"); + ExtraSize = reader.ReadRarVIntUInt32(); + } + if (HasHeaderFlag(HeaderFlagsV5.HasData)) + { + AdditionalDataSize = (long)reader.ReadRarVInt(); + } + } else { + reader.Mark(); + HeaderCrc = reader.ReadUInt16(); + reader.ResetCrc(); + HeaderCode = reader.ReadByte(); + HeaderFlags = reader.ReadUInt16(); + HeaderSize = reader.ReadInt16(); + if (HasHeaderFlag(HeaderFlagsV4.HasData)) + { + AdditionalDataSize = reader.ReadUInt32(); } } } - protected virtual void PostReadingBytes(MarkingBinaryReader reader) - { + protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) { + this.headerType = headerType; + this.isRar5 = header.IsRar5; + HeaderCrc = header.HeaderCrc; + HeaderCode = header.HeaderCode; + HeaderFlags = header.HeaderFlags; + HeaderSize = header.HeaderSize; + ExtraSize = header.ExtraSize; + AdditionalDataSize = header.AdditionalDataSize; + ArchiveEncoding = header.ArchiveEncoding; + ReadFinish(reader); + + int n = RemainingHeaderBytes(reader); + if (n > 0) + { + reader.ReadBytes(n); + } + + VerifyHeaderCrc(reader.GetCrc32()); } - /// - /// This is the number of bytes read when reading the header - /// - protected long ReadBytes { get; private set; } + protected int RemainingHeaderBytes(MarkingBinaryReader reader) { + return checked(HeaderSize - (int)reader.CurrentReadByteCount); + } - protected ushort HeadCRC { get; private set; } + protected virtual void ReadFinish(MarkingBinaryReader reader) + { + throw new NotImplementedException(); + } - internal HeaderType HeaderType { get; private set; } + private void VerifyHeaderCrc(uint crc32) + { + var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; + if (!b) + { + throw new InvalidFormatException("rar header crc mismatch"); + } + } + + public HeaderType HeaderType { get { return this.headerType; } } + + protected bool IsRar5 { get { return this.isRar5; } } + + protected uint HeaderCrc { get; } + + internal byte HeaderCode { get; } + + protected ushort HeaderFlags { get; } + + protected bool HasHeaderFlag(ushort flag) + { + return (HeaderFlags & flag) == flag; + } + + protected int HeaderSize { get; } + + internal ArchiveEncoding ArchiveEncoding { get; } /// - /// Untyped flags. These should be typed when Promoting to another header + /// Extra header size. /// - protected short Flags { get; private set; } - - protected short HeaderSize { get; private set; } - - internal ArchiveEncoding ArchiveEncoding { get; private set; } + protected uint ExtraSize { get; } /// - /// This additional size of the header could be file data + /// Size of additional data (eg file contents) /// - protected uint AdditionalSize { get; private set; } + protected long AdditionalDataSize { get; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index f3b67960..0f562314 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -8,7 +8,7 @@ namespace SharpCompress.Common.Rar.Headers { internal class RarHeaderFactory { - private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 + private bool isRar5; internal RarHeaderFactory(StreamingMode mode, ReaderOptions options) { @@ -20,138 +20,67 @@ namespace SharpCompress.Common.Rar.Headers internal StreamingMode StreamingMode { get; } internal bool IsEncrypted { get; private set; } - internal IEnumerable ReadHeaders(Stream stream) + internal IEnumerable ReadHeaders(Stream stream) { - if (Options.LookForHeader) - { - stream = CheckSFX(stream); - } + var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader); + this.isRar5 = markHeader.IsRar5; + yield return markHeader; RarHeader header; - while ((header = ReadNextHeader(stream)) != null) + while ((header = TryReadNextHeader(stream)) != null) { yield return header; - if (header.HeaderType == HeaderType.EndArchiveHeader) + if (header.HeaderType == HeaderType.EndArchive) { - yield break; // the end? + // End of archive marker. RAR does not read anything after this header letting to use third + // party tools to add extra information such as a digital signature to archive. + yield break; } } } - private Stream CheckSFX(Stream stream) + private RarHeader TryReadNextHeader(Stream stream) { - RewindableStream rewindableStream = GetRewindableStream(stream); - stream = rewindableStream; - BinaryReader reader = new BinaryReader(rewindableStream); - try + RarCrcBinaryReader reader; + if (!IsEncrypted) { - int count = 0; - while (true) - { - byte firstByte = reader.ReadByte(); - if (firstByte == 0x52) - { - MemoryStream buffer = new MemoryStream(); - byte[] nextThreeBytes = reader.ReadBytes(3); - if ((nextThreeBytes[0] == 0x45) - && (nextThreeBytes[1] == 0x7E) - && (nextThreeBytes[2] == 0x5E)) - { - //old format and isvalid - buffer.WriteByte(0x52); - buffer.Write(nextThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - break; - } - byte[] secondThreeBytes = reader.ReadBytes(3); - if ((nextThreeBytes[0] == 0x61) - && (nextThreeBytes[1] == 0x72) - && (nextThreeBytes[2] == 0x21) - && (secondThreeBytes[0] == 0x1A) - && (secondThreeBytes[1] == 0x07) - && (secondThreeBytes[2] == 0x00)) - { - //new format and isvalid - buffer.WriteByte(0x52); - buffer.Write(nextThreeBytes, 0, 3); - buffer.Write(secondThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - break; - } - buffer.Write(nextThreeBytes, 0, 3); - buffer.Write(secondThreeBytes, 0, 3); - rewindableStream.Rewind(buffer); - } - if (count > MAX_SFX_SIZE) - { - break; - } - } - } - catch (Exception e) + reader = new RarCrcBinaryReader(stream); + } + else { -#if NET35 - reader.Close(); -#else - reader.Dispose(); -#endif - throw new InvalidFormatException("Error trying to read rar signature.", e); - } - return stream; - } - - private RewindableStream GetRewindableStream(Stream stream) - { - RewindableStream rewindableStream = stream as RewindableStream; - if (rewindableStream == null) - { - rewindableStream = new RewindableStream(stream); - } - return rewindableStream; - } - - private RarHeader ReadNextHeader(Stream stream) - { #if !NO_CRYPTO - var reader = new RarCryptoBinaryReader(stream, Options.Password); - - if (IsEncrypted) - { if (Options.Password == null) { throw new CryptographicException("Encrypted Rar archive has no password specified."); } - reader.SkipQueue(); - byte[] salt = reader.ReadBytes(8); - reader.InitializeAes(salt); - } + reader = new RarCryptoBinaryReader(stream, Options.Password); #else - var reader = new RarCrcBinaryReader(stream); - + throw new CryptographicException("Rar encryption unsupported on this platform"); #endif + } - RarHeader header = RarHeader.Create(reader, Options.ArchiveEncoding); + var header = RarHeader.TryReadBase(reader, this.isRar5, Options.ArchiveEncoding); if (header == null) { return null; } - switch (header.HeaderType) + switch (header.HeaderCode) { - case HeaderType.ArchiveHeader: + case HeaderCodeV.Rar5ArchiveHeader: + case HeaderCodeV.Rar4ArchiveHeader: { - var ah = header.PromoteHeader(reader); - IsEncrypted = ah.HasPassword; + var ah = new ArchiveHeader(header, reader); + if (ah.IsEncrypted == true) + { + //!!! rar5 we don't know yet + IsEncrypted = true; + } return ah; } - case HeaderType.MarkHeader: - { - return header.PromoteHeader(reader); - } - case HeaderType.ProtectHeader: + case HeaderCodeV.Rar4ProtectHeader: { - ProtectHeader ph = header.PromoteHeader(reader); - + var ph = new ProtectHeader(header, reader); // skip the recovery record data, we do not use it. switch (StreamingMode) { @@ -174,33 +103,24 @@ namespace SharpCompress.Common.Rar.Headers return ph; } - case HeaderType.NewSubHeader: + case HeaderCodeV.Rar5ServiceHeader: { - FileHeader fh = header.PromoteHeader(reader); - switch (StreamingMode) - { - case StreamingMode.Seekable: - { - fh.DataStartPosition = reader.BaseStream.Position; - reader.BaseStream.Position += fh.CompressedSize; - } - break; - case StreamingMode.Streaming: - { - //skip the data because it's useless? - reader.BaseStream.Skip(fh.CompressedSize); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } - } + var fh = new FileHeader(header, reader, HeaderType.Service); + SkipData(fh, reader); return fh; } - case HeaderType.FileHeader: + + case HeaderCodeV.Rar4NewSubHeader: { - FileHeader fh = header.PromoteHeader(reader); + var fh = new FileHeader(header, reader, HeaderType.NewSub); + SkipData(fh, reader); + return fh; + } + + case HeaderCodeV.Rar5FileHeader: + case HeaderCodeV.Rar4FileHeader: + { + var fh = new FileHeader(header, reader, HeaderType.File); switch (StreamingMode) { case StreamingMode.Seekable: @@ -212,16 +132,16 @@ namespace SharpCompress.Common.Rar.Headers case StreamingMode.Streaming: { var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); - if (fh.Salt == null) + if (fh.R4Salt == null) { fh.PackedStream = ms; } else { #if !NO_CRYPTO - fh.PackedStream = new RarCryptoWrapper(ms, Options.Password, fh.Salt); + fh.PackedStream = new RarCryptoWrapper(ms, Options.Password, fh.R4Salt); #else - throw new NotSupportedException("RarCrypto not supported"); + throw new NotSupportedException("RarCrypto not supported"); #endif } } @@ -233,15 +153,40 @@ namespace SharpCompress.Common.Rar.Headers } return fh; } - case HeaderType.EndArchiveHeader: + case HeaderCodeV.Rar5EndArchiveHeader: + case HeaderCodeV.Rar4EndArchiveHeader: { - return header.PromoteHeader(reader); + return new EndArchiveHeader(header, reader); } + case HeaderCodeV.Rar5ArchiveEncryptionHeader: + { + var ch = new ArchiveCryptHeader(header, reader); + IsEncrypted = true; + return ch; + } default: { - throw new InvalidFormatException("Invalid Rar Header: " + header.HeaderType); + throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode); } } } + + private void SkipData(FileHeader fh, RarCrcBinaryReader reader) { + switch (StreamingMode) { + case StreamingMode.Seekable: { + fh.DataStartPosition = reader.BaseStream.Position; + reader.BaseStream.Position += fh.CompressedSize; + } + break; + case StreamingMode.Streaming: { + //skip the data because it's useless? + reader.BaseStream.Skip(fh.CompressedSize); + } + break; + default: { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs index 708241d8..58b3baef 100644 --- a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs @@ -2,22 +2,25 @@ namespace SharpCompress.Common.Rar.Headers { - internal class AVHeader : RarHeader + internal class SignHeader : RarHeader { - protected override void ReadFromReader(MarkingBinaryReader reader) - { - UnpackVersion = reader.ReadByte(); - Method = reader.ReadByte(); - AVVersion = reader.ReadByte(); - AVInfoCRC = reader.ReadInt32(); + protected SignHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Sign) + { + if (IsRar5) throw new InvalidFormatException("unexpected rar5 record"); } - internal int AVInfoCRC { get; private set; } + protected override void ReadFinish(MarkingBinaryReader reader) + { + CreationTime = reader.ReadInt32(); + ArcNameSize = reader.ReadInt16(); + UserNameSize = reader.ReadInt16(); + } - internal byte UnpackVersion { get; private set; } + internal int CreationTime { get; private set; } - internal byte Method { get; private set; } + internal short ArcNameSize { get; private set; } - internal byte AVVersion { get; private set; } + internal short UserNameSize { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index 6df617b5..6ab2923a 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -3,16 +3,17 @@ using SharpCompress.Compressors.Rar; using SharpCompress.IO; namespace SharpCompress.Common.Rar { - internal class RarCrcBinaryReader : MarkingBinaryReader { + internal class RarCrcBinaryReader : MarkingBinaryReader + { private uint currentCrc; public RarCrcBinaryReader(Stream stream) : base(stream) { } - public ushort GetCrc() + public uint GetCrc32() { - return (ushort)~currentCrc; + return ~currentCrc; } public void ResetCrc() @@ -30,6 +31,13 @@ namespace SharpCompress.Common.Rar { return base.ReadBytes(count); } + public override byte ReadByte() + { + var b = base.ReadByte(); + currentCrc = RarCRC.CheckCrc(currentCrc, b); + return b; + } + public override byte[] ReadBytes(int count) { var result = base.ReadBytes(count); diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index 9635375b..9ea3cb2c 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -2,7 +2,6 @@ #if !NO_CRYPTO using System.Collections.Generic; using System.IO; -using SharpCompress.IO; namespace SharpCompress.Common.Rar { @@ -18,6 +17,10 @@ namespace SharpCompress.Common.Rar : base(stream) { this.password = password; + // coderb: not sure why this was being done at this logical point + //SkipQueue(); + byte[] salt = ReadBytes(8); + InitializeAes(salt); } // track read count ourselves rather than using the underlying stream since we buffer @@ -47,6 +50,15 @@ namespace SharpCompress.Common.Rar rijndael = RarRijndael.InitializeFrom(password, salt); } + public override byte ReadByte() { + if (UseEncryption) + { + return ReadAndDecryptBytes(1)[0]; + } + this.readCount++; + return base.ReadByte(); + } + public override byte[] ReadBytes(int count) { if (UseEncryption) @@ -68,13 +80,11 @@ namespace SharpCompress.Common.Rar for (int i = 0; i < alignedSize / 16; i++) { //long ax = System.currentTimeMillis(); - byte[] cipherText = base.ReadBytesNoCrc(16); + byte[] cipherText = ReadBytesNoCrc(16); var readBytes = rijndael.ProcessBlock(cipherText); foreach (var readByte in readBytes) data.Enqueue(readByte); - } - } var decryptedBytes = new byte[count]; diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index cddfca3e..85be0bb3 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -7,10 +7,15 @@ namespace SharpCompress.Common.Rar { internal abstract FileHeader FileHeader { get; } + /// + /// As the V2017 port isn't complete, add this check to use the legacy Rar code. + /// + internal bool IsRarV3 => FileHeader.CompressionAlgorithm == 29 || FileHeader.CompressionAlgorithm == 36; + /// /// The File's 32 bit CRC Hash /// - public override long Crc => FileHeader.FileCRC; + public override long Crc => FileHeader.FileCrc; /// /// The path of the file internal to the Rar Archive. @@ -40,14 +45,14 @@ namespace SharpCompress.Common.Rar /// /// Entry is password protected and encrypted and cannot be extracted. /// - public override bool IsEncrypted => FileHeader.FileFlags.HasFlag(FileFlags.PASSWORD); + public override bool IsEncrypted => FileHeader.IsEncrypted; /// /// Entry is password protected and encrypted and cannot be extracted. /// - public override bool IsDirectory => FileHeader.FileFlags.HasFlag(FileFlags.DIRECTORY); + public override bool IsDirectory => FileHeader.IsDirectory; - public override bool IsSplit => FileHeader.FileFlags.HasFlag(FileFlags.SPLIT_AFTER); + public override bool IsSplitAfter => FileHeader.IsSplitAfter; public override string ToString() { diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 89fee688..31bc7636 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -21,42 +21,41 @@ namespace SharpCompress.Common.Rar headerFactory = new RarHeaderFactory(mode, options); } - internal StreamingMode Mode => headerFactory.StreamingMode; + internal ArchiveHeader ArchiveHeader { get; private set; } + + internal StreamingMode Mode => this.headerFactory.StreamingMode; internal abstract IEnumerable ReadFileParts(); - internal abstract RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader); + internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader); internal IEnumerable GetVolumeFileParts() { - MarkHeader previousMarkHeader = null; - foreach (RarHeader header in headerFactory.ReadHeaders(Stream)) + MarkHeader lastMarkHeader = null; + foreach (var header in this.headerFactory.ReadHeaders(Stream)) { switch (header.HeaderType) { - case HeaderType.ArchiveHeader: + case HeaderType.Mark: + { + lastMarkHeader = header as MarkHeader; + } + break; + case HeaderType.Archive: { ArchiveHeader = header as ArchiveHeader; } break; - case HeaderType.MarkHeader: + case HeaderType.File: { - previousMarkHeader = header as MarkHeader; - } - break; - case HeaderType.FileHeader: - { - FileHeader fh = header as FileHeader; - RarFilePart fp = CreateFilePart(fh, previousMarkHeader); - yield return fp; + var fh = header as FileHeader; + yield return CreateFilePart(lastMarkHeader, fh); } break; } } } - internal ArchiveHeader ArchiveHeader { get; private set; } - private void EnsureArchiveHeaderLoaded() { if (ArchiveHeader == null) @@ -66,7 +65,7 @@ namespace SharpCompress.Common.Rar throw new InvalidOperationException("ArchiveHeader should never been null in a streaming read."); } - //we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream + // we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream GetVolumeFileParts().First(); Stream.Position = 0; } @@ -81,7 +80,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.FIRSTVOLUME); + return ArchiveHeader.IsFirstVolume; } } @@ -93,7 +92,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME); + return ArchiveHeader.IsVolume; } } @@ -106,7 +105,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.SOLID); + return ArchiveHeader.IsSolid; } } } diff --git a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs index dd23d629..584bfb95 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs @@ -34,7 +34,7 @@ namespace SharpCompress.Common.SevenZip public override bool IsDirectory => FilePart.Header.IsDir; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; public override int? Attrib => (int)FilePart.Header.Attrib; diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index 5208bce1..6ff6e19f 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -39,7 +39,7 @@ namespace SharpCompress.Common.Tar public override bool IsDirectory => filePart.Header.EntryType == EntryType.Directory; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index 111c8a20..2db83216 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -76,7 +76,7 @@ namespace SharpCompress.Common.Zip public override bool IsDirectory => filePart.Header.IsDirectory; - public override bool IsSplit => false; + public override bool IsSplitAfter => false; internal override IEnumerable Parts => filePart.AsEnumerable(); } diff --git a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs index 8a754333..7c16cc38 100644 --- a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs +++ b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs @@ -231,7 +231,7 @@ namespace SharpCompress.Compressors.PPMd.H Utility.Fill(charMask, 0); } - internal bool decodeInit(Unpack unpackRead, int escChar) + internal bool decodeInit(IRarUnpack unpackRead, int escChar) { int MaxOrder = unpackRead.Char & 0xff; bool reset = ((MaxOrder & 0x20) != 0); diff --git a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs index 4c04828a..3b5177fc 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs @@ -13,10 +13,10 @@ namespace SharpCompress.Compressors.PPMd.H // uint low, code, range; private long low, code, range; - private readonly Unpack unpackRead; + private readonly IRarUnpack unpackRead; private readonly Stream stream; - internal RangeCoder(Unpack unpackRead) + internal RangeCoder(IRarUnpack unpackRead) { this.unpackRead = unpackRead; Init(); diff --git a/src/SharpCompress/Compressors/Rar/Decode/Compress.cs b/src/SharpCompress/Compressors/Rar/Decode/Compress.cs deleted file mode 100644 index 3689daf0..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/Compress.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal class Compress - { - public const int CODEBUFSIZE = 0x4000; - public const int MAXWINSIZE = 0x400000; - - //UPGRADE_NOTE: Final was removed from the declaration of 'MAXWINMASK '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int MAXWINMASK = (MAXWINSIZE - 1); - - public const int LOW_DIST_REP_COUNT = 16; - - public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC = 60; - public const int LDC = 17; - public const int RC = 28; - - //UPGRADE_NOTE: Final was removed from the declaration of 'HUFF_TABLE_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int HUFF_TABLE_SIZE = (NC + DC + RC + LDC); - public const int BC = 20; - - public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC20 = 48; - public const int RC20 = 28; - public const int BC20 = 19; - public const int MC20 = 257; - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs deleted file mode 100644 index 24e2b0b5..00000000 --- a/src/SharpCompress/Compressors/Rar/Decode/FilterType.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SharpCompress.Compressors.Rar.Decode -{ - internal enum FilterType - { - FILTER_NONE, - FILTER_PPM, - FILTER_E8, - FILTER_E8E9, - FILTER_UPCASETOLOW, - FILTER_AUDIO, - FILTER_RGB, - FILTER_DELTA, - FILTER_ITANIUM, - FILTER_E8E9V2 - } -} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs new file mode 100644 index 00000000..ecd70a47 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -0,0 +1,18 @@ +using System.IO; +using SharpCompress.Common.Rar.Headers; + +namespace SharpCompress.Compressors.Rar +{ + interface IRarUnpack + { + void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); + void DoUnpack(); + + // eg u/i pause/resume button + bool Suspended { get; set; } + + long DestSize { get; } + int Char { get; } + int PpmEscChar { get; set; } + } +} diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index f89ea0f4..8bdfd64b 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar; -using SharpCompress.Common.Rar.Headers; namespace SharpCompress.Compressors.Rar { @@ -51,7 +50,7 @@ namespace SharpCompress.Compressors.Rar currentPartTotalReadBytes = 0; - CurrentCrc = filePartEnumerator.Current.FileHeader.FileCRC; + CurrentCrc = filePartEnumerator.Current.FileHeader.FileCrc; streamListener.FireFilePartExtractionBegin(filePartEnumerator.Current.FilePartName, filePartEnumerator.Current.FileHeader.CompressedSize, @@ -82,9 +81,9 @@ namespace SharpCompress.Compressors.Rar currentCount -= read; totalRead += read; if (((maxPosition - currentPosition) == 0) - && filePartEnumerator.Current.FileHeader.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)) + && filePartEnumerator.Current.FileHeader.IsSplitAfter) { - if (filePartEnumerator.Current.FileHeader.Salt != null) + if (filePartEnumerator.Current.FileHeader.R4Salt != null) { throw new InvalidFormatException("Sharpcompress currently does not support multi-volume decryption."); } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 3a46cd77..df7d85ff 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -2,12 +2,15 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar { - internal class RarCrcStream : RarStream { +namespace SharpCompress.Compressors.Rar +{ + internal class RarCrcStream : RarStream + { private readonly MultiVolumeReadOnlyStream readStream; private uint currentCrc; - public RarCrcStream(Unpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) : base(unpack, fileHeader, readStream) + public RarCrcStream(IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) + : base(unpack, fileHeader, readStream) { this.readStream = readStream; ResetCrc(); @@ -23,19 +26,19 @@ namespace SharpCompress.Compressors.Rar { currentCrc = 0xffffffff; } - public override int Read(byte[] buffer, int offset, int count) { var result = base.Read(buffer, offset, count); - if (result != 0) + if (result != 0) { currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result); - } - else if (GetCrc() != readStream.CurrentCrc && count != 0) + } + else if (GetCrc() != this.readStream.CurrentCrc && count != 0) { // NOTE: we use the last FileHeader in a multipart volume to check CRC throw new InvalidFormatException("file crc mismatch"); } + return result; } } diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index c36f261c..08240859 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -6,7 +6,7 @@ namespace SharpCompress.Compressors.Rar { internal class RarStream : Stream { - private readonly Unpack unpack; + private readonly IRarUnpack unpack; private readonly FileHeader fileHeader; private readonly Stream readStream; @@ -22,25 +22,23 @@ namespace SharpCompress.Compressors.Rar private int outTotal; private bool isDisposed; - public RarStream(Unpack unpack, FileHeader fileHeader, Stream readStream) + public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) { this.unpack = unpack; this.fileHeader = fileHeader; this.readStream = readStream; - fetch = true; - unpack.doUnpack(fileHeader, readStream, this); - fetch = false; + this.fetch = true; + unpack.DoUnpack(fileHeader, readStream, this); + this.fetch = false; } protected override void Dispose(bool disposing) { - if (isDisposed) - { - return; + if (!this.isDisposed) { + this.isDisposed = true; + base.Dispose(disposing); + this.readStream.Dispose(); } - isDisposed = true; - base.Dispose(disposing); - readStream.Dispose(); } public override bool CanRead => true; @@ -76,7 +74,7 @@ namespace SharpCompress.Compressors.Rar outOffset = offset; outCount = count; fetch = true; - unpack.doUnpack(); + unpack.DoUnpack(); fetch = false; } return outTotal; diff --git a/src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs similarity index 92% rename from src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs index cb70b1fd..c2510617 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/AudioVariables.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs @@ -1,4 +1,4 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class AudioVariables { diff --git a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs similarity index 51% rename from src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs index ba13c9b8..a8d499b5 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/BitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class BitDecode : Decode { internal BitDecode() - : base(new int[Compress.BC]) + : base(new int[PackDef.BC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/CodeType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs similarity index 80% rename from src/SharpCompress/Compressors/Rar/Decode/CodeType.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs index acbd4aac..e9703ff1 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/CodeType.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs @@ -1,4 +1,4 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal enum CodeType { diff --git a/src/SharpCompress/Compressors/Rar/Decode/Decode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs similarity index 93% rename from src/SharpCompress/Compressors/Rar/Decode/Decode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs index dc5dacee..ceed74de 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/Decode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs @@ -1,4 +1,4 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class Decode { diff --git a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs similarity index 52% rename from src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs index 0c26c391..1a0b666b 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/DistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class DistDecode : Decode { internal DistDecode() - : base(new int[Compress.DC]) + : base(new int[PackDef.DC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs new file mode 100644 index 00000000..6d915e44 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs @@ -0,0 +1,10 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal enum FilterType : byte + { + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, + FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs similarity index 51% rename from src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs index 6084d0fe..8316c261 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/LitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class LitDecode : Decode { internal LitDecode() - : base(new int[Compress.NC]) + : base(new int[PackDef.NC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs similarity index 53% rename from src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs index 266bf9e1..a2095bd4 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/LowDistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class LowDistDecode : Decode { internal LowDistDecode() - : base(new int[Compress.LDC]) + : base(new int[PackDef.LDC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs similarity index 51% rename from src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs index d42a83eb..e6df223c 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/MultDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class MultDecode : Decode { internal MultDecode() - : base(new int[Compress.MC20]) + : base(new int[PackDef.MC20]) { } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs new file mode 100644 index 00000000..87f1977e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs @@ -0,0 +1,48 @@ +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +{ + internal static class PackDef + { + // 20171217 NOTE: these contants are gone from unrar src code + // seems to be more dynamic + public const int MAXWINSIZE = 0x400000; + public const int MAXWINMASK = MAXWINSIZE - 1; + + public const uint MAX_LZ_MATCH = 0x1001; + public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + public const int LOW_DIST_REP_COUNT = 16; + + public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC = 60; + public const int LDC = 17; + public const int RC = 28; +// 20171217: NOTE: these constants seem to have been updated in the unrar src code +// at some unknown point. updating causes decompression failure, not sure why. +// public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ +// public const int DC = 64; +// public const int LDC = 16; +// public const int RC = 44; + public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; + public const int BC = 20; + + public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC30 = 60; + public const uint LDC30 = 17; + public const uint RC30 = 28; + public const uint BC30 = 20; + public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + + public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC20 = 48; + public const int RC20 = 28; + public const int BC20 = 19; + public const int MC20 = 257; + + // Largest alphabet size among all values listed above. + public const uint LARGEST_TABLE_SIZE = 306; + + //public enum { + // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + //} + } +} \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs similarity index 51% rename from src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs index 3a59587f..24d72d16 100644 --- a/src/SharpCompress/Compressors/Rar/Decode/RepDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs @@ -1,9 +1,9 @@ -namespace SharpCompress.Compressors.Rar.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode { internal class RepDecode : Decode { internal RepDecode() - : base(new int[Compress.RC]) + : base(new int[PackDef.RC]) { } } diff --git a/src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs similarity index 62% rename from src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs index 2550c47a..b3f79fdc 100644 --- a/src/SharpCompress/Compressors/Rar/PPM/BlockTypes.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs @@ -1,4 +1,4 @@ -namespace SharpCompress.Compressors.Rar.PPM +namespace SharpCompress.Compressors.Rar.UnpackV1.PPM { internal enum BlockTypes { diff --git a/src/SharpCompress/Compressors/Rar/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs similarity index 83% rename from src/SharpCompress/Compressors/Rar/Unpack.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 197d6e51..90c09c6d 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -4,31 +4,23 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; using SharpCompress.Compressors.PPMd.H; -using SharpCompress.Compressors.Rar.Decode; -using SharpCompress.Compressors.Rar.PPM; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.PPM; using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar.UnpackV1 { - internal sealed class Unpack : Unpack20 + internal sealed partial class Unpack : BitInput, IRarUnpack { - public bool FileExtracted { // Duplicate method - // private boolean ReadEndOfBlock() throws IOException, RarException - // { - // int BitField = getbits(); - // boolean NewTable, NewFile = false; - // if ((BitField & 0x8000) != 0) { - // NewTable = true; - // addbits(1); - // } else { - // NewFile = true; - // NewTable = (BitField & 0x4000) != 0; - // addbits(2); - // } - // tablesRead = !NewTable; - // return !(NewFile || NewTable && !readTables()); - // } - get; private set; } + private readonly BitInput Inp; + + public Unpack() { + // to ease in porting Unpack50.cs + this.Inp = this; + } + + + public bool FileExtracted { get; private set; } public long DestSize { @@ -40,7 +32,10 @@ namespace SharpCompress.Compressors.Rar } } - public bool Suspended { set => suspended = value; } + public bool Suspended { + get => this.suspended; + set => this.suspended = value; + } public int Char { @@ -56,33 +51,25 @@ namespace SharpCompress.Compressors.Rar public int PpmEscChar { get; set; } - //UPGRADE_NOTE: Final was removed from the declaration of 'ppm '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" private readonly ModelPPM ppm = new ModelPPM(); private readonly RarVM rarVM = new RarVM(); - /* Filters code, one entry per filter */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // Filters code, one entry per filter private readonly List filters = new List(); - /* Filters stack, several entrances of same filter are possible */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // Filters stack, several entrances of same filter are possible private readonly List prgStack = new List(); - /* - * lengths of preceding blocks, one length per filter. Used to reduce size - * required to write block length if lengths are repeating - */ - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + // lengths of preceding blocks, one length per filter. Used to reduce size + // required to write block length if lengths are repeating private readonly List oldFilterLengths = new List(); private int lastFilter; private bool tablesRead; - //UPGRADE_NOTE: The initialization of 'unpOldTable' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - - private readonly byte[] unpOldTable = new byte[Compress.HUFF_TABLE_SIZE]; + private readonly byte[] unpOldTable = new byte[PackDef.HUFF_TABLE_SIZE]; private BlockTypes unpBlockType; @@ -96,25 +83,15 @@ namespace SharpCompress.Compressors.Rar private int lowDistRepCount; - public static int[] DBitLengthCounts = {4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12}; + private static readonly int[] DBitLengthCounts = {4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12}; private FileHeader fileHeader; - public Unpack() - { - window = null; - - //externalWindow = false; - suspended = false; - unpAllBuf = false; - unpSomeRead = false; - } - - public void init(byte[] window) + private void Init(byte[] window) { if (window == null) { - this.window = new byte[Compress.MAXWINSIZE]; + this.window = new byte[PackDef.MAXWINSIZE]; } else { @@ -123,55 +100,56 @@ namespace SharpCompress.Compressors.Rar //externalWindow = true; } inAddr = 0; - unpInitData(false); + UnpInitData(false); } - public void doUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) { - destUnpSize = fileHeader.UncompressedSize; + this.destUnpSize = fileHeader.UncompressedSize; this.fileHeader = fileHeader; - this.writeStream = writeStream; this.readStream = readStream; - bool solid = FlagUtility.HasFlag(fileHeader.FileFlags, FileFlags.SOLID); - if (!solid) + this.writeStream = writeStream; + if (!fileHeader.IsSolid) { - init(null); + Init(null); } - suspended = false; - doUnpack(); + this.suspended = false; + DoUnpack(); } - public void doUnpack() + public void DoUnpack() { - bool solid = FlagUtility.HasFlag(fileHeader.FileFlags, FileFlags.SOLID); - if (fileHeader.PackingMethod == 0x30) + if (fileHeader.CompressionMethod == 0) { - unstoreFile(); + UnstoreFile(); return; } - switch (fileHeader.RarVersion) + switch (fileHeader.CompressionAlgorithm) { case 15: // rar 1.5 compression - unpack15(solid); + unpack15(fileHeader.IsSolid); break; - case 20: - - // rar 2.x compression + case 20: // rar 2.x compression case 26: // files larger than 2GB - unpack20(solid); + unpack20(fileHeader.IsSolid); break; - case 29: - - // rar 3.x compression + case 29: // rar 3.x compression case 36: // alternative hash - unpack29(solid); + Unpack29(fileHeader.IsSolid); break; + + case 50: // rar 5.x compression + Unpack5(fileHeader.IsSolid); + break; + + default: + throw new InvalidFormatException("unknown rar compression version " + this.fileHeader.CompressionAlgorithm); } } - private void unstoreFile() + private void UnstoreFile() { byte[] buffer = new byte[0x10000]; while (true) @@ -194,10 +172,10 @@ namespace SharpCompress.Compressors.Rar } } - private void unpack29(bool solid) + private void Unpack29(bool solid) { - int[] DDecode = new int[Compress.DC]; - byte[] DBits = new byte[Compress.DC]; + int[] DDecode = new int[PackDef.DC]; + byte[] DBits = new byte[PackDef.DC]; int Bits; @@ -219,12 +197,12 @@ namespace SharpCompress.Compressors.Rar if (!suspended) { - unpInitData(solid); + UnpInitData(solid); if (!unpReadBuf()) { return; } - if ((!solid || !tablesRead) && !readTables()) + if ((!solid || !tablesRead) && !ReadTables()) { return; } @@ -237,7 +215,7 @@ namespace SharpCompress.Compressors.Rar while (true) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readBorder) { @@ -249,7 +227,7 @@ namespace SharpCompress.Compressors.Rar // System.out.println(((wrPtr - unpPtr) & // Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr); - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 260 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) { UnpWriteBuf(); if (destUnpSize <= 0) @@ -275,7 +253,7 @@ namespace SharpCompress.Compressors.Rar int NextCh = ppm.decodeChar(); if (NextCh == 0) { - if (!readTables()) + if (!ReadTables()) { break; } @@ -287,7 +265,7 @@ namespace SharpCompress.Compressors.Rar } if (NextCh == 3) { - if (!readVMCodePPM()) + if (!ReadVMCodePPM()) { break; } @@ -322,7 +300,7 @@ namespace SharpCompress.Compressors.Rar { break; } - copyString(Length + 32, Distance + 2); + CopyString(Length + 32, Distance + 2); continue; } if (NextCh == 5) @@ -332,7 +310,7 @@ namespace SharpCompress.Compressors.Rar { break; } - copyString(Length + 4, 1); + CopyString(Length + 4, 1); continue; } } @@ -376,7 +354,7 @@ namespace SharpCompress.Compressors.Rar int LowDist = this.decodeNumber(LDD); if (LowDist == 16) { - lowDistRepCount = Compress.LOW_DIST_REP_COUNT - 1; + lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; Distance += prevLowDist; } else @@ -402,15 +380,15 @@ namespace SharpCompress.Compressors.Rar } } - insertOldDist(Distance); - insertLastMatch(Length, Distance); + InsertOldDist(Distance); + InsertLastMatch(Length, Distance); - copyString(Length, Distance); + CopyString(Length, Distance); continue; } if (Number == 256) { - if (!readEndOfBlock()) + if (!ReadEndOfBlock()) { break; } @@ -418,7 +396,7 @@ namespace SharpCompress.Compressors.Rar } if (Number == 257) { - if (!readVMCode()) + if (!ReadVMCode()) { break; } @@ -428,7 +406,7 @@ namespace SharpCompress.Compressors.Rar { if (lastLength != 0) { - copyString(lastLength, lastDist); + CopyString(lastLength, lastDist); } continue; } @@ -449,8 +427,8 @@ namespace SharpCompress.Compressors.Rar Length += Utility.URShift(GetBits(), (16 - Bits)); AddBits(Bits); } - insertLastMatch(Length, Distance); - copyString(Length, Distance); + InsertLastMatch(Length, Distance); + CopyString(Length, Distance); continue; } if (Number < 272) @@ -461,9 +439,9 @@ namespace SharpCompress.Compressors.Rar Distance += Utility.URShift(GetBits(), (16 - Bits)); AddBits(Bits); } - insertOldDist(Distance); - insertLastMatch(2, Distance); - copyString(2, Distance); + InsertOldDist(Distance); + InsertLastMatch(2, Distance); + CopyString(2, Distance); } } UnpWriteBuf(); @@ -472,7 +450,7 @@ namespace SharpCompress.Compressors.Rar private void UnpWriteBuf() { int WrittenBorder = wrPtr; - int WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + int WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; for (int I = 0; I < prgStack.Count; I++) { UnpackFilter flt = prgStack[I]; @@ -487,17 +465,17 @@ namespace SharpCompress.Compressors.Rar } int BlockStart = flt.BlockStart; // ->BlockStart; int BlockLength = flt.BlockLength; // ->BlockLength; - if (((BlockStart - WrittenBorder) & Compress.MAXWINMASK) < WriteSize) + if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) { if (WrittenBorder != BlockStart) { UnpWriteArea(WrittenBorder, BlockStart); WrittenBorder = BlockStart; - WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; } if (BlockLength <= WriteSize) { - int BlockEnd = (BlockStart + BlockLength) & Compress.MAXWINMASK; + int BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; if (BlockStart < BlockEnd || BlockEnd == 0) { // VM.SetMemory(0,Window+BlockStart,BlockLength); @@ -505,7 +483,7 @@ namespace SharpCompress.Compressors.Rar } else { - int FirstPartLength = Compress.MAXWINSIZE - BlockStart; + int FirstPartLength = PackDef.MAXWINSIZE - BlockStart; // VM.SetMemory(0,Window+BlockStart,FirstPartLength); rarVM.setMemory(0, window, BlockStart, FirstPartLength); @@ -639,7 +617,7 @@ namespace SharpCompress.Compressors.Rar writtenFileSize += FilteredDataSize; destUnpSize -= FilteredDataSize; WrittenBorder = BlockEnd; - WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; } else { @@ -669,7 +647,7 @@ namespace SharpCompress.Compressors.Rar } if (endPtr < startPtr) { - UnpWriteData(window, startPtr, -startPtr & Compress.MAXWINMASK); + UnpWriteData(window, startPtr, -startPtr & PackDef.MAXWINMASK); UnpWriteData(window, 0, endPtr); unpAllBuf = true; } @@ -696,7 +674,12 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= size; } - private void insertOldDist(int distance) + private void InsertOldDist(uint distance) { + // TODO uint + InsertOldDist((int)distance); + } + + private void InsertOldDist(int distance) { oldDist[3] = oldDist[2]; oldDist[2] = oldDist[1]; @@ -704,20 +687,25 @@ namespace SharpCompress.Compressors.Rar oldDist[0] = distance; } - private void insertLastMatch(int length, int distance) + private void InsertLastMatch(int length, int distance) { lastDist = distance; lastLength = length; } - private void copyString(int length, int distance) + private void CopyString(uint length, uint distance) { + // TODO uint + CopyString((int)length, (int)distance) ; + } + + private void CopyString(int length, int distance) { // System.out.println("copyString(" + length + ", " + distance + ")"); int destPtr = unpPtr - distance; // System.out.println(unpPtr+":"+distance); - if (destPtr >= 0 && destPtr < Compress.MAXWINSIZE - 260 && unpPtr < Compress.MAXWINSIZE - 260) + if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260) { window[unpPtr++] = window[destPtr++]; @@ -730,13 +718,13 @@ namespace SharpCompress.Compressors.Rar { while (length-- != 0) { - window[unpPtr] = window[destPtr++ & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } } - protected internal override void unpInitData(bool solid) + private void UnpInitData(bool solid) { if (!solid) { @@ -752,8 +740,9 @@ namespace SharpCompress.Compressors.Rar unpPtr = 0; wrPtr = 0; PpmEscChar = 2; +WriteBorder=Math.Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; - initFilters(); + InitFilters(); } InitBitInput(); ppmError = false; @@ -763,7 +752,37 @@ namespace SharpCompress.Compressors.Rar unpInitData20(solid); } - private void initFilters() +//void Unpack::UnpInitData(bool Solid) +//{ +// if (!Solid) +// { +// memset(OldDist,0,sizeof(OldDist)); +// OldDistPtr=0; +// LastDist=LastLength=0; +//// memset(Window,0,MaxWinSize); +// memset(&BlockTables,0,sizeof(BlockTables)); +// UnpPtr=WrPtr=0; +// WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; +// } +// // Filters never share several solid files, so we can safely reset them +// // even in solid archive. +// InitFilters(); +// +// Inp.InitBitInput(); +// WrittenFileSize=0; +// ReadTop=0; +// ReadBorder=0; +// +// memset(&BlockHeader,0,sizeof(BlockHeader)); +// BlockHeader.BlockSize=-1; // '-1' means not defined yet. +//#ifndef SFX_MODULE +// UnpInitData20(Solid); +//#endif +// UnpInitData30(Solid); +// UnpInitData50(Solid); +//} + + private void InitFilters() { oldFilterLengths.Clear(); lastFilter = 0; @@ -773,7 +792,7 @@ namespace SharpCompress.Compressors.Rar prgStack.Clear(); } - private bool readEndOfBlock() + private bool ReadEndOfBlock() { int BitField = GetBits(); bool NewTable, NewFile = false; @@ -785,18 +804,18 @@ namespace SharpCompress.Compressors.Rar else { NewFile = true; - NewTable = (BitField & 0x4000) != 0 ? true : false; + NewTable = (BitField & 0x4000) != 0; AddBits(2); } tablesRead = !NewTable; - return !(NewFile || NewTable && !readTables()); + return !(NewFile || NewTable && !ReadTables()); } - private bool readTables() + private bool ReadTables() { - byte[] bitLength = new byte[Compress.BC]; + byte[] bitLength = new byte[PackDef.BC]; - byte[] table = new byte[Compress.HUFF_TABLE_SIZE]; + byte[] table = new byte[PackDef.HUFF_TABLE_SIZE]; if (inAddr > readTop - 25) { if (!unpReadBuf()) @@ -822,7 +841,7 @@ namespace SharpCompress.Compressors.Rar } AddBits(2); - for (int i = 0; i < Compress.BC; i++) + for (int i = 0; i < PackDef.BC; i++) { int length = (Utility.URShift(GetBits(), 12)) & 0xFF; AddBits(4); @@ -850,9 +869,9 @@ namespace SharpCompress.Compressors.Rar } } - UnpackUtility.makeDecodeTables(bitLength, 0, BD, Compress.BC); + UnpackUtility.makeDecodeTables(bitLength, 0, BD, PackDef.BC); - int TableSize = Compress.HUFF_TABLE_SIZE; + int TableSize = PackDef.HUFF_TABLE_SIZE; for (int i = 0; i < TableSize;) { @@ -912,10 +931,10 @@ namespace SharpCompress.Compressors.Rar { return (false); } - UnpackUtility.makeDecodeTables(table, 0, LD, Compress.NC); - UnpackUtility.makeDecodeTables(table, Compress.NC, DD, Compress.DC); - UnpackUtility.makeDecodeTables(table, Compress.NC + Compress.DC, LDD, Compress.LDC); - UnpackUtility.makeDecodeTables(table, Compress.NC + Compress.DC + Compress.LDC, RD, Compress.RC); + UnpackUtility.makeDecodeTables(table, 0, LD, PackDef.NC); + UnpackUtility.makeDecodeTables(table, PackDef.NC, DD, PackDef.DC); + UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC, LDD, PackDef.LDC); + UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC + PackDef.LDC, RD, PackDef.RC); // memcpy(unpOldTable,table,sizeof(unpOldTable)); @@ -923,7 +942,7 @@ namespace SharpCompress.Compressors.Rar return (true); } - private bool readVMCode() + private bool ReadVMCode() { int FirstByte = GetBits() >> 8; AddBits(8); @@ -939,7 +958,6 @@ namespace SharpCompress.Compressors.Rar AddBits(16); } - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" List vmCode = new List(); for (int I = 0; I < Length; I++) { @@ -950,10 +968,10 @@ namespace SharpCompress.Compressors.Rar vmCode.Add((byte)(GetBits() >> 8)); AddBits(8); } - return (addVMCode(FirstByte, vmCode, Length)); + return (AddVMCode(FirstByte, vmCode, Length)); } - private bool readVMCodePPM() + private bool ReadVMCodePPM() { int FirstByte = ppm.decodeChar(); if (FirstByte == -1) @@ -985,7 +1003,6 @@ namespace SharpCompress.Compressors.Rar Length = B1 * 256 + B2; } - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" List vmCode = new List(); for (int I = 0; I < Length; I++) { @@ -996,10 +1013,10 @@ namespace SharpCompress.Compressors.Rar } vmCode.Add((byte)Ch); // VMCode[I]=Ch; } - return (addVMCode(FirstByte, vmCode, Length)); + return (AddVMCode(FirstByte, vmCode, Length)); } - private bool addVMCode(int firstByte, List vmCode, int length) + private bool AddVMCode(int firstByte, List vmCode, int length) { BitInput Inp = new BitInput(); Inp.InitBitInput(); @@ -1017,7 +1034,7 @@ namespace SharpCompress.Compressors.Rar FiltPos = RarVM.ReadData(Inp); if (FiltPos == 0) { - initFilters(); + InitFilters(); } else { @@ -1075,7 +1092,7 @@ namespace SharpCompress.Compressors.Rar { BlockStart += 258; } - StackFilter.BlockStart = ((BlockStart + unpPtr) & Compress.MAXWINMASK); + StackFilter.BlockStart = ((BlockStart + unpPtr) & PackDef.MAXWINMASK); if ((firstByte & 0x20) != 0) { StackFilter.BlockLength = RarVM.ReadData(Inp); @@ -1084,7 +1101,7 @@ namespace SharpCompress.Compressors.Rar { StackFilter.BlockLength = FiltPos < oldFilterLengths.Count ? oldFilterLengths[FiltPos] : 0; } - StackFilter.NextWindow = (wrPtr != unpPtr) && ((wrPtr - unpPtr) & Compress.MAXWINMASK) <= BlockStart; + StackFilter.NextWindow = (wrPtr != unpPtr) && ((wrPtr - unpPtr) & PackDef.MAXWINMASK) <= BlockStart; // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x // BlockStart=%08x",UnpPtr,WrPtr,BlockStart); @@ -1238,7 +1255,7 @@ namespace SharpCompress.Compressors.Rar } } - public void cleanUp() + private void CleanUp() { if (ppm != null) { diff --git a/src/SharpCompress/Compressors/Rar/Unpack15.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs similarity index 89% rename from src/SharpCompress/Compressors/Rar/Unpack15.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs index c7c1c877..6c0e09f3 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs @@ -3,79 +3,61 @@ * Original author: Edmund Wagner * Creation date: 21.06.2007 * -* Source: $HeadURL$ -* Last changed: $LastChangedDate$ -* * the unrar licence applies to all junrar source and binary distributions * you are not allowed to use this source to re-create the RAR compression algorithm -* -* Here some html entities which can be used for escaping javadoc tags: -* "&": "&" or "&" -* "<": "<" or "<" -* ">": ">" or ">" -* "@": "@" */ using System; using System.IO; -using SharpCompress.Compressors.Rar.Decode; -using SharpCompress.Compressors.Rar.VM; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar.UnpackV1 { - /// DOCUMENT ME - /// - /// - /// $LastChangedBy$ - /// - /// $LastChangedRevision$ - /// - internal abstract class Unpack15 : BitInput + internal partial class Unpack { - protected internal int readBorder; + private int readBorder; - protected internal bool suspended; + private bool suspended; - protected internal bool unpAllBuf; + internal bool unpAllBuf; - //protected internal ComprDataIO unpIO; + //private ComprDataIO unpIO; + private Stream readStream; + private Stream writeStream; - protected Stream readStream; - protected Stream writeStream; + internal bool unpSomeRead; - protected internal bool unpSomeRead; + private int readTop; - protected internal int readTop; + private long destUnpSize; - protected internal long destUnpSize; + private byte[] window; - protected internal byte[] window; + private readonly int[] oldDist = new int[4]; - protected internal int[] oldDist = new int[4]; + private int unpPtr, wrPtr; - protected internal int unpPtr, wrPtr; + private int oldDistPtr; - protected internal int oldDistPtr; - - protected internal int[] ChSet = new int[256], + private readonly int[] ChSet = new int[256], ChSetA = new int[256], ChSetB = new int[256], ChSetC = new int[256]; - protected internal int[] Place = new int[256], + private readonly int[] Place = new int[256], PlaceA = new int[256], PlaceB = new int[256], PlaceC = new int[256]; - protected internal int[] NToPl = new int[256], NToPlB = new int[256], NToPlC = new int[256]; + private readonly int[] NToPl = new int[256], NToPlB = new int[256], NToPlC = new int[256]; - protected internal int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; + private int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; - protected internal int Buf60, NumHuf, StMode, LCount, FlagsCnt; + private int Buf60, NumHuf, StMode, LCount, FlagsCnt; - protected internal int Nhfb, Nlzb, MaxDist3; + private int Nhfb, Nlzb, MaxDist3; - protected internal int lastDist, lastLength; + private int lastDist, lastLength; private const int STARTL1 = 2; @@ -126,25 +108,23 @@ namespace SharpCompress.Compressors.Rar private static readonly int[] PosHf4 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0}; - internal static int[] ShortLen1 = {1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0}; + private static readonly int[] ShortLen1 = {1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0}; - internal static int[] ShortXor1 = + private static readonly int[] ShortXor1 = { 0, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff, 0xc0, 0x80, 0x90, 0x98 , 0x9c, 0xb0 }; - internal static int[] ShortLen2 = {2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0}; + private static readonly int[] ShortLen2 = {2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0}; - internal static int[] ShortXor2 = + private static readonly int[] ShortXor2 = { 0, 0x40, 0x60, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xc0, 0x80, 0x90, 0x98 , 0x9c, 0xb0 }; - protected internal abstract void unpInitData(bool solid); - - protected void unpack15(bool solid) + private void unpack15(bool solid) { if (suspended) { @@ -152,7 +132,7 @@ namespace SharpCompress.Compressors.Rar } else { - unpInitData(solid); + UnpInitData(solid); oldUnpInitData(solid); unpReadBuf(); if (!solid) @@ -174,13 +154,13 @@ namespace SharpCompress.Compressors.Rar while (destUnpSize >= 0) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readTop - 30 && !unpReadBuf()) { break; } - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 270 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { oldUnpWriteBuf(); if (suspended) @@ -242,7 +222,7 @@ namespace SharpCompress.Compressors.Rar oldUnpWriteBuf(); } - protected bool unpReadBuf() + private bool unpReadBuf() { int dataSize = readTop - inAddr; if (dataSize < 0) @@ -702,8 +682,8 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= Length; while ((Length--) != 0) { - window[unpPtr] = window[(unpPtr - Distance) & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[(unpPtr - Distance) & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } @@ -718,7 +698,7 @@ namespace SharpCompress.Compressors.Rar return ((Utility.URShift((Num - (I != 0 ? DecTab[I - 1] : 0)), (16 - StartPos))) + PosTab[StartPos]); } - protected void oldUnpWriteBuf() + private void oldUnpWriteBuf() { if (unpPtr != wrPtr) { @@ -726,7 +706,7 @@ namespace SharpCompress.Compressors.Rar } if (unpPtr < wrPtr) { - writeStream.Write(window, wrPtr, -wrPtr & Compress.MAXWINMASK); + writeStream.Write(window, wrPtr, -wrPtr & PackDef.MAXWINMASK); writeStream.Write(window, 0, unpPtr); unpAllBuf = true; } diff --git a/src/SharpCompress/Compressors/Rar/Unpack20.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs similarity index 78% rename from src/SharpCompress/Compressors/Rar/Unpack20.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs index 608442c1..db8e8cd7 100644 --- a/src/SharpCompress/Compressors/Rar/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs @@ -3,78 +3,48 @@ * Original author: Edmund Wagner * Creation date: 21.06.2007 * -* Source: $HeadURL$ -* Last changed: $LastChangedDate$ -* * the unrar licence applies to all junrar source and binary distributions * you are not allowed to use this source to re-create the RAR compression algorithm -* -* Here some html entities which can be used for escaping javadoc tags: -* "&": "&" or "&" -* "<": "<" or "<" -* ">": ">" or ">" -* "@": "@" */ using System; -using SharpCompress.Compressors.Rar.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar.UnpackV1 { - /// DOCUMENT ME - /// - /// - /// $LastChangedBy$ - /// - /// $LastChangedRevision$ - /// - internal abstract class Unpack20 : Unpack15 + internal partial class Unpack { - public Unpack20() - { - InitBlock(); - } + private readonly MultDecode[] MD = new MultDecode[4]; - private void InitBlock() - { - UnpOldTable20 = new byte[Compress.MC20 * 4]; - } + private readonly byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; - protected internal MultDecode[] MD = new MultDecode[4]; - - //UPGRADE_NOTE: The initialization of 'UnpOldTable20' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - protected internal byte[] UnpOldTable20; - - protected internal int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; + private int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; private readonly AudioVariables[] AudV = new AudioVariables[4]; - protected internal LitDecode LD = new LitDecode(); + private readonly LitDecode LD = new LitDecode(); - protected internal DistDecode DD = new DistDecode(); + private readonly DistDecode DD = new DistDecode(); - protected internal LowDistDecode LDD = new LowDistDecode(); + private readonly LowDistDecode LDD = new LowDistDecode(); - protected internal RepDecode RD = new RepDecode(); + private readonly RepDecode RD = new RepDecode(); - protected internal BitDecode BD = new BitDecode(); + private readonly BitDecode BD = new BitDecode(); - //UPGRADE_NOTE: Final was removed from the declaration of 'LDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] LDecode = + private static readonly int[] LDecode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'LBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly byte[] LBits = + private static readonly byte[] LBits = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'DDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] DDecode = + private static readonly int[] DDecode = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, @@ -83,21 +53,18 @@ namespace SharpCompress.Compressors.Rar 917504, 983040 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'DBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] DBits = + private static readonly int[] DBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16 , 16, 16, 16, 16, 16, 16, 16 }; - //UPGRADE_NOTE: Final was removed from the declaration of 'SDDecode'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192}; + private static readonly int[] SDDecode = {0, 4, 8, 16, 32, 64, 128, 192}; - //UPGRADE_NOTE: Final was removed from the declaration of 'SDBits'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6}; + private static readonly int[] SDBits = {2, 2, 3, 4, 5, 6, 6, 6}; - internal void unpack20(bool solid) + private void unpack20(bool solid) { int Bits; @@ -107,7 +74,7 @@ namespace SharpCompress.Compressors.Rar } else { - unpInitData(solid); + UnpInitData(solid); if (!unpReadBuf()) { return; @@ -124,7 +91,7 @@ namespace SharpCompress.Compressors.Rar while (destUnpSize >= 0) { - unpPtr &= Compress.MAXWINMASK; + unpPtr &= PackDef.MAXWINMASK; if (inAddr > readTop - 30) { @@ -133,7 +100,7 @@ namespace SharpCompress.Compressors.Rar break; } } - if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 270 && wrPtr != unpPtr) + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { oldUnpWriteBuf(); if (suspended) @@ -258,7 +225,7 @@ namespace SharpCompress.Compressors.Rar destUnpSize -= Length; int DestPtr = unpPtr - Distance; - if (DestPtr < Compress.MAXWINSIZE - 300 && unpPtr < Compress.MAXWINSIZE - 300) + if (DestPtr < PackDef.MAXWINSIZE - 300 && unpPtr < PackDef.MAXWINSIZE - 300) { window[unpPtr++] = window[DestPtr++]; window[unpPtr++] = window[DestPtr++]; @@ -272,16 +239,16 @@ namespace SharpCompress.Compressors.Rar { while ((Length--) != 0) { - window[unpPtr] = window[DestPtr++ & Compress.MAXWINMASK]; - unpPtr = (unpPtr + 1) & Compress.MAXWINMASK; + window[unpPtr] = window[DestPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; } } } private bool ReadTables20() { - byte[] BitLength = new byte[Compress.BC20]; - byte[] Table = new byte[Compress.MC20 * 4]; + byte[] BitLength = new byte[PackDef.BC20]; + byte[] Table = new byte[PackDef.MC20 * 4]; int TableSize, N, I; if (inAddr > readTop - 25) { @@ -308,18 +275,18 @@ namespace SharpCompress.Compressors.Rar UnpCurChannel = 0; } AddBits(2); - TableSize = Compress.MC20 * UnpChannels; + TableSize = PackDef.MC20 * UnpChannels; } else { - TableSize = Compress.NC20 + Compress.DC20 + Compress.RC20; + TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; } - for (I = 0; I < Compress.BC20; I++) + for (I = 0; I < PackDef.BC20; I++) { BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); AddBits(4); } - UnpackUtility.makeDecodeTables(BitLength, 0, BD, Compress.BC20); + UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); I = 0; while (I < TableSize) { @@ -372,14 +339,14 @@ namespace SharpCompress.Compressors.Rar { for (I = 0; I < UnpChannels; I++) { - UnpackUtility.makeDecodeTables(Table, I * Compress.MC20, MD[I], Compress.MC20); + UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); } } else { - UnpackUtility.makeDecodeTables(Table, 0, LD, Compress.NC20); - UnpackUtility.makeDecodeTables(Table, Compress.NC20, DD, Compress.DC20); - UnpackUtility.makeDecodeTables(Table, Compress.NC20 + Compress.DC20, RD, Compress.RC20); + UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); } // memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); @@ -390,7 +357,7 @@ namespace SharpCompress.Compressors.Rar return (true); } - protected void unpInitData20(bool Solid) + private void unpInitData20(bool Solid) { if (!Solid) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs new file mode 100644 index 00000000..01d3604f --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs @@ -0,0 +1,847 @@ +#if true +using System; +using System.Collections.Generic; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; +using SharpCompress.Compressors.Rar.VM; + +using size_t=System.UInt32; +using UnpackBlockHeader = SharpCompress.Compressors.Rar.UnpackV1; + +namespace SharpCompress.Compressors.Rar.UnpackV1 +{ + internal partial class Unpack + { + + // Maximum allowed number of compressed bits processed in quick mode. + private const int MAX_QUICK_DECODE_BITS = 10; + + // Maximum number of filters per entire data block. Must be at least + // twice more than MAX_PACK_FILTERS to store filters from two data blocks. + private const int MAX_UNPACK_FILTERS = 8192; + + // Maximum number of filters per entire data block for RAR3 unpack. + // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters + // from two data blocks. + private const int MAX3_UNPACK_FILTERS = 8192; + + // Limit maximum number of channels in RAR3 delta filter to some reasonable + // value to prevent too slow processing of corrupt archives with invalid + // channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. + // No need to provide it for RAR5, which uses only 5 bits to store channels. + private const int MAX3_UNPACK_CHANNELS = 1024; + + // Maximum size of single filter block. We restrict it to limit memory + // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. + private const int MAX_FILTER_BLOCK_SIZE = 0x400000; + + // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, + // so we keep number of buffered filter in unpacker reasonable. + private const int UNPACK_MAX_WRITE = 0x400000; + + // Decode compressed bit fields to alphabet numbers. +// struct DecodeTable +// { +// // Real size of DecodeNum table. +// public uint MaxNum; +// +// // Left aligned start and upper limit codes defining code space +// // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of +// // range for bit length and DecodeLen[BitLength] defines next code +// // after the end of range or in other words the upper limit code +// // for specified bit length. +// //uint DecodeLen[16]; +// public uint [] DecodeLen = new uint[16]; +// +// // Every item of this array contains the sum of all preceding items. +// // So it contains the start position in code list for every bit length. +// public uint DecodePos[16]; +// +// // Number of compressed bits processed in quick mode. +// // Must not exceed MAX_QUICK_DECODE_BITS. +// public uint QuickBits; +// +// // Translates compressed bits (up to QuickBits length) +// // to bit length in quick mode. +// public byte QuickLen[1< Filters { get { return this.filters; } } + +// TODO: make sure these aren't already somewhere else +public int BlockSize; +public int BlockBitSize; +public int BlockStart; +public int HeaderSize; +public bool LastBlockInFile; +public bool TablePresent; + + public void Unpack5(bool Solid) { + FileExtracted=true; + + if (!Suspended) + { + UnpInitData(Solid); + if (!UnpReadBuf()) + return; + + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if (!ReadBlockHeader() || + !ReadTables() || !TablesRead5) + return; + } + + while (true) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr>=ReadBorder) + { + bool FileDone=false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || + Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && + Inp.InBit>=BlockHeader.BlockBitSize) + { + if (BlockHeader.LastBlockInFile) + { + FileDone=true; + break; + } + if (!ReadBlockHeader() || !ReadTables()) + return; + } + if (FileDone || !UnpReadBuf()) + break; + } + + if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) + return; + if (Suspended) + { + FileExtracted=false; + return; + } + } + + //uint MainSlot=DecodeNumber(Inp,LD); + uint MainSlot= this.DecodeNumber(LD); + if (MainSlot<256) + { +// if (Fragmented) +// FragWindow[UnpPtr++]=(byte)MainSlot; +// else + Window[UnpPtr++]=(byte)MainSlot; + continue; + } + if (MainSlot>=262) + { + uint Length=SlotToLength(MainSlot-262); + + //uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); + int DBits; + uint Distance=1,DistSlot=this.DecodeNumber(DD); + if (DistSlot<4) + { + DBits=0; + Distance+=DistSlot; + } + else + { + //DBits=DistSlot/2 - 1; + DBits=(int)(DistSlot/2 - 1); + Distance+=(2 | (DistSlot & 1)) << DBits; + } + + if (DBits>0) + { + if (DBits>=4) + { + if (DBits>4) + { + Distance+=((Inp.getbits()>>(36-DBits))<<4); + Inp.AddBits(DBits-4); + } + //uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); + uint LowDist=this.DecodeNumber(LDD); + Distance+=LowDist; + } + else + { + Distance+=Inp.getbits()>>(32-DBits); + Inp.AddBits(DBits); + } + } + + if (Distance>0x100) + { + Length++; + if (Distance>0x2000) + { + Length++; + if (Distance>0x40000) + Length++; + } + } + + InsertOldDist(Distance); + LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else + CopyString(Length,Distance); + continue; + } + if (MainSlot==256) + { + UnpackFilter Filter = new UnpackFilter(); + if (!ReadFilter(Filter) || !AddFilter(Filter)) + break; + continue; + } + if (MainSlot==257) + { + if (LastLength!=0) +// if (Fragmented) +// FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); +// else + //CopyString(LastLength,OldDist[0]); + CopyString(LastLength,OldDistN(0)); + continue; + } + if (MainSlot<262) + { + //uint DistNum=MainSlot-258; + int DistNum=(int)(MainSlot-258); + //uint Distance=OldDist[DistNum]; + uint Distance=OldDistN(DistNum); + //for (uint I=DistNum;I>0;I--) + for (int I=DistNum;I>0;I--) + //OldDistN[I]=OldDistN(I-1); + SetOldDistN(I, OldDistN(I-1)); + //OldDistN[0]=Distance; + SetOldDistN(0, Distance); + + uint LengthSlot=this.DecodeNumber(RD); + uint Length=SlotToLength(LengthSlot); + LastLength=Length; +// if (Fragmented) +// FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); +// else + CopyString(Length,Distance); + continue; + } + } + UnpWriteBuf(); + } + + + uint ReadFilterData() + { + uint ByteCount=(Inp.fgetbits()>>14)+1; + Inp.AddBits(2); + + uint Data=0; + //for (uint I=0;I>8)<<(I*8); + Inp.AddBits(8); + } + return Data; + } + + + bool ReadFilter(UnpackFilter Filter) + { + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16) + if (!UnpReadBuf()) + return false; + + Filter.uBlockStart=ReadFilterData(); + Filter.uBlockLength=ReadFilterData(); + if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) + Filter.BlockLength=0; + + //Filter.Type=Inp.fgetbits()>>13; + Filter.Type=(byte)(this.Inp.fgetbits()>>13); + Inp.faddbits(3); + + if (Filter.Type==(byte)FilterType.FILTER_DELTA) + { + //Filter.Channels=(Inp.fgetbits()>>11)+1; + Filter.Channels=(byte)((this.Inp.fgetbits()>>11)+1); + Inp.faddbits(5); + } + + return true; + } + + + bool AddFilter(UnpackFilter Filter) + { + if (Filters.Count>=MAX_UNPACK_FILTERS) + { + UnpWriteBuf(); // Write data, apply and flush filters. + if (Filters.Count>=MAX_UNPACK_FILTERS) + InitFilters(); // Still too many filters, prevent excessive memory use. + } + + // If distance to filter start is that large that due to circular dictionary + // mode now it points to old not written yet data, then we set 'NextWindow' + // flag and process this filter only after processing that older data. + Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; + + Filter.uBlockStart=(uint)((Filter.BlockStart+UnpPtr)&MaxWinMask); + Filters.Add(Filter); + return true; + } + + + bool UnpReadBuf() + { + int DataSize=ReadTop-Inp.InAddr; // Data left to process. + if (DataSize<0) + return false; + BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart; + if (Inp.InAddr>BitInput.MAX_SIZE/2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize>0) + //memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); + // TODO: perf + //Buffer.BlockCopy(InBuf, inAddr, InBuf, 0, DataSize); + + Inp.InAddr=0; + ReadTop=DataSize; + } + else + DataSize=ReadTop; + int ReadCode=0; + if (BitInput.MAX_SIZE!=DataSize) + //ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); + ReadCode = readStream.Read(InBuf, DataSize, BitInput.MAX_SIZE-DataSize); + if (ReadCode>0) // Can be also -1. + ReadTop+=ReadCode; + ReadBorder=ReadTop-30; + BlockHeader.BlockStart=Inp.InAddr; + if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet. + { + // We may need to quit from main extraction loop and read new block header + // and trees earlier than data in input buffer ends. + ReadBorder=Math.Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1); + } + return ReadCode!=-1; + } + +//? +// void UnpWriteBuf() +// { +// size_t WrittenBorder=WrPtr; +// size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; +// size_t WriteSizeLeft=FullWriteSize; +// bool NotAllFiltersProcessed=false; +// for (size_t I=0;IType==FilterType.FILTER_NONE) +// continue; +// if (flt->NextWindow) +// { +// // Here we skip filters which have block start in current data range +// // due to address wrap around in circular dictionary, but actually +// // belong to next dictionary block. If such filter start position +// // is included to current write range, then we reset 'NextWindow' flag. +// // In fact we can reset it even without such check, because current +// // implementation seems to guarantee 'NextWindow' flag reset after +// // buffer writing for all existing filters. But let's keep this check +// // just in case. Compressor guarantees that distance between +// // filter block start and filter storing position cannot exceed +// // the dictionary size. So if we covered the filter block start with +// // our write here, we can safely assume that filter is applicable +// // to next block on no further wrap arounds is possible. +// if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) +// flt->NextWindow=false; +// continue; +// } +// uint BlockStart=flt->BlockStart; +// uint BlockLength=flt->BlockLength; +// if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. +// { +// uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; +// +// FilterSrcMemory.Alloc(BlockLength); +// byte *Mem=&FilterSrcMemory[0]; +// if (BlockStartUnpWrite(OutMem,BlockLength); +// +// UnpSomeRead=true; +// WrittenFileSize+=BlockLength; +// WrittenBorder=BlockEnd; +// WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; +// } +// } +// else +// { +// // Current filter intersects the window write border, so we adjust +// // the window border to process this filter next time, not now. +// WrPtr=WrittenBorder; +// +// // Since Filter start position can only increase, we quit processing +// // all following filters for this data block and reset 'NextWindow' +// // flag for them. +// for (size_t J=I;JType!=FilterType.FILTER_NONE) +// flt->NextWindow=false; +// } +// +// // Do not write data left after current filter now. +// NotAllFiltersProcessed=true; +// break; +// } +// } +// } +// +// // Remove processed filters from queue. +// size_t EmptyCount=0; +// for (size_t I=0;I0) +// Filters[I-EmptyCount]=Filters[I]; +// if (Filters[I].Type==FilterType.FILTER_NONE) +// EmptyCount++; +// } +// if (EmptyCount>0) +// Filters.Alloc(Filters.Size()-EmptyCount); +// +// if (!NotAllFiltersProcessed) // Only if all filters are processed. +// { +// // Write data left after last filter. +// UnpWriteArea(WrittenBorder,UnpPtr); +// WrPtr=UnpPtr; +// } +// +// // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE +// // instead of potentially huge MaxWinSize blocks. It also allows us +// // to keep the size of Filters array reasonable. +// WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; +// +// // Choose the nearest among WriteBorder and WrPtr actual written border. +// // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. +// if (WriteBorder==UnpPtr || +// WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) +// WriteBorder=WrPtr; +// } + + +// unused + //x byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) +// byte[] ApplyFilter(byte []Data, uint DataSize, UnpackFilter Flt) +// { +// //x byte *SrcData=Data; +// byte []SrcData=Data; +// switch(Flt.Type) +// { +// case (byte)FilterType.FILTER_E8: +// case (byte)FilterType.FILTER_E8E9: +// { +// uint FileOffset=(uint)WrittenFileSize; +// +// const uint FileSize=0x1000000; +// byte CmpByte2=Flt.Type==(byte)FilterType.FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; +// // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" +// // to avoid overflow for DataSize<4. +// for (uint CurPos=0;CurPos+4=0 +// RawPut4(Addr+FileSize,Data); +// } +// else +// if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); +// D[2]=(byte)(Offset>>16); +// } +// } +// } +// return SrcData; +// case (byte)FilterType.FILTER_DELTA: +// { +// // Unlike RAR3, we do not need to reject excessive channel +// // values here, since RAR5 uses only 5 bits to store channel. +// uint Channels=Flt->Channels,SrcPos=0; +// +// FilterDstMemory.Alloc(DataSize); +// byte *DstData=&FilterDstMemory[0]; +// +// // Bytes from same channels are grouped to continual data blocks, +// // so we need to place them back to their interleaving positions. +// for (uint CurChannel=0;CurChannel0) +//// { +//// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); +//// UnpWriteData(&FragWindow[StartPtr],BlockSize); +//// SizeToWrite-=BlockSize; +//// StartPtr=(StartPtr+BlockSize) & MaxWinMask; +//// } +//// } +//// else +// if (EndPtr=DestUnpSize) +// return; +// size_t WriteSize=Size; +// long LeftToWrite=DestUnpSize-WrittenFileSize; +// if ((long)WriteSize>LeftToWrite) +// WriteSize=(size_t)LeftToWrite; +// UnpIO->UnpWrite(Data,WriteSize); +// WrittenFileSize+=Size; +// } + + + void UnpInitData50(bool Solid) + { + if (!Solid) + TablesRead5=false; + } + + + bool ReadBlockHeader() + { + Header.HeaderSize=0; + + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) + if (!UnpReadBuf()) + return false; + //Inp.faddbits((8-Inp.InBit)&7); + Inp.faddbits((uint)((8-this.Inp.InBit)&7)); + + byte BlockFlags=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + //uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. + uint ByteCount=(uint)(((BlockFlags>>3)&3)+1); // Block size byte count. + + if (ByteCount==4) + return false; + + //Header.HeaderSize=2+ByteCount; + Header.HeaderSize=(int)(2+ByteCount); + + Header.BlockBitSize=(BlockFlags&7)+1; + + byte SavedCheckSum=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + + int BlockSize=0; + //for (uint I=0;I>8)<<(I*8); + BlockSize+=(int)(Inp.fgetbits()>>8)<<(I*8); + Inp.AddBits(8); + } + + Header.BlockSize=BlockSize; + byte CheckSum=(byte)(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); + if (CheckSum!=SavedCheckSum) + return false; + + Header.BlockStart=Inp.InAddr; + ReadBorder=Math.Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); + + Header.LastBlockInFile=(BlockFlags & 0x40)!=0; + Header.TablePresent=(BlockFlags & 0x80)!=0; + return true; + } + +//? +// bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) +// { +// if (!Header.TablePresent) +// return true; +// +// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) +// if (!UnpReadBuf()) +// return false; +// +// byte BitLength[BC]; +// for (uint I=0;I> 12); +// Inp.faddbits(4); +// if (Length==15) +// { +// uint ZeroCount=(byte)(Inp.fgetbits() >> 12); +// Inp.faddbits(4); +// if (ZeroCount==0) +// BitLength[I]=15; +// else +// { +// ZeroCount+=2; +// while (ZeroCount-- > 0 && IReadTop-5) +// if (!UnpReadBuf()) +// return false; +// uint Number=DecodeNumber(Inp,&Tables.BD); +// if (Number<16) +// { +// Table[I]=Number; +// I++; +// } +// else +// if (Number<18) +// { +// uint N; +// if (Number==16) +// { +// N=(Inp.fgetbits() >> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// if (I==0) +// { +// // We cannot have "repeat previous" code at the first position. +// // Multiple such codes would shift Inp position without changing I, +// // which can lead to reading beyond of Inp boundary in mutithreading +// // mode, where Inp.ExternalBuffer disables bounds check and we just +// // reserve a lot of buffer space to not need such check normally. +// return false; +// } +// else +// while (N-- > 0 && I> 13)+3; +// Inp.faddbits(3); +// } +// else +// { +// N=(Inp.fgetbits() >> 9)+11; +// Inp.faddbits(7); +// } +// while (N-- > 0 && IReadTop) +// return false; +// MakeDecodeTables(&Table[0],&Tables.LD,NC); +// MakeDecodeTables(&Table[NC],&Tables.DD,DC); +// MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); +// MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); +// return true; +// } + +//? +// void InitFilters() +// { +// Filters.SoftReset(); +// } + + } +} +#endif \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackFilter.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs similarity index 65% rename from src/SharpCompress/Compressors/Rar/UnpackFilter.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs index d199a7e6..66d5d887 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackFilter.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs @@ -1,14 +1,21 @@ using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar.UnpackV1 { internal class UnpackFilter { + public byte Type; + public byte Channels; + internal UnpackFilter() { Program = new VMPreparedProgram(); } + // TODO uint + internal uint uBlockStart { get { return (uint)BlockStart; } set { BlockStart = (int)value; } } + internal uint uBlockLength { get { return (uint)BlockLength; } set { BlockLength = (int)value; } } + internal int BlockStart { get; set; } internal int BlockLength { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs new file mode 100644 index 00000000..35b65eb7 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs @@ -0,0 +1,31 @@ + +namespace SharpCompress.Compressors.Rar.UnpackV1 +{ + internal partial class Unpack + { + uint SlotToLength(uint Slot) + { + //uint LBits,Length=2; + int LBits; + uint Length=2; + if (Slot<8) + { + LBits=0; + Length+=Slot; + } + else + { + //LBits=Slot/4-1; + LBits=(int)(Slot/4-1); + Length+=(4 | (Slot & 3)) << LBits; + } + + if (LBits>0) + { + Length+=getbits()>>(16-LBits); + AddBits(LBits); + } + return Length; + } + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs similarity index 96% rename from src/SharpCompress/Compressors/Rar/UnpackUtility.cs rename to src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs index 8f9cfada..46261496 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackUtility.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs @@ -1,9 +1,14 @@ using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar.UnpackV1 { internal static class UnpackUtility { +//!!! TODO rename methods + internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) { + return (uint)input.decodeNumber(dec); + } + internal static int decodeNumber(this BitInput input, Decode.Decode dec) { int bits; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs new file mode 100644 index 00000000..f702fb48 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs @@ -0,0 +1,68 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal partial class BitInput + { + +public 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 + // 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; + InBuf=new byte[BufSize]; + + // Ensure that we get predictable results when accessing bytes in area + // not filled with read data. + //memset(InBuf,0,BufSize); + } + else + InBuf=null; +} + + +//BitInput::~BitInput() +//{ +// if (!ExternalBuffer) +// delete[] InBuf; +//} +// + +public +void faddbits(uint Bits) +{ + // Function wrapped version of inline addbits to save code size. + addbits(Bits); +} + +public +uint fgetbits() +{ + // Function wrapped version of inline getbits to save code size. + return getbits(); +} + + +void SetExternalBuffer(byte []Buf) +{ + //if (InBuf!=NULL && !ExternalBuffer) + // delete[] InBuf; + InBuf=Buf; + ExternalBuffer=true; +} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs new file mode 100644 index 00000000..0eb0b64a --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs @@ -0,0 +1,70 @@ +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + +internal partial class BitInput +{ + public const int MAX_SIZE=0x8000; // Size of input buffer. + + public int InAddr; // Curent byte position in the buffer. + public int InBit; // Current bit position in the current byte. + + public bool ExternalBuffer; + + //BitInput(bool AllocBuffer); + //~BitInput(); + + public byte[] InBuf; // Dynamically allocated input buffer. + + public + void InitBitInput() + { + InAddr=InBit=0; + } + + // Move forward by 'Bits' bits. + public void addbits(uint _Bits) + { + var Bits = checked((int)_Bits); + Bits+=InBit; + 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. + public uint getbits() + { + uint BitField=(uint)InBuf[InAddr] << 16; + BitField|=(uint)InBuf[InAddr+1] << 8; + BitField|=(uint)InBuf[InAddr+2]; + BitField >>= (8-InBit); + return BitField & 0xffff; + } + + // Return 32 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + public uint getbits32() + { + uint BitField=(uint)InBuf[InAddr] << 24; + BitField|=(uint)InBuf[InAddr+1] << 16; + BitField|=(uint)InBuf[InAddr+2] << 8; + BitField|=(uint)InBuf[InAddr+3]; + BitField <<= InBit; + BitField|=(uint)InBuf[InAddr+4] >> (8-InBit); + return BitField & 0xffffffff; + } + + //void faddbits(uint Bits); + //uint fgetbits(); + + // Check if buffer has enough space for IncPtr bytes. Returns 'true' + // if buffer will be overflown. + bool Overflow(uint IncPtr) + { + return InAddr+IncPtr>=MAX_SIZE; + } + + //void SetExternalBuffer(byte *Buf); +} + +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs new file mode 100644 index 00000000..cd0b5eb0 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -0,0 +1,155 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + +using System; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + partial class FragmentedWindow + { + +public FragmentedWindow() +{ + //memset(Mem,0,sizeof(Mem)); + //memset(MemSize,0,sizeof(MemSize)); +} + + +//FragmentedWindow::~FragmentedWindow() +//{ +// Reset(); +//} + + +void Reset() +{ + for (uint I=0;I=MinSize) + { + NewMem=new byte[Size]; + if (NewMem!=null) + break; + Size-=Size/32; + } + if (NewMem==null) + //throw std::bad_alloc(); + throw new InvalidOperationException(); + + // Clean the window to generate the same output when unpacking corrupt + // RAR files, which may access to unused areas of sliding dictionary. + // sharpcompress: don't need this, freshly allocated above + //Utility.Memset(NewMem,0,Size); + + Mem[BlockNum]=NewMem; + TotalSize+=Size; + MemSize[BlockNum]=TotalSize; + BlockNum++; + } + if (TotalSize 0) + { + this[UnpPtr]=this[SrcPtr++ & MaxWinMask]; + // We need to have masked UnpPtr after quit from loop, so it must not + // be replaced with '(*this)[UnpPtr++ & MaxWinMask]' + UnpPtr=(UnpPtr+1) & MaxWinMask; + } +} + + +public void CopyData(byte[] Dest, size_t destOffset, size_t WinPos,size_t Size) +{ + for (size_t I=0;I DestUnpSize; } + + public int Char + { + get + { + // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code + if (InAddr > MAX_SIZE - 30) + { + UnpReadBuf(); + } + return InBuf[InAddr++]; + } + } + + public int PpmEscChar { get => this.PPMEscChar; set => this.PPMEscChar = value; } + + public static byte[] EnsureCapacity(byte[] array, int length) { + return array.Length < length ? new byte[length] : array; + } + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs new file mode 100644 index 00000000..0d30cd8e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs @@ -0,0 +1,126 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; +using uint32 = System.UInt32; + +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + partial class Unpack + { +//#define rotls(x,n,xsize) (((x)<<(n)) | ((x)>>(xsize-(n)))) +//#define rotrs(x,n,xsize) (((x)>>(n)) | ((x)<<(xsize-(n)))) +//#define rotl32(x,n) rotls(x,n,32) +//#define rotr32(x,n) rotrs(x,n,32) +// +//inline uint RawGet2(const void *Data) +//{ +// byte *D=(byte *)Data; +// return D[0]+(D[1]<<8); +//} + +uint32 RawGet4(byte[] D, int offset) +{ + return (uint)(D[offset]+(D[offset+1]<<8)+(D[offset+2]<<16)+(D[offset+3]<<24)); +} + +//inline uint64 RawGet8(const void *Data) +//{ +//#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) +// byte *D=(byte *)Data; +// return INT32TO64(RawGet4(D+4),RawGet4(D)); +//#else +// return *(uint64 *)Data; +//#endif +//} +// +// +//inline void RawPut2(uint Field,void *Data) +//{ +// byte *D=(byte *)Data; +// D[0]=(byte)(Field); +// D[1]=(byte)(Field>>8); +//} + +void RawPut4(uint32 Field,byte[] D, int offset) +{ + D[offset]=(byte)(Field); + D[offset+1]=(byte)(Field>>8); + D[offset+2]=(byte)(Field>>16); + D[offset+3]=(byte)(Field>>24); +} + +//inline void RawPut8(uint64 Field,void *Data) +//{ +//#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) +// byte *D=(byte *)Data; +// D[0]=(byte)(Field); +// D[1]=(byte)(Field>>8); +// D[2]=(byte)(Field>>16); +// D[3]=(byte)(Field>>24); +// D[4]=(byte)(Field>>32); +// D[5]=(byte)(Field>>40); +// D[6]=(byte)(Field>>48); +// D[7]=(byte)(Field>>56); +//#else +// *(uint64 *)Data=Field; +//#endif +//} + + +//#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) +//#define USE_MEM_BYTESWAP +//#endif + +// Load 4 big endian bytes from memory and return uint32. +//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) +// return __builtin_bswap32(*(uint32 *)m); +//#else +// return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; +//#endif +//} + + +// Save integer to memory as big endian. +//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) +// *(uint32*)mem = __builtin_bswap32(i); +//#else +// mem[0]=byte(i>>24); +// mem[1]=byte(i>>16); +// mem[2]=byte(i>>8); +// mem[3]=byte(i); +//#endif +//} + + +//inline uint32 ByteSwap32(uint32 i) +//{ +//#ifdef _MSC_VER +// return _byteswap_ulong(i); +//#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) +// return __builtin_bswap32(i); +//#else +// return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); +//#endif +//} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs new file mode 100644 index 00000000..d76ee10c --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -0,0 +1,501 @@ +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack15Local; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal partial class Unpack + { + +const int STARTL1 =2; +static uint[] DecL1={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, + 0xee00,0xf000,0xf200,0xf200,0xffff}; +static uint[] PosL1={0,0,0,2,3,5,7,11,16,20,24,32,32}; + +const int STARTL2 =3; +static uint[] DecL2={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00, + 0xf000,0xf200,0xf240,0xffff}; +static uint[] PosL2={0,0,0,0,5,7,9,13,18,22,26,34,36}; + +const int STARTHF0 =4; +static uint[] DecHf0={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200, + 0xf200,0xf200,0xffff}; +static uint[] PosHf0={0,0,0,0,0,8,16,24,33,33,33,33,33}; + + +const int STARTHF1 =5; +static uint[] DecHf1={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200, + 0xf7e0,0xffff}; +static uint[] PosHf1={0,0,0,0,0,0,4,44,60,76,80,80,127}; + + +const int STARTHF2 =5; +static uint[] DecHf2={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff, + 0xffff,0xffff}; +static uint[] PosHf2={0,0,0,0,0,0,2,7,53,117,233,0,0}; + + +const int STARTHF3 =6; +static uint[] DecHf3={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff, + 0xffff}; +static uint[] PosHf3={0,0,0,0,0,0,0,2,16,218,251,0,0}; + + +const int STARTHF4 =8; +static uint[] DecHf4={0xff00,0xffff,0xffff,0xffff,0xffff,0xffff}; +static uint[] PosHf4={0,0,0,0,0,0,0,0,0,255,0,0,0}; + + +void Unpack15(bool Solid) +{ + UnpInitData(Solid); + UnpInitData15(Solid); + UnpReadBuf(); + if (!Solid) + { + InitHuff(); + UnpPtr=0; + } + else + UnpPtr=WrPtr; + --DestUnpSize; + if (DestUnpSize>=0) + { + GetFlagsBuf(); + FlagsCnt=8; + } + + while (DestUnpSize>=0) + { + UnpPtr&=MaxWinMask; + + if (Inp.InAddr>ReadTop-30 && !UnpReadBuf()) + break; + if (((WrPtr-UnpPtr) & MaxWinMask)<270 && WrPtr!=UnpPtr) + UnpWriteBuf20(); + if (StMode != 0) + { + HuffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt=7; + } + + if ((FlagBuf & 0x80) != 0) + { + FlagBuf<<=1; + if (Nlzb > Nhfb) + LongLZ(); + else + HuffDecode(); + } + else + { + FlagBuf<<=1; + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt=7; + } + if ((FlagBuf & 0x80) != 0) + { + FlagBuf<<=1; + if (Nlzb > Nhfb) + HuffDecode(); + else + LongLZ(); + } + else + { + FlagBuf<<=1; + ShortLZ(); + } + } + } + UnpWriteBuf20(); +} + + +//#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos]) +uint GetShortLen1(uint pos) { return ((pos)==1 ? (uint)(this.Buf60+3):ShortLen1[pos]); } +//#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos]) +uint GetShortLen2(uint pos) { return ((pos)==3 ? (uint)(this.Buf60+3):ShortLen2[pos]); } + +internal static class Unpack15Local { + public static readonly uint[] ShortLen1={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; + public static readonly uint[] ShortXor1={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, + 0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; + public static readonly uint[] ShortLen2={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; + public static readonly uint[] ShortXor2={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, + 0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; +} + +void ShortLZ() +{ + uint Length,SaveLength; + uint LastDistance; + uint Distance; + int DistancePlace; + NumHuf=0; + + uint BitField=Inp.fgetbits(); + if (LCount==2) + { + Inp.faddbits(1); + if (BitField >= 0x8000) + { + CopyString15((uint)LastDist,LastLength); + return; + } + BitField <<= 1; + LCount=0; + } + + BitField>>=8; + +// not thread safe, replaced by GetShortLen1 and GetShortLen2 macro +// ShortLen1[1]=ShortLen2[3]=Buf60+3; + + if (AvrLn1<37) + { + for (Length=0;;Length++) + if (((BitField^ShortXor1[Length]) & (~(0xff>>(int)GetShortLen1(Length))))==0) + break; + Inp.faddbits(GetShortLen1(Length)); + } + else + { + for (Length=0;;Length++) + if (((BitField^ShortXor2[Length]) & (~(0xff>>(int)GetShortLen2(Length))))==0) + break; + Inp.faddbits(GetShortLen2(Length)); + } + + if (Length >= 9) + { + if (Length == 9) + { + LCount++; + CopyString15((uint)LastDist,LastLength); + return; + } + if (Length == 14) + { + LCount=0; + Length=DecodeNum(Inp.fgetbits(),STARTL2,DecL2,PosL2)+5; + Distance=(Inp.fgetbits()>>1) | 0x8000; + Inp.faddbits(15); + LastLength=Length; + LastDist=Distance; + CopyString15(Distance,Length); + return; + } + + LCount=0; + SaveLength=Length; + Distance=OldDist[(OldDistPtr-(Length-9)) & 3]; + Length=DecodeNum(Inp.fgetbits(),STARTL1,DecL1,PosL1)+2; + if (Length==0x101 && SaveLength==10) + { + Buf60 ^= 1; + return; + } + if (Distance > 256) + Length++; + if (Distance >= MaxDist3) + Length++; + + OldDist[OldDistPtr++]=Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + CopyString15(Distance,Length); + return; + } + + LCount=0; + AvrLn1 += Length; + AvrLn1 -= AvrLn1 >> 4; + + DistancePlace=(int)(DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2) & 0xff); + Distance=ChSetA[DistancePlace]; + if (--DistancePlace != -1) + { + LastDistance=ChSetA[DistancePlace]; + ChSetA[DistancePlace+1]=(ushort)LastDistance; + ChSetA[DistancePlace]=(ushort)Distance; + } + Length+=2; + OldDist[OldDistPtr++] = ++Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + CopyString15(Distance,Length); +} + + +void LongLZ() +{ + uint Length; + uint Distance; + uint DistancePlace,NewDistancePlace; + uint OldAvr2,OldAvr3; + + NumHuf=0; + Nlzb+=16; + if (Nlzb > 0xff) + { + Nlzb=0x90; + Nhfb >>= 1; + } + OldAvr2=AvrLn2; + + uint BitField=Inp.fgetbits(); + if (AvrLn2 >= 122) + Length=DecodeNum(BitField,STARTL2,DecL2,PosL2); + else + if (AvrLn2 >= 64) + Length=DecodeNum(BitField,STARTL1,DecL1,PosL1); + else + if (BitField < 0x100) + { + Length=BitField; + Inp.faddbits(16); + } + else + { + for (Length=0;((BitField<<(int)Length)&0x8000)==0;Length++) + ; + Inp.faddbits(Length+1); + } + + AvrLn2 += Length; + AvrLn2 -= AvrLn2 >> 5; + + BitField=Inp.fgetbits(); + if (AvrPlcB > 0x28ff) + DistancePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); + else + if (AvrPlcB > 0x6ff) + DistancePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); + else + DistancePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); + + AvrPlcB += DistancePlace; + AvrPlcB -= AvrPlcB >> 8; + while (true) + { + Distance = ChSetB[DistancePlace & 0xff]; + NewDistancePlace = NToPlB[Distance++ & 0xff]++; + if ((Distance & 0xff) != 0) + CorrHuff(ChSetB,NToPlB); + else + break; + } + + ChSetB[DistancePlace & 0xff]=ChSetB[NewDistancePlace]; + ChSetB[NewDistancePlace]=(ushort)Distance; + + Distance=((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1; + Inp.faddbits(7); + + OldAvr3=AvrLn3; + if (Length!=1 && Length!=4) + if (Length==0 && Distance <= MaxDist3) + { + AvrLn3++; + AvrLn3 -= AvrLn3 >> 8; + } + else + if (AvrLn3 > 0) + AvrLn3--; + Length+=3; + if (Distance >= MaxDist3) + Length++; + if (Distance <= 256) + Length+=8; + if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) + MaxDist3=0x7f00; + else + MaxDist3=0x2001; + OldDist[OldDistPtr++]=Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + CopyString15(Distance,Length); +} + + +void HuffDecode() +{ + uint CurByte,NewBytePlace; + uint Length; + uint Distance; + int BytePlace; + + uint BitField=Inp.fgetbits(); + + if (AvrPlc > 0x75ff) + BytePlace=(int)DecodeNum(BitField,STARTHF4,DecHf4,PosHf4); + else + if (AvrPlc > 0x5dff) + BytePlace=(int)DecodeNum(BitField,STARTHF3,DecHf3,PosHf3); + else + if (AvrPlc > 0x35ff) + BytePlace=(int)DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); + else + if (AvrPlc > 0x0dff) + BytePlace=(int)DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); + else + BytePlace=(int)DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); + BytePlace&=0xff; + if (StMode != 0) + { + if (BytePlace==0 && BitField > 0xfff) + BytePlace=0x100; + if (--BytePlace==-1) + { + BitField=Inp.fgetbits(); + Inp.faddbits(1); + if ((BitField & 0x8000) != 0) + { + NumHuf=StMode=0; + return; + } + else + { + Length = (BitField & 0x4000) != 0 ? 4U : 3; + Inp.faddbits(1); + Distance=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2); + Distance = (Distance << 5) | (Inp.fgetbits() >> 11); + Inp.faddbits(5); + CopyString15(Distance,Length); + return; + } + } + } + else + if (NumHuf++ >= 16 && FlagsCnt==0) + StMode=1; + AvrPlc += (uint)BytePlace; + AvrPlc -= AvrPlc >> 8; + Nhfb+=16; + if (Nhfb > 0xff) + { + Nhfb=0x90; + Nlzb >>= 1; + } + + Window[UnpPtr++]=(byte)(ChSet[BytePlace]>>8); + --DestUnpSize; + + while (true) + { + CurByte=ChSet[BytePlace]; + NewBytePlace=NToPl[CurByte++ & 0xff]++; + if ((CurByte & 0xff) > 0xa1) + CorrHuff(ChSet,NToPl); + else + break; + } + + ChSet[BytePlace]=ChSet[NewBytePlace]; + ChSet[NewBytePlace]=(ushort)CurByte; +} + + +void GetFlagsBuf() +{ + uint Flags,NewFlagsPlace; + uint FlagsPlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2); + + // Our Huffman table stores 257 items and needs all them in other parts + // of code such as when StMode is on, so the first item is control item. + // While normally we do not use the last item to code the flags byte here, + // we need to check for value 256 when unpacking in case we unpack + // a corrupt archive. + if (FlagsPlace>=ChSetC.Length) + return; + + while (true) + { + Flags=ChSetC[FlagsPlace]; + FlagBuf=Flags>>8; + NewFlagsPlace=NToPlC[Flags++ & 0xff]++; + if ((Flags & 0xff) != 0) + break; + CorrHuff(ChSetC,NToPlC); + } + + ChSetC[FlagsPlace]=ChSetC[NewFlagsPlace]; + ChSetC[NewFlagsPlace]=(ushort)Flags; +} + +void UnpInitData15(bool Solid) +{ + if (!Solid) + { + AvrPlcB=AvrLn1=AvrLn2=AvrLn3=0; + NumHuf=Buf60=0; + AvrPlc=0x3500; + MaxDist3=0x2001; + Nhfb=Nlzb=0x80; + } + FlagsCnt=0; + FlagBuf=0; + StMode=0; + LCount=0; + ReadTop=0; +} + +void InitHuff() +{ + for (uint I=0;I<256;I++) + { + ChSet[I]=ChSetB[I]=(ushort)(I<<8); + ChSetA[I]=(ushort)I; + ChSetC[I]=(ushort)(((~I+1) & 0xff)<<8); + } + Utility.Memset(NToPl,0,NToPl.Length); + Utility.Memset(NToPlB,0,NToPlB.Length); + Utility.Memset(NToPlC,0,NToPlC.Length); + CorrHuff(ChSetB,NToPlB); +} + + +void CorrHuff(ushort[] CharSet,byte[] NumToPlace) +{ + int I,J; + for (I=7;I>=0;I--) + for (J=0;J<32;J++) + CharSet[J]=(ushort)((CharSet[J] & ~0xff) | I); + Utility.Memset(NumToPlace,0,NToPl.Length); + for (I=6;I>=0;I--) + NumToPlace[I]=(byte)((7-I)*32); +} + + +void CopyString15(uint Distance,uint Length) +{ + DestUnpSize-=Length; + while (Length-- != 0) + { + Window[UnpPtr]=Window[(UnpPtr-Distance) & MaxWinMask]; + UnpPtr=(UnpPtr+1) & MaxWinMask; + } +} + + +uint DecodeNum(uint Num,uint StartPos,uint[] DecTab,uint[] PosTab) +{ + int I; + for (Num&=0xfff0,I=0;DecTab[I]<=Num;I++) + StartPos++; + Inp.faddbits(StartPos); + return(((Num-(I != 0 ? DecTab[I-1]:0))>>(int)(16-StartPos))+PosTab[StartPos]); +} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs new file mode 100644 index 00000000..57ecdc02 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -0,0 +1,404 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + +using System; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal partial class Unpack + { + +void CopyString20(uint Length,uint Distance) +{ + LastDist=OldDist[OldDistPtr++ & 3]=Distance; + LastLength=Length; + DestUnpSize-=Length; + CopyString(Length,Distance); +} + + +internal static class Unpack20Local { + public static readonly byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; + public static readonly byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + public static readonly uint[] DDecode={0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; + public static readonly byte[] DBits= {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; + public static readonly byte[] SDDecode={0,4,8,16,32,64,128,192}; + public static readonly byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; +} +void Unpack20(bool Solid) +{ + uint Bits; + + if (Suspended) + UnpPtr=WrPtr; + else + { + UnpInitData(Solid); + if (!UnpReadBuf()) + return; + if ((!Solid || !TablesRead2) && !ReadTables20()) + return; + --DestUnpSize; + } + + while (DestUnpSize>=0) + { + UnpPtr&=MaxWinMask; + + if (Inp.InAddr>ReadTop-30) + if (!UnpReadBuf()) + break; + if (((WrPtr-UnpPtr) & MaxWinMask)<270 && WrPtr!=UnpPtr) + { + UnpWriteBuf20(); + if (Suspended) + return; + } + if (UnpAudioBlock) + { + uint AudioNumber=DecodeNumber(Inp,MD[UnpCurChannel]); + + if (AudioNumber==256) + { + if (!ReadTables20()) + break; + continue; + } + Window[UnpPtr++]=DecodeAudio((int)AudioNumber); + if (++UnpCurChannel==UnpChannels) + UnpCurChannel=0; + --DestUnpSize; + continue; + } + + uint Number=DecodeNumber(Inp,BlockTables.LD); + if (Number<256) + { + Window[UnpPtr++]=(byte)Number; + --DestUnpSize; + continue; + } + if (Number>269) + { + uint Length=(uint)(LDecode[Number-=270]+3); + if ((Bits=LBits[Number])>0) + { + Length+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + + uint DistNumber=DecodeNumber(Inp,BlockTables.DD); + uint Distance=DDecode[DistNumber]+1; + if ((Bits=DBits[DistNumber])>0) + { + Distance+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000L) + Length++; + } + + CopyString20(Length,Distance); + continue; + } + if (Number==269) + { + if (!ReadTables20()) + break; + continue; + } + if (Number==256) + { + CopyString20(LastLength,LastDist); + continue; + } + if (Number<261) + { + uint Distance=OldDist[(OldDistPtr-(Number-256)) & 3]; + uint LengthNumber=DecodeNumber(Inp,BlockTables.RD); + uint Length=(uint)(LDecode[LengthNumber]+2); + if ((Bits=LBits[LengthNumber])>0) + { + Length+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + if (Distance>=0x101) + { + Length++; + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000) + Length++; + } + } + CopyString20(Length,Distance); + continue; + } + if (Number<270) + { + uint Distance=(uint)(SDDecode[Number-=261]+1); + if ((Bits=SDBits[Number])>0) + { + Distance+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + CopyString20(2,Distance); + continue; + } + } + ReadLastTables(); + UnpWriteBuf20(); +} + + +void UnpWriteBuf20() +{ + if (UnpPtr!=WrPtr) + UnpSomeRead=true; + if (UnpPtrReadTop-25) + if (!UnpReadBuf()) + return false; + uint BitField=Inp.getbits(); + UnpAudioBlock=(BitField & 0x8000)!=0; + + if ((BitField & 0x4000) != 0) + Utility.Memset(UnpOldTable20,0,UnpOldTable20.Length); + Inp.addbits(2); + + uint TableSize; + if (UnpAudioBlock) + { + UnpChannels=((BitField>>12) & 3)+1; + if (UnpCurChannel>=UnpChannels) + UnpCurChannel=0; + Inp.addbits(2); + TableSize=MC20*UnpChannels; + } + else + TableSize=NC20+DC20+RC20; + + for (uint I=0;I> 12); + Inp.addbits(4); + } + MakeDecodeTables(BitLength,0,BlockTables.BD,BC20); + for (uint I=0;IReadTop-5) + if (!UnpReadBuf()) + return false; + uint Number=DecodeNumber(Inp,BlockTables.BD); + if (Number<16) + { + Table[I]=(byte)((Number+this.UnpOldTable20[I]) & 0xf); + I++; + } + else + if (Number==16) + { + uint N=(Inp.getbits() >> 14)+3; + Inp.addbits(2); + if (I==0) + return false; // We cannot have "repeat previous" code at the first position. + else + while (N-- > 0 && I> 13)+3; + Inp.addbits(3); + } + else + { + N=(Inp.getbits() >> 9)+11; + Inp.addbits(7); + } + while (N-- > 0 && IReadTop) + return true; + if (UnpAudioBlock) + for (uint I=0;I=Inp.InAddr+5) + if (UnpAudioBlock) + { + if (DecodeNumber(Inp,MD[UnpCurChannel])==256) + ReadTables20(); + } + else + if (DecodeNumber(Inp,BlockTables.LD)==269) + ReadTables20(); +} + + +void UnpInitData20(bool Solid) +{ + if (!Solid) + { + TablesRead2=false; + UnpAudioBlock=false; + UnpChannelDelta=0; + UnpCurChannel=0; + UnpChannels=1; + + //memset(AudV,0,sizeof(AudV)); + AudV = new AudioVariables[4]; + Utility.Memset(UnpOldTable20, 0, UnpOldTable20.Length); + //memset(MD,0,sizeof(MD)); + MD = new DecodeTable[4]; + } +} + + +byte DecodeAudio(int Delta) +{ + AudioVariables V=AudV[UnpCurChannel]; + V.ByteCount++; + V.D4=V.D3; + V.D3=V.D2; + V.D2=V.LastDelta-V.D1; + V.D1=V.LastDelta; + int PCh=8*V.LastChar+V.K1*V.D1+V.K2*V.D2+V.K3*V.D3+V.K4*V.D4+V.K5*UnpChannelDelta; + PCh=(PCh>>3) & 0xFF; + + uint Ch=(uint)(PCh-Delta); + + int D=(sbyte)Delta; + // Left shift of negative value is undefined behavior in C++, + // so we cast it to unsigned to follow the standard. + D=(int)((uint)D<<3); + + V.Dif[0]+=(uint)Math.Abs(D); + V.Dif[1]+=(uint)Math.Abs(D-V.D1); + V.Dif[2]+=(uint)Math.Abs(D+V.D1); + V.Dif[3]+=(uint)Math.Abs(D-V.D2); + V.Dif[4]+=(uint)Math.Abs(D+V.D2); + V.Dif[5]+=(uint)Math.Abs(D-V.D3); + V.Dif[6]+=(uint)Math.Abs(D+V.D3); + V.Dif[7]+=(uint)Math.Abs(D-V.D4); + V.Dif[8]+=(uint)Math.Abs(D+V.D4); + V.Dif[9]+=(uint)Math.Abs(D-UnpChannelDelta); + V.Dif[10]+=(uint)Math.Abs(D+UnpChannelDelta); + + UnpChannelDelta=V.LastDelta=(sbyte)(Ch-V.LastChar); + V.LastChar=(int)Ch; + + if ((V.ByteCount & 0x1F)==0) + { + uint MinDif=V.Dif[0],NumMinDif=0; + V.Dif[0]=0; + for (uint I=1;I=-16) + V.K1--; + break; + case 2: + if (V.K1<16) + V.K1++; + break; + case 3: + if (V.K2>=-16) + V.K2--; + break; + case 4: + if (V.K2<16) + V.K2++; + break; + case 5: + if (V.K3>=-16) + V.K3--; + break; + case 6: + if (V.K3<16) + V.K3++; + break; + case 7: + if (V.K4>=-16) + V.K4--; + break; + case 8: + if (V.K4<16) + V.K4++; + break; + case 9: + if (V.K5>=-16) + V.K5--; + break; + case 10: + if (V.K5<16) + V.K5++; + break; + } + } + return (byte)Ch; +} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs new file mode 100644 index 00000000..7e77c7d4 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -0,0 +1,800 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + +using System; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; +//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; +/* +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal partial class Unpack + { + +#if !RarV2017_RAR5ONLY +// We use it instead of direct PPM.DecodeChar call to be sure that +// we reset PPM structures in case of corrupt data. It is important, +// because these structures can be invalid after PPM.DecodeChar returned -1. +int SafePPMDecodeChar() +{ + int Ch=PPM.DecodeChar(); + if (Ch==-1) // Corrupt PPM data found. + { + PPM.CleanUp(); // Reset possibly corrupt PPM data structures. + UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode. + } + return(Ch); +} + +internal static class Unpack30Local { + public static readonly byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; + public static readonly byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + public static readonly int[] DDecode = new int[DC]; + public static readonly byte[] DBits = new byte[DC]; + public static readonly int[] DBitLengthCounts= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; + public static readonly byte[] SDDecode={0,4,8,16,32,64,128,192}; + public static readonly byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6}; +} +void Unpack29(bool Solid) +{ + uint Bits; + + if (DDecode[1]==0) + { + int Dist=0,BitLength=0,Slot=0; + for (int I=0;IReadBorder) + { + if (!UnpReadBuf30()) + break; + } + if (((WrPtr-UnpPtr) & MaxWinMask)<260 && WrPtr!=UnpPtr) + { + UnpWriteBuf30(); + if (WrittenFileSize>DestUnpSize) + return; + if (Suspended) + { + FileExtracted=false; + return; + } + } + if (UnpBlockType==BLOCK_PPM) + { + // Here speed is critical, so we do not use SafePPMDecodeChar, + // because sometimes even the inline function can introduce + // some additional penalty. + int Ch=PPM.DecodeChar(); + if (Ch==-1) // Corrupt PPM data found. + { + PPM.CleanUp(); // Reset possibly corrupt PPM data structures. + UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode. + break; + } + if (Ch==PPMEscChar) + { + int NextCh=SafePPMDecodeChar(); + if (NextCh==0) // End of PPM encoding. + { + if (!ReadTables30()) + break; + continue; + } + if (NextCh==-1) // Corrupt PPM data found. + break; + if (NextCh==2) // End of file in PPM mode. + break; + if (NextCh==3) // Read VM code. + { + if (!ReadVMCodePPM()) + break; + continue; + } + if (NextCh==4) // LZ inside of PPM. + { + uint Distance=0,Length; + bool Failed=false; + for (int I=0;I<4 && !Failed;I++) + { + int _Ch=SafePPMDecodeChar(); + if (_Ch==-1) + Failed=true; + else + if (I==3) + Length=(byte)_Ch; + else + Distance=(Distance<<8)+(byte)_Ch; + } + if (Failed) + break; + + CopyString(Length+32,Distance+2); + continue; + } + if (NextCh==5) // One byte distance match (RLE) inside of PPM. + { + int Length=SafePPMDecodeChar(); + if (Length==-1) + break; + CopyString((uint)(Length+4),1); + continue; + } + // If we are here, NextCh must be 1, what means that current byte + // is equal to our 'escape' byte, so we just store it to Window. + } + Window[UnpPtr++]=(byte)Ch; + continue; + } + + uint Number=DecodeNumber(Inp,BlockTables.LD); + if (Number<256) + { + Window[UnpPtr++]=(byte)Number; + continue; + } + if (Number>=271) + { + uint Length=(uint)(LDecode[Number-=271]+3); + if ((Bits=LBits[Number])>0) + { + Length+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + + uint DistNumber=DecodeNumber(Inp,BlockTables.DD); + uint Distance=(uint)(DDecode[DistNumber]+1); + if ((Bits=DBits[DistNumber])>0) + { + if (DistNumber>9) + { + if (Bits>4) + { + Distance+=((Inp.getbits()>>(int)(20-Bits))<<4); + Inp.addbits(Bits-4); + } + if (LowDistRepCount>0) + { + LowDistRepCount--; + Distance+=(uint)PrevLowDist; + } + else + { + uint LowDist=DecodeNumber(Inp,BlockTables.LDD); + if (LowDist==16) + { + LowDistRepCount=(int)(LOW_DIST_REP_COUNT-1); + Distance+=(uint)PrevLowDist; + } + else + { + Distance+=LowDist; + PrevLowDist=(int)LowDist; + } + } + } + else + { + Distance+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + } + + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000) + Length++; + } + + InsertOldDist(Distance); + LastLength=Length; + CopyString(Length,Distance); + continue; + } + if (Number==256) + { + if (!ReadEndOfBlock()) + break; + continue; + } + if (Number==257) + { + if (!ReadVMCode()) + break; + continue; + } + if (Number==258) + { + if (LastLength!=0) + CopyString(LastLength,OldDist[0]); + continue; + } + if (Number<263) + { + uint DistNum=Number-259; + uint Distance=OldDist[DistNum]; + for (uint I=DistNum;I>0;I--) + OldDist[I]=OldDist[I-1]; + OldDist[0]=Distance; + + uint LengthNumber=DecodeNumber(Inp,BlockTables.RD); + int Length=LDecode[LengthNumber]+2; + if ((Bits=LBits[LengthNumber])>0) + { + Length+=(int)(Inp.getbits()>>(int)(16-Bits)); + Inp.addbits(Bits); + } + LastLength=(uint)Length; + CopyString((uint)Length,Distance); + continue; + } + if (Number<272) + { + uint Distance=(uint)(SDDecode[Number-=263]+1); + if ((Bits=SDBits[Number])>0) + { + Distance+=Inp.getbits()>>(int)(16-Bits); + Inp.addbits(Bits); + } + InsertOldDist(Distance); + LastLength=2; + CopyString(2,Distance); + continue; + } + } + UnpWriteBuf30(); +} + + +// Return 'false' to quit unpacking the current file or 'true' to continue. +bool ReadEndOfBlock() +{ + uint BitField=Inp.getbits(); + bool NewTable,NewFile=false; + + // "1" - no new file, new table just here. + // "00" - new file, no new table. + // "01" - new file, new table (in beginning of next file). + + if ((BitField & 0x8000)!=0) + { + NewTable=true; + Inp.addbits(1); + } + else + { + NewFile=true; + NewTable=(BitField & 0x4000)!=0; + Inp.addbits(2); + } + TablesRead3=!NewTable; + + // Quit immediately if "new file" flag is set. If "new table" flag + // is present, we'll read the table in beginning of next file + // based on 'TablesRead3' 'false' value. + if (NewFile) + return false; + return ReadTables30(); // Quit only if we failed to read tables. +} + + +bool ReadVMCode() +{ + // Entire VM code is guaranteed to fully present in block defined + // by current Huffman table. Compressor checks that VM code does not cross + // Huffman block boundaries. + uint FirstByte=Inp.getbits()>>8; + Inp.addbits(8); + uint Length=(FirstByte & 7)+1; + if (Length==7) + { + Length=(Inp.getbits()>>8)+7; + Inp.addbits(8); + } + else + if (Length==8) + { + Length=Inp.getbits(); + Inp.addbits(16); + } + if (Length==0) + return false; + Array VMCode(Length); + for (uint I=0;I=ReadTop-1 && !UnpReadBuf30() && I>8; + Inp.addbits(8); + } + return AddVMCode(FirstByte,&VMCode[0],Length); +} + + +bool ReadVMCodePPM() +{ + uint FirstByte=(uint)SafePPMDecodeChar(); + if ((int)FirstByte==-1) + return false; + uint Length=(FirstByte & 7)+1; + if (Length==7) + { + int B1=SafePPMDecodeChar(); + if (B1==-1) + return false; + Length=B1+7; + } + else + if (Length==8) + { + int B1=SafePPMDecodeChar(); + if (B1==-1) + return false; + int B2=SafePPMDecodeChar(); + if (B2==-1) + return false; + Length=B1*256+B2; + } + if (Length==0) + return false; + Array VMCode(Length); + for (uint I=0;IFilters30.Count || FiltPos>OldFilterLengths.Count) + return false; + LastFilter=(int)FiltPos; + bool NewFilter=(FiltPos==Filters30.Count); + + UnpackFilter30 StackFilter=new UnpackFilter30(); // New filter for PrgStack. + + UnpackFilter30 Filter; + if (NewFilter) // New filter code, never used before since VM reset. + { + if (FiltPos>MAX3_UNPACK_FILTERS) + { + // Too many different filters, corrupt archive. + //delete StackFilter; + return false; + } + + Filters30.Add(1); + Filters30[Filters30.Count-1]=Filter=new UnpackFilter30(); + StackFilter.ParentFilter=(uint)(Filters30.Count-1); + + // Reserve one item to store the data block length of our new filter + // entry. We'll set it to real block length below, after reading it. + // But we need to initialize it now, because when processing corrupt + // data, we can access this item even before we set it to real value. + OldFilterLengths.Add(0); + } + else // Filter was used in the past. + { + Filter=Filters30[(int)FiltPos]; + StackFilter.ParentFilter=FiltPos; + } + + int EmptyCount=0; + for (int I=0;I0) + PrgStack[I]=null; + } + if (EmptyCount==0) + { + if (PrgStack.Count>MAX3_UNPACK_FILTERS) + { + //delete StackFilter; + return false; + } + PrgStack.Add(1); + EmptyCount=1; + } + size_t StackPos=(uint)(this.PrgStack.Count-EmptyCount); + PrgStack[(int)StackPos]=StackFilter; + + uint BlockStart=RarVM.ReadData(VMCodeInp); + if ((FirstByte & 0x40)!=0) + BlockStart+=258; + StackFilter.BlockStart=(uint)((BlockStart+UnpPtr)&MaxWinMask); + if ((FirstByte & 0x20)!=0) + { + StackFilter.BlockLength=RarVM.ReadData(VMCodeInp); + + // Store the last data block length for current filter. + OldFilterLengths[(int)FiltPos]=(int)StackFilter.BlockLength; + } + else + { + // Set the data block size to same value as the previous block size + // for same filter. It is possible for corrupt data to access a new + // and not filled yet item of OldFilterLengths array here. This is why + // we set new OldFilterLengths items to zero above. + StackFilter.BlockLength=FiltPos>9; + VMCodeInp.faddbits(7); + for (int I=0;I<7;I++) + if ((InitMask & (1<=0x10000 || VMCodeSize==0) + return false; + Array VMCode(VMCodeSize); + for (uint I=0;I>8; + VMCodeInp.faddbits(8); + } + VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); + } + StackFilter.Prg.Type=Filter.Prg.Type; + + return true; +} + + +bool UnpReadBuf30() +{ + int DataSize=ReadTop-Inp.InAddr; // Data left to process. + if (DataSize<0) + return false; + if (Inp.InAddr>BitInput.MAX_SIZE/2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize>0) + //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Array.Copy(Inp.InBuf,Inp.InAddr,Inp.InBuf,0,DataSize); + Inp.InAddr=0; + ReadTop=DataSize; + } + else + DataSize=ReadTop; + int ReadCode=UnpIO_UnpRead(Inp.InBuf,DataSize,BitInput.MAX_SIZE-DataSize); + if (ReadCode>0) + ReadTop+=ReadCode; + ReadBorder=ReadTop-30; + return ReadCode!=-1; +} + + +void UnpWriteBuf30() +{ + uint WrittenBorder=(uint)WrPtr; + uint WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask); + for (int I=0;IParentFilter]->Prg; + VM_PreparedProgram *Prg=&flt->Prg; + + ExecuteCode(Prg); + + byte[] FilteredData=Prg.FilteredData; + uint FilteredDataSize=Prg.FilteredDataSize; + + delete PrgStack[I]; + PrgStack[I]=null; + while (I+1Prg; + VM_PreparedProgram *NextPrg=&NextFilter->Prg; + + ExecuteCode(NextPrg); + + FilteredData=NextPrg.FilteredData; + FilteredDataSize=NextPrg.FilteredDataSize; + I++; + delete PrgStack[I]; + PrgStack[I]=null; + } + UnpIO_UnpWrite(FilteredData,0,FilteredDataSize); + UnpSomeRead=true; + WrittenFileSize+=FilteredDataSize; + WrittenBorder=BlockEnd; + WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask); + } + else + { + // Current filter intersects the window write border, so we adjust + // the window border to process this filter next time, not now. + for (size_t J=I;JInitR[6]=(uint)WrittenFileSize; + VM.Execute(Prg); +} + + +bool ReadTables30() +{ + byte[] BitLength = new byte[BC]; + byte[] Table = new byte[HUFF_TABLE_SIZE30]; + if (Inp.InAddr>ReadTop-25) + if (!UnpReadBuf30()) + return(false); + Inp.faddbits((uint)((8-Inp.InBit)&7)); + uint BitField=Inp.fgetbits(); + if ((BitField & 0x8000) != 0) + { + UnpBlockType=BLOCK_PPM; + return(PPM.DecodeInit(this,PPMEscChar)); + } + UnpBlockType=BLOCK_LZ; + + PrevLowDist=0; + LowDistRepCount=0; + + if ((BitField & 0x4000) == 0) + Utility.Memset(UnpOldTable,0,UnpOldTable.Length); + Inp.faddbits(2); + + for (uint I=0;I> 12); + Inp.faddbits(4); + if (Length==15) + { + uint ZeroCount=(byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (ZeroCount==0) + BitLength[I]=15; + else + { + ZeroCount+=2; + while (ZeroCount-- > 0 && IReadTop-5) + if (!UnpReadBuf30()) + return(false); + uint Number=DecodeNumber(Inp,BlockTables.BD); + if (Number<16) + { + Table[I]=(byte)((Number+this.UnpOldTable[I]) & 0xf); + I++; + } + else + if (Number<18) + { + uint N; + if (Number==16) + { + N=(Inp.fgetbits() >> 13)+3; + Inp.faddbits(3); + } + else + { + N=(Inp.fgetbits() >> 9)+11; + Inp.faddbits(7); + } + if (I==0) + return false; // We cannot have "repeat previous" code at the first position. + else + while (N-- > 0 && I> 13)+3; + Inp.faddbits(3); + } + else + { + N=(Inp.fgetbits() >> 9)+11; + Inp.faddbits(7); + } + while (N-- > 0 && IReadTop) + return false; + MakeDecodeTables(Table,0,BlockTables.LD,NC30); + MakeDecodeTables(Table,(int)NC30,BlockTables.DD,DC30); + MakeDecodeTables(Table,(int)(NC30+DC30),BlockTables.LDD,LDC30); + MakeDecodeTables(Table,(int)(NC30+DC30+LDC30),BlockTables.RD,RC30); + //x memcpy(UnpOldTable,Table,sizeof(UnpOldTable)); + Array.Copy(Table,0,UnpOldTable,0,UnpOldTable.Length); + return true; +} + +#endif + +void UnpInitData30(bool Solid) +{ + if (!Solid) + { + TablesRead3=false; + Utility.Memset(UnpOldTable, 0, UnpOldTable.Length); + PPMEscChar=2; + UnpBlockType=BLOCK_LZ; + } + InitFilters30(Solid); +} + + +void InitFilters30(bool Solid) +{ + if (!Solid) + { + //OldFilterLengths.SoftReset(); + OldFilterLengths.Clear(); + LastFilter=0; + + //for (size_t I=0;I=ReadBorder) + { + bool FileDone=false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || + Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && + Inp.InBit>=BlockHeader.BlockBitSize) + { + if (BlockHeader.LastBlockInFile) + { + FileDone=true; + break; + } + if (!ReadBlockHeader(Inp,ref BlockHeader) || !ReadTables(Inp, ref BlockHeader, ref BlockTables)) + return; + } + if (FileDone || !UnpReadBuf()) + break; + } + + if (((WriteBorder-UnpPtr) & MaxWinMask)DestUnpSize) + return; + if (Suspended) + { + FileExtracted=false; + return; + } + } + + uint MainSlot=DecodeNumber(Inp,BlockTables.LD); + if (MainSlot<256) + { + if (Fragmented) + FragWindow[UnpPtr++]=(byte)MainSlot; + else + Window[UnpPtr++]=(byte)MainSlot; + continue; + } + if (MainSlot>=262) + { + uint Length=SlotToLength(Inp,MainSlot-262); + + uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,BlockTables.DD); + if (DistSlot<4) + { + DBits=0; + Distance+=DistSlot; + } + else + { + DBits=DistSlot/2 - 1; + Distance+=(2 | (DistSlot & 1)) << (int)DBits; + } + + if (DBits>0) + { + if (DBits>=4) + { + if (DBits>4) + { + Distance+=((Inp.getbits32()>>(int)(36-DBits))<<4); + Inp.addbits(DBits-4); + } + uint LowDist=DecodeNumber(Inp,BlockTables.LDD); + Distance+=LowDist; + } + else + { + Distance+=Inp.getbits32()>>(int)(32-DBits); + Inp.addbits(DBits); + } + } + + if (Distance>0x100) + { + Length++; + if (Distance>0x2000) + { + Length++; + if (Distance>0x40000) + Length++; + } + } + + InsertOldDist(Distance); + LastLength=Length; + if (Fragmented) + FragWindow.CopyString(Length,Distance,ref UnpPtr,MaxWinMask); + else + CopyString(Length,Distance); + continue; + } + if (MainSlot==256) + { + UnpackFilter Filter = new UnpackFilter(); + if (!ReadFilter(Inp,Filter) || !AddFilter(Filter)) + break; + continue; + } + if (MainSlot==257) + { + if (LastLength!=0) + if (Fragmented) + FragWindow.CopyString(LastLength,OldDist[0],ref UnpPtr,MaxWinMask); + else + CopyString(LastLength,OldDist[0]); + continue; + } + if (MainSlot<262) + { + uint DistNum=MainSlot-258; + uint Distance=OldDist[DistNum]; + for (uint I=DistNum;I>0;I--) + OldDist[I]=OldDist[I-1]; + OldDist[0]=Distance; + + uint LengthSlot=DecodeNumber(Inp,BlockTables.RD); + uint Length=SlotToLength(Inp,LengthSlot); + LastLength=Length; + if (Fragmented) + FragWindow.CopyString(Length,Distance,ref UnpPtr,MaxWinMask); + else + CopyString(Length,Distance); + continue; + } + } + UnpWriteBuf(); +} + + +uint ReadFilterData(BitInput Inp) +{ + uint ByteCount=(Inp.fgetbits()>>14)+1; + Inp.addbits(2); + + uint Data=0; + for (uint I=0;I>8)<<(int)(I*8); + Inp.addbits(8); + } + return Data; +} + + +bool ReadFilter(BitInput Inp,UnpackFilter Filter) +{ + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16) + if (!UnpReadBuf()) + return false; + + Filter.BlockStart=ReadFilterData(Inp); + Filter.BlockLength=ReadFilterData(Inp); + if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE) + Filter.BlockLength=0; + + Filter.Type=(byte)(Inp.fgetbits()>>13); + Inp.faddbits(3); + + if (Filter.Type==FILTER_DELTA) + { + Filter.Channels=(byte)((Inp.fgetbits()>>11)+1); + Inp.faddbits(5); + } + + return true; +} + + +bool AddFilter(UnpackFilter Filter) +{ + if (Filters.Count>=MAX_UNPACK_FILTERS) + { + UnpWriteBuf(); // Write data, apply and flush filters. + if (Filters.Count>=MAX_UNPACK_FILTERS) + InitFilters(); // Still too many filters, prevent excessive memory use. + } + + // If distance to filter start is that large that due to circular dictionary + // mode now it points to old not written yet data, then we set 'NextWindow' + // flag and process this filter only after processing that older data. + Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart; + + Filter.BlockStart=(uint)((Filter.BlockStart+UnpPtr)&MaxWinMask); + Filters.Add(Filter); + return true; +} + + +bool UnpReadBuf() +{ + int DataSize=ReadTop-Inp.InAddr; // Data left to process. + if (DataSize<0) + return false; + BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart; + if (Inp.InAddr>BitInput.MAX_SIZE/2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize>0) + //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); + Inp.InAddr=0; + ReadTop=DataSize; + } + else + DataSize=ReadTop; + int ReadCode=0; + if (BitInput.MAX_SIZE!=DataSize) + ReadCode=UnpIO_UnpRead(Inp.InBuf,DataSize,BitInput.MAX_SIZE-DataSize); + if (ReadCode>0) // Can be also -1. + ReadTop+=ReadCode; + ReadBorder=ReadTop-30; + BlockHeader.BlockStart=Inp.InAddr; + if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet. + { + // We may need to quit from main extraction loop and read new block header + // and trees earlier than data in input buffer ends. + ReadBorder=Math.Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1); + } + return ReadCode!=-1; +} + + +void UnpWriteBuf() +{ + size_t WrittenBorder=WrPtr; + size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; + size_t WriteSizeLeft=FullWriteSize; + bool NotAllFiltersProcessed=false; + //for (size_t I=0;I int + for (int I=0;I0) // We set it to 0 also for invalid filters. + { + uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; + + //x FilterSrcMemory.Alloc(BlockLength); + FilterSrcMemory = EnsureCapacity(FilterSrcMemory, checked((int)BlockLength)); + byte[] Mem= FilterSrcMemory; + if (BlockStart int + for (int J=I;J int + int EmptyCount=0; + // sharpcompress: size_t -> int + for (int I=0;I0) + Filters[I-EmptyCount]=Filters[I]; + if (Filters[I].Type==FILTER_NONE) + EmptyCount++; + } + if (EmptyCount>0) + //Filters.Alloc(Filters.Count-EmptyCount); + Filters.RemoveRange(Filters.Count-EmptyCount, EmptyCount); + + if (!NotAllFiltersProcessed) // Only if all filters are processed. + { + // Write data left after last filter. + UnpWriteArea(WrittenBorder,UnpPtr); + WrPtr=UnpPtr; + } + + // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE + // instead of potentially huge MaxWinSize blocks. It also allows us + // to keep the size of Filters array reasonable. + WriteBorder=(UnpPtr+Math.Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; + + // Choose the nearest among WriteBorder and WrPtr actual written border. + // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. + if (WriteBorder==UnpPtr || + WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) + WriteBorder=WrPtr; +} + + +byte[] ApplyFilter(byte[] __d,uint DataSize,UnpackFilter Flt) +{ + int Data = 0; + byte[] SrcData=__d; + switch(Flt.Type) + { + case FILTER_E8: + case FILTER_E8E9: + { + uint FileOffset=(uint)WrittenFileSize; + + const uint FileSize=0x1000000; + byte CmpByte2=Flt.Type==FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; + // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" + // to avoid overflow for DataSize<4. + for (uint CurPos=0;CurPos+4=0 + RawPut4(Addr+FileSize,__d,Data); + } + else + if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); + __d[D+2]=(byte)(Offset>>16); + } + } + } + return SrcData; + case FILTER_DELTA: + { + // Unlike RAR3, we do not need to reject excessive channel + // values here, since RAR5 uses only 5 bits to store channel. + uint Channels=Flt.Channels,SrcPos=0; + + //x FilterDstMemory.Alloc(DataSize); + FilterDstMemory = EnsureCapacity(FilterDstMemory, checked((int)DataSize)); + + byte[] DstData=FilterDstMemory; + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (uint CurChannel=0;CurChannel0) + { + size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); + //UnpWriteData(&FragWindow[StartPtr],BlockSize); + FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); + UnpWriteData(__buffer, __offset, BlockSize); + SizeToWrite-=BlockSize; + StartPtr=(StartPtr+BlockSize) & MaxWinMask; + } + } + else + if (EndPtr=DestUnpSize) + return; + size_t WriteSize=Size; + int64 LeftToWrite=DestUnpSize-WrittenFileSize; + if ((int64)WriteSize>LeftToWrite) + WriteSize=(size_t)LeftToWrite; + UnpIO_UnpWrite(Data, offset, WriteSize); + WrittenFileSize+=Size; +} + + +void UnpInitData50(bool Solid) +{ + if (!Solid) + TablesRead5=false; +} + + +bool ReadBlockHeader(BitInput Inp,ref UnpackBlockHeader Header) +{ + Header.HeaderSize=0; + + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7) + if (!UnpReadBuf()) + return false; + Inp.faddbits((uint)((8-Inp.InBit)&7)); + + byte BlockFlags=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + uint ByteCount=(uint)(((BlockFlags>>3)&3)+1); // Block size byte count. + + if (ByteCount==4) + return false; + + Header.HeaderSize=(int)(2+ByteCount); + + Header.BlockBitSize=(BlockFlags&7)+1; + + byte SavedCheckSum=(byte)(Inp.fgetbits()>>8); + Inp.faddbits(8); + + int BlockSize=0; + for (uint I=0;I>8)<<(int)(I*8)); + Inp.addbits(8); + } + + Header.BlockSize=BlockSize; + byte CheckSum=(byte)(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16)); + if (CheckSum!=SavedCheckSum) + return false; + + Header.BlockStart=Inp.InAddr; + ReadBorder=Math.Min(ReadBorder,Header.BlockStart+Header.BlockSize-1); + + Header.LastBlockInFile=(BlockFlags & 0x40)!=0; + Header.TablePresent=(BlockFlags & 0x80)!=0; + return true; +} + + +bool ReadTables(BitInput Inp,ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) +{ + if (!Header.TablePresent) + return true; + + if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) + if (!UnpReadBuf()) + return false; + + byte[] BitLength = new byte[BC]; + for (uint I=0;I> 12); + Inp.faddbits(4); + if (Length==15) + { + uint ZeroCount=(byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (ZeroCount==0) + BitLength[I]=15; + else + { + ZeroCount+=2; + while (ZeroCount-- > 0 && IReadTop-5) + if (!UnpReadBuf()) + return false; + uint Number=DecodeNumber(Inp,Tables.BD); + if (Number<16) + { + Table[I]=(byte)Number; + I++; + } + else + if (Number<18) + { + uint N; + if (Number==16) + { + N=(Inp.fgetbits() >> 13)+3; + Inp.faddbits(3); + } + else + { + N=(Inp.fgetbits() >> 9)+11; + Inp.faddbits(7); + } + if (I==0) + { + // We cannot have "repeat previous" code at the first position. + // Multiple such codes would shift Inp position without changing I, + // which can lead to reading beyond of Inp boundary in mutithreading + // mode, where Inp.ExternalBuffer disables bounds check and we just + // reserve a lot of buffer space to not need such check normally. + return false; + } + else + while (N-- > 0 && I> 13)+3; + Inp.faddbits(3); + } + else + { + N=(Inp.fgetbits() >> 9)+11; + Inp.faddbits(7); + } + while (N-- > 0 && IReadTop) + return false; + MakeDecodeTables(Table, 0, Tables.LD,NC); + MakeDecodeTables(Table, (int)NC,Tables.DD,DC); + MakeDecodeTables(Table, (int)(NC+DC),Tables.LDD,LDC); + MakeDecodeTables(Table, (int)(NC+DC+LDC),Tables.RD,RC); + return true; +} + + +void InitFilters() +{ + //Filters.SoftReset(); + Filters.Clear(); +} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs new file mode 100644 index 00000000..44abffa3 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -0,0 +1,380 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif + +using System; +using SharpCompress.Common; +using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal sealed partial class Unpack : BitInput + { + +public Unpack(/* ComprDataIO *DataIO */) +//:Inp(true),VMCodeInp(true) +: base(true) +{ + _UnpackCtor(); + + //UnpIO=DataIO; + Window=null; + Fragmented=false; + Suspended=false; + UnpAllBuf=false; + UnpSomeRead=false; +#if RarV2017_RAR_SMP + MaxUserThreads=1; + UnpThreadPool=CreateThreadPool(); + ReadBufMT=null; + UnpThreadData=null; +#endif + MaxWinSize=0; + MaxWinMask=0; + + // Perform initialization, which should be done only once for all files. + // It prevents crash if first DoUnpack call is later made with wrong + // (true) 'Solid' value. + UnpInitData(false); +#if !RarV2017_SFX_MODULE + // RAR 1.5 decompression initialization + UnpInitData15(false); + InitHuff(); +#endif +} + +// later: may need Dispose() if we support thread pool +//Unpack::~Unpack() +//{ +// InitFilters30(false); +// +// if (Window!=null) +// free(Window); +//#if RarV2017_RAR_SMP +// DestroyThreadPool(UnpThreadPool); +// delete[] ReadBufMT; +// delete[] UnpThreadData; +//#endif +//} + + +void Init(size_t WinSize,bool Solid) +{ + // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size + // will be 0 because of size_t overflow. Let's issue the memory error. + if (WinSize==0) + //ErrHandler.MemoryError(); + throw new InvalidFormatException("invalid window size (possibly due to a rar file with a 4GB being unpacked on a 32-bit platform)"); + + // Minimum window size must be at least twice more than maximum possible + // size of filter block, which is 0x10000 in RAR now. If window size is + // smaller, we can have a block with never cleared flt->NextWindow flag + // in UnpWriteBuf(). Minimum window size 0x20000 would be enough, but let's + // use 0x40000 for extra safety and possible filter area size expansion. + const size_t MinAllocSize=0x40000; + if (WinSize>16)>0x10000) // Window size must not exceed 4 GB. + return; + + // Archiving code guarantees that window size does not grow in the same + // solid stream. So if we are here, we are either creating a new window + // or increasing the size of non-solid window. So we could safely reject + // current window data without copying them to a new window, though being + // extra cautious, we still handle the solid window grow case below. + bool Grow=Solid && (Window!=null || Fragmented); + + // We do not handle growth for existing fragmented window. + if (Grow && Fragmented) + //throw std::bad_alloc(); + throw new InvalidFormatException("Grow && Fragmented"); + + byte[] NewWindow=Fragmented ? null : new byte[WinSize]; + + if (NewWindow==null) + if (Grow || WinSize<0x1000000) + { + // We do not support growth for new fragmented window. + // Also exclude RAR4 and small dictionaries. + //throw std::bad_alloc(); + throw new InvalidFormatException("Grow || WinSize<0x1000000"); + } + else + { + if (Window!=null) // If allocated by preceding files. + { + //free(Window); + Window=null; + } + FragWindow.Init(WinSize); + Fragmented=true; + } + + if (!Fragmented) + { + // Clean the window to generate the same output when unpacking corrupt + // RAR files, which may access unused areas of sliding dictionary. + // sharpcompress: don't need this, freshly allocated above + //memset(NewWindow,0,WinSize); + + + // If Window is not NULL, it means that window size has grown. + // In solid streams we need to copy data to a new window in such case. + // RAR archiving code does not allow it in solid streams now, + // but let's implement it anyway just in case we'll change it sometimes. + if (Grow) + for (size_t I=1;I<=MaxWinSize;I++) + NewWindow[(UnpPtr-I)&(WinSize-1)]=Window[(UnpPtr-I)&(MaxWinSize-1)]; + + //if (Window!=null) + // free(Window); + Window=NewWindow; + } + + MaxWinSize=WinSize; + MaxWinMask=MaxWinSize-1; +} + + +void DoUnpack(uint Method,bool Solid) +{ + // Methods <50 will crash in Fragmented mode when accessing NULL Window. + // They cannot be called in such mode now, but we check it below anyway + // just for extra safety. + switch(Method) + { +#if !RarV2017_SFX_MODULE + case 15: // rar 1.5 compression + if (!Fragmented) + Unpack15(Solid); + break; + case 20: // rar 2.x compression + case 26: // files larger than 2GB + if (!Fragmented) + Unpack20(Solid); + break; +#endif +#if !RarV2017_RAR5ONLY + case 29: // rar 3.x compression + if (!Fragmented) + throw new NotImplementedException(); + break; +#endif + case 50: // RAR 5.0 compression algorithm. +#if RarV2017_RAR_SMP + if (MaxUserThreads>1) + { +// We do not use the multithreaded unpack routine to repack RAR archives +// in 'suspended' mode, because unlike the single threaded code it can +// write more than one dictionary for same loop pass. So we would need +// larger buffers of unknown size. Also we do not support multithreading +// in fragmented window mode. + if (!Fragmented) + { + Unpack5MT(Solid); + break; + } + } +#endif + Unpack5(Solid); + break; +#if !Rar2017_NOSTRICT + default: throw new InvalidFormatException("unknown compression method " + Method); +#endif + } +} + + +void UnpInitData(bool Solid) +{ + if (!Solid) + { + Utility.Memset(this.OldDist, 0, this.OldDist.Length); + OldDistPtr=0; + LastDist=LastLength=0; +// memset(Window,0,MaxWinSize); + //memset(&BlockTables,0,sizeof(BlockTables)); + BlockTables = new UnpackBlockTables(); + // sharpcompress: no default ctor for struct + BlockTables.Init(); + UnpPtr=WrPtr=0; + WriteBorder=Math.Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; + } + // Filters never share several solid files, so we can safely reset them + // even in solid archive. + InitFilters(); + + Inp.InitBitInput(); + WrittenFileSize=0; + ReadTop=0; + ReadBorder=0; + + //memset(&BlockHeader,0,sizeof(BlockHeader)); + BlockHeader = new UnpackBlockHeader(); + BlockHeader.BlockSize=-1; // '-1' means not defined yet. +#if !RarV2017_SFX_MODULE + UnpInitData20(Solid); +#endif + //UnpInitData30(Solid); + UnpInitData50(Solid); +} + + +// LengthTable contains the length in bits for every element of alphabet. +// Dec is the structure to decode Huffman code/ +// Size is size of length table and DecodeNum field in Dec structure, +void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec,uint Size) +{ + // Size of alphabet and DecodePos array. + Dec.MaxNum=Size; + + // Calculate how many entries for every bit length in LengthTable we have. + uint[] LengthCount = new uint[16]; + //memset(LengthCount,0,sizeof(LengthCount)); + for (size_t I=0;IDecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + Utility.FillFast(Dec.DecodeNum, 0); + + // Initialize not really used entry for zero length code. + Dec.DecodePos[0]=0; + + // Start code for bit length 1 is 0. + Dec.DecodeLen[0]=0; + + // Right aligned upper limit code for current bit length. + uint UpperLimit=0; + + for (int I=1;I<16;I++) + { + // Adjust the upper limit code. + UpperLimit+=LengthCount[I]; + + // Left aligned upper limit code. + uint LeftAligned=UpperLimit<<(16-I); + + // Prepare the upper limit code for next bit length. + UpperLimit*=2; + + // Store the left aligned upper limit code. + Dec.DecodeLen[I]=(uint)LeftAligned; + + // Every item of this array contains the sum of all preceding items. + // So it contains the start position in code list for every bit length. + Dec.DecodePos[I]=Dec.DecodePos[I-1]+LengthCount[I-1]; + } + + // Prepare the copy of DecodePos. We'll modify this copy below, + // so we cannot use the original DecodePos. + uint[] CopyDecodePos = new uint[Dec.DecodePos.Length]; + //memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); + Array.Copy(Dec.DecodePos, 0, CopyDecodePos, 0, CopyDecodePos.Length); + + // For every bit length in the bit length table and so for every item + // of alphabet. + for (uint I=0;I=Dec.DecodeLen[CurBitLength]) + CurBitLength++; + + // Translation of right aligned bit string to bit length. + Dec.QuickLen[Code]=CurBitLength; + + // Prepare the table for quick translation of position in code list + // to position in alphabet. + + // Calculate the distance from the start code for current bit length. + uint Dist=BitField-Dec.DecodeLen[CurBitLength-1]; + + // Right align the distance. + Dist>>=(16-CurBitLength); + + // Now we can calculate the position in the code list. It is the sum + // of first position for current bit length and right aligned distance + // between our bit field and start code for current bit length. + uint Pos; + if (CurBitLength 0) + { + Window[UnpPtr++] = Window[SrcPtr++]; + } + +// byte *Src=Window+SrcPtr; +// byte *Dest=Window+UnpPtr; +// UnpPtr+=Length; +// +//#if FAST_MEMCPY +// if (Distance=8) +// { +// Dest[0]=Src[0]; +// Dest[1]=Src[1]; +// Dest[2]=Src[2]; +// Dest[3]=Src[3]; +// Dest[4]=Src[4]; +// Dest[5]=Src[5]; +// Dest[6]=Src[6]; +// Dest[7]=Src[7]; +// +// Src+=8; +// Dest+=8; +// Length-=8; +// } +//#if FAST_MEMCPY +// else +// while (Length>=8) +// { +// // In theory we still could overlap here. +// // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8). +// // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH +// // always, so overlap here is impossible. +// +// // This memcpy expanded inline by MSVC. We could also use uint64 +// // assignment, which seems to provide about the same speed. +// memcpy(Dest,Src,8); +// +// Src+=8; +// Dest+=8; +// Length-=8; +// } +//#endif +// +// // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s. +// if (Length>0) { Dest[0]=Src[0]; +// if (Length>1) { Dest[1]=Src[1]; +// if (Length>2) { Dest[2]=Src[2]; +// if (Length>3) { Dest[3]=Src[3]; +// if (Length>4) { Dest[4]=Src[4]; +// if (Length>5) { Dest[5]=Src[5]; +// if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s. + } + else + while (Length-- > 0) // Slow copying with all possible precautions. + { + Window[UnpPtr]=Window[SrcPtr++ & MaxWinMask]; + // We need to have masked UnpPtr after quit from loop, so it must not + // be replaced with 'Window[UnpPtr++ & MaxWinMask]' + UnpPtr=(UnpPtr+1) & MaxWinMask; + } +} + + +uint DecodeNumber(BitInput Inp,DecodeTable Dec) +{ + // Left aligned 15 bit length raw bit field. + uint BitField=Inp.getbits() & 0xfffe; + + if (BitField>(int)(16-Dec.QuickBits); + Inp.addbits(Dec.QuickLen[Code]); + return Dec.QuickNum[Code]; + } + + // Detect the real bit length for current code. + uint Bits=15; + for (uint I=Dec.QuickBits+1;I<15;I++) + if (BitField>=(int)(16-Bits); + + // Now we can calculate the position in the code list. It is the sum + // of first position for current bit length and right aligned distance + // between our bit field and start code for current bit length. + uint Pos=Dec.DecodePos[Bits]+Dist; + + // Out of bounds safety check required for damaged archives. + if (Pos>=Dec.MaxNum) + Pos=0; + + // Convert the position in the code list to position in alphabet + // and return it. + return Dec.DecodeNum[Pos]; +} + + +uint SlotToLength(BitInput Inp,uint Slot) +{ + uint LBits,Length=2; + if (Slot<8) + { + LBits=0; + Length+=Slot; + } + else + { + LBits=Slot/4-1; + Length+=(4 | (Slot & 3)) << (int)LBits; + } + + if (LBits>0) + { + Length+=Inp.getbits()>>(int)(16-LBits); + Inp.addbits(LBits); + } + return Length; +} + + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/notes.txt b/src/SharpCompress/Compressors/Rar/UnpackV2017/notes.txt new file mode 100644 index 00000000..d56277c2 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/notes.txt @@ -0,0 +1,50 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + + +notes on C->C# primitive mappings: + +nint := native integer +nuint := native unsigned integer + +type 32b 64b mapping CAREFUL! +char 8 bit 8 bit short +unsigned char 8 bit 8 bit ushort +short int 16 bit 16 bit short + +short int 16 bit 16 bit short +unsigned short int 16 bit 16 bit ushort +int 32 bit 32 bit int +unsigned int 32 bit 32 bit uint +long int 32 bit 64 bit nint *** +unsigned long int 32 bit 64 bit nuint *** +long long int 64 bit 64 bit long +unsigned long long int 64 bit 64 bit ulong +size_t 32 bit 64 bit nuint + +The size_t type is the unsigned integer type that is the result of the sizeof operator (and the offsetof operator), +so it is guaranteed to be big enough to contain the size of the biggest object your system can handle (e.g., a static array of 8Gb). +[size_t] -> ulong (x64) +[size_t] -> uint (x86) + +size_t is an unsigned data type defined by several C/C++ standards, e.g. the C99 ISO/IEC 9899 standard, that is defined +in stddef.h.1 It can be further imported by inclusion of stdlib.h as this file internally sub includes stddef.h. +This type is used to represent the size of an object. Library functions that take or return sizes expect them to be of type or +have the return type of size_t. Further, the most frequently used compiler-based operator sizeof should evaluate to a constant +value that is compatible with size_t. + + +20171218 +urggh, this allows things like new int[int.MaxValue] but NOT new byte[uint.MaxValue] +currently arrays are limited to being indexed by an int hence int.MaxValue entries. weak. +To get arrays > 2GB on x64 we need to configure + +https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element \ No newline at end of file diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs new file mode 100644 index 00000000..06520ab5 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -0,0 +1,443 @@ +#if !Rar2017_64bit +using nint = System.Int32; +using nuint = System.UInt32; +using size_t = System.UInt32; +#else +using nint = System.Int64; +using nuint = System.UInt64; +using size_t = System.UInt64; +#endif +using int64 = System.Int64; + +using System.Collections.Generic; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; + +// TODO: REMOVE THIS... WIP +#pragma warning disable 169 +#pragma warning disable 414 + +namespace SharpCompress.Compressors.Rar.UnpackV2017 +{ + internal static class UnpackGlobal + { + + +// Maximum allowed number of compressed bits processed in quick mode. +public const int MAX_QUICK_DECODE_BITS =10; + +// Maximum number of filters per entire data block. Must be at least +// twice more than MAX_PACK_FILTERS to store filters from two data blocks. +public const int MAX_UNPACK_FILTERS =8192; + +// Maximum number of filters per entire data block for RAR3 unpack. +// Must be at least twice more than v3_MAX_PACK_FILTERS to store filters +// from two data blocks. +public const int MAX3_UNPACK_FILTERS =8192; + +// Limit maximum number of channels in RAR3 delta filter to some reasonable +// value to prevent too slow processing of corrupt archives with invalid +// channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. +// No need to provide it for RAR5, which uses only 5 bits to store channels. +private const int MAX3_UNPACK_CHANNELS =1024; + +// Maximum size of single filter block. We restrict it to limit memory +// allocation. Must be equal or larger than MAX_ANALYZE_SIZE. +public const int MAX_FILTER_BLOCK_SIZE =0x400000; + +// Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, +// so we keep number of buffered filter in unpacker reasonable. +public const int UNPACK_MAX_WRITE =0x400000; + } + +// Decode compressed bit fields to alphabet numbers. +sealed class DecodeTable +{ + // Real size of DecodeNum table. + public uint MaxNum; + + // Left aligned start and upper limit codes defining code space + // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of + // range for bit length and DecodeLen[BitLength] defines next code + // after the end of range or in other words the upper limit code + // for specified bit length. + public readonly uint[] DecodeLen = new uint[16]; + + // Every item of this array contains the sum of all preceding items. + // So it contains the start position in code list for every bit length. + public readonly uint[] DecodePos = new uint[16]; + + // Number of compressed bits processed in quick mode. + // Must not exceed MAX_QUICK_DECODE_BITS. + public uint QuickBits; + + // Translates compressed bits (up to QuickBits length) + // to bit length in quick mode. + public readonly byte[] QuickLen = new byte[1< Filters = new List(); + + readonly uint[] OldDist = new uint[4]; + uint OldDistPtr; + uint LastLength; + + // LastDist is necessary only for RAR2 and older with circular OldDist + // array. In RAR3 last distance is always stored in OldDist[0]. + uint LastDist; + + size_t UnpPtr,WrPtr; + + // Top border of read packed data. + int ReadTop; + + // Border to call UnpReadBuf. We use it instead of (ReadTop-C) + // for optimization reasons. Ensures that we have C bytes in buffer + // unless we are at the end of file. + int ReadBorder; + + UnpackBlockHeader BlockHeader; + UnpackBlockTables BlockTables; + + size_t WriteBorder; + + byte[] Window; + + readonly FragmentedWindow FragWindow = new FragmentedWindow(); + bool Fragmented; + + + int64 DestUnpSize; + + //bool Suspended; + bool UnpAllBuf; + bool UnpSomeRead; + int64 WrittenFileSize; + bool FileExtracted; + + +/***************************** Unpack v 1.5 *********************************/ + //void Unpack15(bool Solid); + //void ShortLZ(); + //void LongLZ(); + //void HuffDecode(); + //void GetFlagsBuf(); + //void UnpInitData15(int Solid); + //void InitHuff(); + //void CorrHuff(ushort *CharSet,byte *NumToPlace); + //void CopyString15(uint Distance,uint Length); + //uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab); + + readonly ushort[] ChSet = new ushort[256],ChSetA = new ushort[256],ChSetB = new ushort[256],ChSetC = new ushort[256]; + readonly byte[] NToPl = new byte[256],NToPlB = new byte[256],NToPlC = new byte[256]; + uint FlagBuf,AvrPlc,AvrPlcB,AvrLn1,AvrLn2,AvrLn3; + int Buf60,NumHuf,StMode,LCount,FlagsCnt; + uint Nhfb,Nlzb,MaxDist3; +/***************************** Unpack v 1.5 *********************************/ + +/***************************** Unpack v 2.0 *********************************/ + //void Unpack20(bool Solid); + + DecodeTable[] MD = new DecodeTable[4]; // Decode multimedia data, up to 4 channels. + + readonly byte[] UnpOldTable20 = new byte[MC20*4]; + bool UnpAudioBlock; + uint UnpChannels,UnpCurChannel; + int UnpChannelDelta; + //void CopyString20(uint Length,uint Distance); + //bool ReadTables20(); + //void UnpWriteBuf20(); + //void UnpInitData20(int Solid); + //void ReadLastTables(); + //byte DecodeAudio(int Delta); + AudioVariables[] AudV = new AudioVariables[4]; +/***************************** Unpack v 2.0 *********************************/ + +/***************************** Unpack v 3.0 *********************************/ + public const int BLOCK_LZ = 0; + public const int BLOCK_PPM = 1; + + //void UnpInitData30(bool Solid); + //void Unpack29(bool Solid); + //void InitFilters30(bool Solid); + //bool ReadEndOfBlock(); + //bool ReadVMCode(); + //bool ReadVMCodePPM(); + //bool AddVMCode(uint FirstByte,byte *Code,int CodeSize); + //int SafePPMDecodeChar(); + //bool ReadTables30(); + //bool UnpReadBuf30(); + //void UnpWriteBuf30(); + //void ExecuteCode(VM_PreparedProgram *Prg); + + int PrevLowDist,LowDistRepCount; + +/*#if !RarV2017_RAR5ONLY + ModelPPM PPM; +#endif*/ + int PPMEscChar; + + readonly byte [] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; + int UnpBlockType; + + // If we already read decoding tables for Unpack v2,v3,v5. + // We should not use a single variable for all algorithm versions, + // because we can have a corrupt archive with one algorithm file + // followed by another algorithm file with "solid" flag and we do not + // want to reuse tables from one algorithm in another. + bool TablesRead2,TablesRead3,TablesRead5; + + // Virtual machine to execute filters code. +/*#if !RarV2017_RAR5ONLY + RarVM VM; +#endif*/ + + // Buffer to read VM filters code. We moved it here from AddVMCode + // function to reduce time spent in BitInput constructor. + readonly BitInput VMCodeInp = new BitInput(true); + + // Filters code, one entry per filter. + readonly List Filters30 = new List(); + + // Filters stack, several entrances of same filter are possible. + readonly List PrgStack = new List(); + + // Lengths of preceding data blocks, one length of one last block + // for every filter. Used to reduce the size required to write + // the data block length if lengths are repeating. + readonly List OldFilterLengths = new List(); + + int LastFilter; +/***************************** Unpack v 3.0 *********************************/ + + //Unpack(ComprDataIO *DataIO); + //~Unpack(); + //void Init(size_t WinSize,bool Solid); + //void DoUnpack(uint Method,bool Solid); + bool IsFileExtracted() {return(FileExtracted);} + void SetDestSize(int64 DestSize) {DestUnpSize=DestSize;FileExtracted=false;} + void SetSuspended(bool Suspended) {this.Suspended=Suspended;} + +#if RarV2017_RAR_SMP + // More than 8 threads are unlikely to provide a noticeable gain + // for unpacking, but would use the additional memory. + void SetThreads(uint Threads) {MaxUserThreads=Min(Threads,8);} + + void UnpackDecode(UnpackThreadData &D); +#endif + + size_t MaxWinSize; + size_t MaxWinMask; + + uint GetChar() + { + if (Inp.InAddr>BitInput.MAX_SIZE-30) + UnpReadBuf(); + return(Inp.InBuf[Inp.InAddr++]); + } + + + } +} diff --git a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs index 74c525b2..3d1fd979 100644 --- a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs +++ b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs @@ -5,8 +5,14 @@ namespace SharpCompress.Compressors.Rar.VM /// the max size of the input internal const int MAX_SIZE = 0x8000; - protected int inAddr; - protected int inBit; + public int inAddr; + public int inBit; + +// TODO: rename var + public int InAddr { get { return this.inAddr; } set { this.inAddr = value; } } + public int InBit { get { return this.inBit; } set { this.inBit = value; } } + public bool ExternalBuffer; + /// internal BitInput() @@ -22,6 +28,11 @@ namespace SharpCompress.Compressors.Rar.VM inBit = 0; } + internal void faddbits(uint bits) { + // TODO uint + AddBits((int)bits); + } + /// /// also named faddbits /// @@ -33,6 +44,16 @@ namespace SharpCompress.Compressors.Rar.VM inBit = bits & 7; } + internal uint fgetbits() { + // TODO uint + return (uint)GetBits(); + } + + internal uint getbits() { + // TODO uint + return (uint)GetBits(); + } + /// /// (also named fgetbits) /// diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index aa7c820b..e1037995 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using SharpCompress.Converters; namespace SharpCompress.IO @@ -36,12 +35,22 @@ namespace SharpCompress.IO public override bool ReadBoolean() { - return ReadBytes(1).Single() != 0; + return ReadByte() != 0; } + // NOTE: there is a somewhat fragile dependency on the internals of this class + // with RarCrcBinaryReader and RarCryptoBinaryReader. + // + // RarCrcBinaryReader/RarCryptoBinaryReader need to override any specific methods + // that call directly to the base BinaryReader and do not delegate to other methods + // in this class so that it can track the each byte being read. + // + // if altering this class in a way that changes the implementation be sure to + // update RarCrcBinaryReader/RarCryptoBinaryReader. public override byte ReadByte() { - return ReadBytes(1).Single(); + CurrentReadByteCount++; + return base.ReadByte(); } public override byte[] ReadBytes(int count) @@ -121,5 +130,77 @@ namespace SharpCompress.IO { return DataConverter.LittleEndian.GetUInt64(ReadBytes(8), 0); } + + // RAR5 style variable length encoded value + // maximum value of 0xffffffffffffffff (64 bits) + // technote: "implies max 10 bytes consumed" -- but not really because we could extend indefinitely using 0x80 0x80 ... 0x80 00 + // + // Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data + // and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence. + // So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present, + // contains next 7 bits and so on. + public ulong ReadRarVInt(int maxBytes = 10) { + // hopefully this gets inlined + return DoReadRarVInt((maxBytes - 1) * 7); + } + + private ulong DoReadRarVInt(int maxShift) { + int shift = 0; + ulong result = 0; + do { + byte b0 = ReadByte(); + uint b1 = ((uint)b0) & 0x7f; + ulong n = b1; + ulong shifted = n << shift; + if (n != shifted >> shift) { + // overflow + break; + } + result |= shifted; + if (b0 == b1) { + return result; + } + shift += 7; + } while (shift <= maxShift); + + throw new FormatException("malformed vint"); + } + + public uint ReadRarVIntUInt32(int maxBytes = 5) { + // hopefully this gets inlined + return DoReadRarVIntUInt32((maxBytes - 1) * 7); + } + + public ushort ReadRarVIntUInt16(int maxBytes = 3) { + // hopefully this gets inlined + return checked((ushort)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + } + + public byte ReadRarVIntByte(int maxBytes = 2) { + // hopefully this gets inlined + return checked((byte)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + } + + private uint DoReadRarVIntUInt32(int maxShift) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + uint b1 = ((uint)b0) & 0x7f; + uint n = b1; + uint shifted = n << shift; + if (n != shifted >> shift) { + // overflow + break; + } + result |= shifted; + if (b0 == b1) { + return result; + } + shift += 7; + } while (shift <= maxShift); + + throw new FormatException("malformed vint"); + } } } \ No newline at end of file diff --git a/src/SharpCompress/Lazy.cs b/src/SharpCompress/Lazy.cs new file mode 100644 index 00000000..893e3bb1 --- /dev/null +++ b/src/SharpCompress/Lazy.cs @@ -0,0 +1,29 @@ +using System; + +namespace SharpCompress +{ + public class Lazy + { + private readonly Func _lazyFunc; + private bool _evaluated; + private T _value; + + public Lazy(Func lazyFunc) + { + _lazyFunc = lazyFunc; + } + + public T Value + { + get + { + if (!_evaluated) + { + _value = _lazyFunc(); + _evaluated = true; + } + return _value; + } + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index 0424f21a..b09497f1 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -46,11 +46,8 @@ namespace SharpCompress.Readers public void Dispose() { - if (entriesForCurrentReadStream != null) - { - entriesForCurrentReadStream.Dispose(); - } - Volume.Dispose(); + entriesForCurrentReadStream?.Dispose(); + Volume?.Dispose(); } #endregion diff --git a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs index 3de56052..558249e7 100644 --- a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs @@ -33,14 +33,9 @@ namespace SharpCompress.Readers.Rar internal override bool NextEntryForCurrentStream() { - if (!base.NextEntryForCurrentStream()) - { - //if we're got another stream to try to process then do so - if (streams.MoveNext() && LoadStreamForReading(streams.Current)) - { - return true; - } - return false; + if (!base.NextEntryForCurrentStream()) { + // if we're got another stream to try to process then do so + return this.streams.MoveNext() && LoadStreamForReading(this.streams.Current); } return true; } @@ -94,7 +89,7 @@ namespace SharpCompress.Readers.Rar return true; } - if (!reader.Entry.IsSplit) + if (!reader.Entry.IsSplitAfter) { return false; } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index d82de9cf..a40dca44 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Common; @@ -13,7 +13,8 @@ namespace SharpCompress.Readers.Rar public abstract class RarReader : AbstractReader { private RarVolume volume; - private readonly Unpack pack = new Unpack(); + internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); + internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); internal RarReader(ReaderOptions options) : base(options, ArchiveType.Rar) @@ -24,8 +25,6 @@ namespace SharpCompress.Readers.Rar public override RarVolume Volume => volume; - #region Open - /// /// Opens a RarReader for Non-seeking usage with a single volume /// @@ -50,8 +49,6 @@ namespace SharpCompress.Readers.Rar return new MultiVolumeRarReader(streams, options ?? new ReaderOptions()); } - #endregion - protected override IEnumerable GetEntries(Stream stream) { volume = new RarReaderVolume(stream, Options); @@ -67,11 +64,14 @@ namespace SharpCompress.Readers.Rar return Entry.Parts; } - protected override EntryStream GetEntryStream() + protected override EntryStream GetEntryStream() { - return CreateEntryStream(new RarCrcStream(pack, Entry.FileHeader, - new MultiVolumeReadOnlyStream( - CreateFilePartEnumerableForCurrentEntry().Cast(), this))); + var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); + if (Entry.IsRarV3) + { + return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); + } + return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); } } } diff --git a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs index bf68043f..98a8c5c0 100644 --- a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs +++ b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs @@ -13,7 +13,7 @@ namespace SharpCompress.Readers.Rar { } - internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader) + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) { return new NonSeekableStreamFilePart(markHeader, fileHeader); } diff --git a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs index f98ec9b4..b8d64f8d 100644 --- a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs @@ -16,10 +16,9 @@ namespace SharpCompress.Readers.Rar internal override void ValidateArchive(RarVolume archive) { - if (archive.IsMultiVolume) - { - throw new MultiVolumeExtractionException( - "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."); + if (archive.IsMultiVolume) { + var msg = "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."; + throw new MultiVolumeExtractionException(msg); } } diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index ae6587f6..e97d093d 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -2,6 +2,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; #if NETCORE using SharpCompress.Buffers; #endif @@ -73,6 +76,71 @@ namespace SharpCompress } } +#if NET45 + // super fast memset, up to 40x faster than for loop on large arrays + // see https://stackoverflow.com/questions/1897555/what-is-the-equivalent-of-memset-in-c + private static readonly Action MemsetDelegate = CreateMemsetDelegate(); + + private static Action CreateMemsetDelegate() { + var dynamicMethod = new DynamicMethod( + "Memset", + MethodAttributes.Public | MethodAttributes.Static, + CallingConventions.Standard, + null, + new[] { typeof(IntPtr), typeof(byte), typeof(uint) }, + typeof(Utility), + true); + var generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Initblk); + generator.Emit(OpCodes.Ret); + return (Action)dynamicMethod.CreateDelegate(typeof(Action)); + } + + public static void Memset(byte[] array, byte what, int length) + { + var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, (uint)length); + gcHandle.Free(); + } +#else + public static void Memset(byte[] array, byte what, int length) + { + for(var i = 0; i < length; i++) + { + array[i] = what; + } + } +#endif + + public static void Memset(T[] array, T what, int length) + { + for(var i = 0; i < length; i++) + { + array[i] = what; + } + } + + public static void FillFast(T[] array, T val) where T : struct + { + for (int i = 0; i < array.Length; i++) + { + array[i] = val; + } + } + + public static void FillFast(T[] array, int start, int length, T val) where T : struct + { + int toIndex = start + length; + for (int i = start; i < toIndex; i++) + { + array[i] = val; + } + } + + /// /// Fills the array with an specific value. /// @@ -116,6 +184,18 @@ namespace SharpCompress action(item); } } + + public static void Copy(Array sourceArray, long sourceIndex, Array destinationArray, long destinationIndex, long length) + { + if (sourceIndex > Int32.MaxValue || sourceIndex < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + if (destinationIndex > Int32.MaxValue || destinationIndex < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + if (length > Int32.MaxValue || length < Int32.MinValue) + throw new ArgumentOutOfRangeException(); + + Array.Copy(sourceArray, (int)sourceIndex, destinationArray, (int)destinationIndex, (int)length); + } public static IEnumerable AsEnumerable(this T item) { @@ -252,9 +332,15 @@ namespace SharpCompress (UInt16)(iTime % 65536)); } - public static DateTime DosDateToDateTime(Int32 iTime) + /// + /// Convert Unix time value to a DateTime object. + /// + /// The Unix time stamp you want to convert to DateTime. + /// Returns a DateTime object that represents value of the Unix time. + public static DateTime UnixTimeToDateTime(long unixtime) { - return DosDateToDateTime((UInt32)iTime); + DateTime sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return sTime.AddSeconds(unixtime); } public static long TransferTo(this Stream source, Stream destination) @@ -353,10 +439,5 @@ namespace SharpCompress } return true; } - - public static void CopyTo(this byte[] array, byte[] destination, int index) - { - Array.Copy(array, 0, destination, index, array.Length); - } } } \ No newline at end of file diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 4ce9f872..a5413891 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -108,6 +108,7 @@ namespace SharpCompress.Test testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); ArchiveFileRead(testArchive.AsEnumerable(), readerOptions); } + protected void ArchiveFileRead(IEnumerable testArchives, ReaderOptions readerOptions = null) { foreach (var path in testArchives) diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index e227a74e..72bd222e 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -14,22 +14,38 @@ namespace SharpCompress.Test.Rar public void Rar_EncryptedFileAndHeader_Archive() { ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); - } + /*[Fact] + public void Rar5_EncryptedFileAndHeader_Archive() + { + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ + [Fact] public void Rar_EncryptedFileOnly_Archive() { ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); - } + /*[Fact] + public void Rar5_EncryptedFileOnly_Archive() + { + ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); + }*/ + [Fact] public void Rar_Encrypted_Archive() { - ReadRarPassword("Encrypted.rar", "test"); + ReadRarPassword("Rar.Encrypted.rar", "test"); } + /*[Fact] + public void Rar5_Encrypted_Archive() + { + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ + private void ReadRarPassword(string testArchive, string password) { ResetScratch(); @@ -59,7 +75,7 @@ namespace SharpCompress.Test.Rar [Fact] public void Rar_Multi_Archive_Encrypted() { - Assert.Throws(() => ArchiveFileReadPassword("EncryptedParts.part01.rar", "test")); + Assert.Throws(() => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test")); } protected void ArchiveFileReadPassword(string archiveName, string password) @@ -90,6 +106,12 @@ namespace SharpCompress.Test.Rar ArchiveStreamRead("Rar.none.rar"); } + [Fact] + public void Rar5_None_ArchiveStreamRead() + { + ArchiveStreamRead("Rar5.none.rar"); + } + [Fact] public void Rar_ArchiveStreamRead() { @@ -97,10 +119,20 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_test_invalid_exttime_ArchiveStreamRead() + public void Rar5_ArchiveStreamRead() + { + ArchiveStreamRead("Rar5.rar"); + } + + [Fact] + public void Rar_test_invalid_exttime_ArchiveStreamRead() { + DoRar_test_invalid_exttime_ArchiveStreamRead("Rar.test_invalid_exttime.rar"); + } + + private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "test_invalid_exttime.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { using (var archive = ArchiveFactory.Open(stream)) { @@ -120,7 +152,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_ArchiveStreamRead() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) { using (var archive = RarArchive.Open(stream, new ReaderOptions() { @@ -142,17 +174,26 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsSolidArchiveCheck() + public void Rar_IsSolidArchiveCheck() { + DoRar_IsSolidArchiveCheck("Rar.rar"); + } + + [Fact] + public void Rar5_IsSolidArchiveCheck() { + DoRar_IsSolidArchiveCheck("Rar5.rar"); + } + + private void DoRar_IsSolidArchiveCheck(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) { using (var archive = RarArchive.Open(stream)) { Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory(SCRATCH_FILES_PATH, + entry.WriteToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, @@ -170,6 +211,12 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveStreamRead("Rar.solid.rar")); } + [Fact] + public void Rar5_Solid_ArchiveStreamRead() + { + Assert.Throws(() => ArchiveStreamRead("Rar5.solid.rar")); + } + [Fact] public void Rar_Solid_StreamRead_Extract_All() { @@ -177,23 +224,42 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_ArchiveStreamRead() + public void Rar5_Solid_StreamRead_Extract_All() { - var testArchives = new string[] { "Rar.multi.part01.rar", + ArchiveStreamReadExtractAll("Rar5.solid.rar", CompressionType.Rar); + } + + [Fact] + public void Rar_Multi_ArchiveStreamRead() { + DoRar_Multi_ArchiveStreamRead(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_ArchiveStreamRead() { + DoRar_Multi_ArchiveStreamRead(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_ArchiveStreamRead(string[] archives) + { ResetScratch(); - using (var archive = RarArchive.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var archive = RarArchive.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(File.OpenRead))) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + entry.WriteToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -209,6 +275,12 @@ namespace SharpCompress.Test.Rar ArchiveFileRead("Rar.none.rar"); } + [Fact] + public void Rar5NoneArchiveFileRead() + { + ArchiveFileRead("Rar5.none.rar"); + } + [Fact] public void Rar_ArchiveFileRead() { @@ -216,10 +288,25 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_ArchiveFileRead_HasDirectories() + public void Rar5_ArchiveFileRead() + { + ArchiveFileRead("Rar5.rar"); + } + + [Fact] + public void Rar_ArchiveFileRead_HasDirectories() { + DoRar_ArchiveFileRead_HasDirectories("Rar.rar"); + } + + [Fact] + public void Rar5_ArchiveFileRead_HasDirectories() { + DoRar_ArchiveFileRead_HasDirectories("Rar5.rar"); + } + + private void DoRar_ArchiveFileRead_HasDirectories(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { using (var archive = RarArchive.Open(stream)) { @@ -233,7 +320,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_ArchiveFileRead() { ResetScratch(); - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"), new ReaderOptions() + using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions() { LookForHeader = true })) @@ -256,6 +343,12 @@ namespace SharpCompress.Test.Rar Assert.Throws(() => ArchiveFileRead("Rar.solid.rar")); } + [Fact] + public void Rar5_Solid_ArchiveFileRead() + { + Assert.Throws(() => ArchiveFileRead("Rar5.solid.rar")); + } + [Fact] public void Rar_Multi_ArchiveFileRead() { @@ -263,9 +356,24 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsFirstVolume_True() + public void Rar5_Multi_ArchiveFileRead() { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part01.rar"))) + ArchiveFileRead("Rar5.multi.part01.rar"); + } + + [Fact] + public void Rar_IsFirstVolume_True() { + DoRar_IsFirstVolume_True("Rar.multi.part01.rar"); + } + + [Fact] + public void Rar5_IsFirstVolume_True() { + DoRar_IsFirstVolume_True("Rar5.multi.part01.rar"); + } + + private void DoRar_IsFirstVolume_True(string firstFilename) + { + using (var archive = RarArchive.Open(Path.Combine(this.TEST_ARCHIVES_PATH, firstFilename))) { Assert.True(archive.IsMultipartVolume()); Assert.True(archive.IsFirstVolume()); @@ -273,9 +381,18 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_IsFirstVolume_False() + public void Rar_IsFirstVolume_False() { + DoRar_IsFirstVolume_False("Rar.multi.part03.rar"); + } + + [Fact] + public void Rar5_IsFirstVolume_False() { + DoRar_IsFirstVolume_False("Rar5.multi.part03.rar"); + } + + private void DoRar_IsFirstVolume_False(string notFirstFilename) { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part03.rar"))) + using (var archive = RarArchive.Open(Path.Combine(this.TEST_ARCHIVES_PATH, notFirstFilename))) { Assert.True(archive.IsMultipartVolume()); Assert.False(archive.IsFirstVolume()); diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index 29309cff..c2732522 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -16,21 +16,33 @@ namespace SharpCompress.Test.Rar public RarHeaderFactoryTest() { ResetScratch(); - rarHeaderFactory = new RarHeaderFactory(StreamingMode.Seekable, new ReaderOptions() - { - LeaveStreamOpen = true - }); + rarHeaderFactory = new RarHeaderFactory( + StreamingMode.Seekable, + new ReaderOptions { LeaveStreamOpen = true }); } + [Fact] + public void Rar_ReadHeaders_RecognizeEncryptedFlag() + { + ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); + } [Fact] - public void ReadHeaders_RecognizeEncryptedFlag() + public void Rar5_ReadHeaders_RecognizeEncryptedFlag() { + ReadEncryptedFlag("Rar5.encrypted_filesAndHeader.rar", true); + } - ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); - - + [Fact] + public void Rar_ReadHeaders_RecognizeNoEncryptedFlag() + { + ReadEncryptedFlag("Rar.rar", false); + } + [Fact] + public void Rar5_ReadHeaders_RecognizeNoEncryptedFlag() + { + ReadEncryptedFlag("Rar5.rar", false); } private void ReadEncryptedFlag(string testArchive, bool isEncrypted) @@ -39,7 +51,7 @@ namespace SharpCompress.Test.Rar { foreach (var header in rarHeaderFactory.ReadHeaders(stream)) { - if (header.HeaderType == HeaderType.ArchiveHeader) + if (header.HeaderType == HeaderType.Archive || header.HeaderType == HeaderType.Crypt) { Assert.Equal(isEncrypted, rarHeaderFactory.IsEncrypted); break; @@ -48,16 +60,9 @@ namespace SharpCompress.Test.Rar } } - [Fact] - public void ReadHeaders_RecognizeNoEncryptedFlag() - { - ReadEncryptedFlag("Rar.rar", false); - } - private FileStream GetReaderStream(string testArchive) { - return new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive), - FileMode.Open); + return new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive), FileMode.Open); } } } diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index aa6d5aa0..a4101c64 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -10,23 +10,36 @@ namespace SharpCompress.Test.Rar public class RarReaderTests : ReaderTests { [Fact] - public void Rar_Multi_Reader() - { - var testArchives = new string[] { "Rar.multi.part01.rar", + public void Rar_Multi_Reader() { + DoRar_Multi_Reader(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_Reader() { + DoRar_Multi_Reader(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_Reader(string[] archives) + { ResetScratch(); - using (var reader = RarReader.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var reader = RarReader.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)))) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -37,20 +50,22 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_Reader_Encrypted() + public void Rar_Multi_Reader_Encrypted() { + DoRar_Multi_Reader_Encrypted(new string[] { + "Rar.EncryptedParts.part01.rar", + "Rar.EncryptedParts.part02.rar", + "Rar.EncryptedParts.part03.rar", + "Rar.EncryptedParts.part04.rar", + "Rar.EncryptedParts.part05.rar", + "Rar.EncryptedParts.part06.rar"}); + } + + private void DoRar_Multi_Reader_Encrypted(string[] archives) { - var testArchives = new string[] { "EncryptedParts.part01.rar", - "EncryptedParts.part02.rar", - "EncryptedParts.part03.rar", - "EncryptedParts.part04.rar", - "EncryptedParts.part05.rar", - "EncryptedParts.part06.rar"}; - - Assert.Throws(() => { ResetScratch(); - using (var reader = RarReader.Open(testArchives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + using (var reader = RarReader.Open(archives.Select(s => Path.Combine(this.TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)), new ReaderOptions() { @@ -59,7 +74,7 @@ namespace SharpCompress.Test.Rar { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, @@ -72,28 +87,41 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Multi_Reader_Delete_Files() - { - var testArchives = new string[] { "Rar.multi.part01.rar", + public void Rar_Multi_Reader_Delete_Files() { + DoRar_Multi_Reader_Delete_Files(new string[] { + "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}; + "Rar.multi.part06.rar"}); + } + [Fact] + public void Rar5_Multi_Reader_Delete_Files() { + DoRar_Multi_Reader_Delete_Files(new string[] { + "Rar5.multi.part01.rar", + "Rar5.multi.part02.rar", + "Rar5.multi.part03.rar", + "Rar5.multi.part04.rar", + "Rar5.multi.part05.rar", + "Rar5.multi.part06.rar"}); + } + private void DoRar_Multi_Reader_Delete_Files(string[] archives) + { ResetScratch(); - foreach (var file in testArchives) + foreach (var file in archives) { - File.Copy(Path.Combine(TEST_ARCHIVES_PATH, file), Path.Combine(SCRATCH2_FILES_PATH, file)); + File.Copy(Path.Combine(this.TEST_ARCHIVES_PATH, file), Path.Combine(this.SCRATCH2_FILES_PATH, file)); } - var streams = testArchives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)).Select(File.OpenRead).ToList(); + var streams = archives.Select(s => Path.Combine(this.SCRATCH2_FILES_PATH, s)).Select(File.OpenRead).ToList(); using (var reader = RarReader.Open(streams)) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -106,7 +134,7 @@ namespace SharpCompress.Test.Rar } VerifyFiles(); - foreach (var file in testArchives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))) + foreach (var file in archives.Select(s => Path.Combine(this.SCRATCH2_FILES_PATH, s))) { File.Delete(file); } @@ -118,32 +146,60 @@ namespace SharpCompress.Test.Rar Read("Rar.none.rar", CompressionType.Rar); } + [Fact] + public void Rar5_None_Reader() + { + Read("Rar5.none.rar", CompressionType.Rar); + } + [Fact] public void Rar_Reader() { Read("Rar.rar", CompressionType.Rar); } + [Fact] + public void Rar5_Reader() + { + Read("Rar5.rar", CompressionType.Rar); + } + [Fact] public void Rar_EncryptedFileAndHeader_Reader() { ReadRar("Rar.encrypted_filesAndHeader.rar", "test"); - } + /*[Fact] + public void Rar5_EncryptedFileAndHeader_Reader() + { + ReadRar("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ + [Fact] public void Rar_EncryptedFileOnly_Reader() { ReadRar("Rar.encrypted_filesOnly.rar", "test"); - } + /*[Fact] + public void Rar5_EncryptedFileOnly_Reader() + { + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); + }*/ + [Fact] public void Rar_Encrypted_Reader() { - ReadRar("Encrypted.rar", "test"); + ReadRar("Rar.Encrypted.rar", "test"); } + /*[Fact] + public void Rar5_Encrypted_Reader() + { + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); + }*/ + private void ReadRar(string testArchive, string password) { ResetScratch(); @@ -170,10 +226,19 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Entry_Stream() + public void Rar_Entry_Stream() { + DoRar_Entry_Stream("Rar.rar"); + } + + [Fact] + public void Rar5_Entry_Stream() { + DoRar_Entry_Stream("Rar5.rar"); + } + + private void DoRar_Entry_Stream(string filename) { ResetScratch(); - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (Stream stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream)) { while (reader.MoveToNextEntry()) @@ -185,7 +250,7 @@ namespace SharpCompress.Test.Rar { string file = Path.GetFileName(reader.Entry.Key); string folder = Path.GetDirectoryName(reader.Entry.Key); - string destdir = Path.Combine(SCRATCH_FILES_PATH, folder); + string destdir = Path.Combine(this.SCRATCH_FILES_PATH, folder); if (!Directory.Exists(destdir)) { Directory.CreateDirectory(destdir); @@ -207,7 +272,7 @@ namespace SharpCompress.Test.Rar public void Rar_Reader_Audio_program() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Audio_program.rar"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar"))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -231,7 +296,7 @@ namespace SharpCompress.Test.Rar public void Rar_Jpg_Reader() { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rarjpeg.jpg"))) + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -257,10 +322,25 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Solid_Skip_Reader() + public void Rar5_Solid_Reader() + { + Read("Rar5.solid.rar", CompressionType.Rar); + } + + [Fact] + public void Rar_Solid_Skip_Reader() { + DoRar_Solid_Skip_Reader("Rar.solid.rar"); + } + + [Fact] + public void Rar5_Solid_Skip_Reader() { + DoRar_Solid_Skip_Reader("Rar5.solid.rar"); + } + + private void DoRar_Solid_Skip_Reader(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.solid.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -271,7 +351,7 @@ namespace SharpCompress.Test.Rar if (reader.Entry.Key.Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true @@ -282,10 +362,19 @@ namespace SharpCompress.Test.Rar } [Fact] - public void Rar_Reader_Skip() + public void Rar_Reader_Skip() { + DoRar_Reader_Skip("Rar.rar"); + } + + [Fact] + public void Rar5_Reader_Skip() { + DoRar_Reader_Skip("Rar5.rar"); + } + + private void DoRar_Reader_Skip(string filename) { ResetScratch(); - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.rar"))) + using (var stream = File.OpenRead(Path.Combine(this.TEST_ARCHIVES_PATH, filename))) using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true @@ -296,7 +385,7 @@ namespace SharpCompress.Test.Rar if (reader.Entry.Key.Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + reader.WriteEntryToDirectory(this.SCRATCH_FILES_PATH, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true diff --git a/tests/TestArchives/Archives/Audio_program.rar b/tests/TestArchives/Archives/Rar.Audio_program.rar similarity index 100% rename from tests/TestArchives/Archives/Audio_program.rar rename to tests/TestArchives/Archives/Rar.Audio_program.rar diff --git a/tests/TestArchives/Archives/Encrypted.rar b/tests/TestArchives/Archives/Rar.Encrypted.rar similarity index 100% rename from tests/TestArchives/Archives/Encrypted.rar rename to tests/TestArchives/Archives/Rar.Encrypted.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part01.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part01.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part01.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part01.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part02.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part02.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part02.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part02.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part03.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part03.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part03.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part03.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part04.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part04.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part04.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part04.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part05.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part05.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part05.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part05.rar diff --git a/tests/TestArchives/Archives/EncryptedParts.part06.rar b/tests/TestArchives/Archives/Rar.EncryptedParts.part06.rar similarity index 100% rename from tests/TestArchives/Archives/EncryptedParts.part06.rar rename to tests/TestArchives/Archives/Rar.EncryptedParts.part06.rar diff --git a/tests/TestArchives/Archives/Rarjpeg.jpg b/tests/TestArchives/Archives/Rar.jpeg.jpg similarity index 100% rename from tests/TestArchives/Archives/Rarjpeg.jpg rename to tests/TestArchives/Archives/Rar.jpeg.jpg diff --git a/tests/TestArchives/Archives/test_invalid_exttime.rar b/tests/TestArchives/Archives/Rar.test_invalid_exttime.rar similarity index 100% rename from tests/TestArchives/Archives/test_invalid_exttime.rar rename to tests/TestArchives/Archives/Rar.test_invalid_exttime.rar diff --git a/tests/TestArchives/Archives/Rar5.crc_blake2.rar b/tests/TestArchives/Archives/Rar5.crc_blake2.rar new file mode 100644 index 00000000..2e89e107 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.crc_blake2.rar differ diff --git a/tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar b/tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar new file mode 100644 index 00000000..d0bad6d4 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.encrypted_filesAndHeader.rar differ diff --git a/tests/TestArchives/Archives/Rar5.encrypted_filesOnly.rar b/tests/TestArchives/Archives/Rar5.encrypted_filesOnly.rar new file mode 100644 index 00000000..cb18a286 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.encrypted_filesOnly.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part01.rar b/tests/TestArchives/Archives/Rar5.multi.part01.rar new file mode 100644 index 00000000..89731875 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part01.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part02.rar b/tests/TestArchives/Archives/Rar5.multi.part02.rar new file mode 100644 index 00000000..d23d82e4 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part02.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part03.rar b/tests/TestArchives/Archives/Rar5.multi.part03.rar new file mode 100644 index 00000000..159c55d8 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part03.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part04.rar b/tests/TestArchives/Archives/Rar5.multi.part04.rar new file mode 100644 index 00000000..cb0ed618 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part04.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part05.rar b/tests/TestArchives/Archives/Rar5.multi.part05.rar new file mode 100644 index 00000000..ec132ab8 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part05.rar differ diff --git a/tests/TestArchives/Archives/Rar5.multi.part06.rar b/tests/TestArchives/Archives/Rar5.multi.part06.rar new file mode 100644 index 00000000..8b908610 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.multi.part06.rar differ diff --git a/tests/TestArchives/Archives/Rar5.none.rar b/tests/TestArchives/Archives/Rar5.none.rar new file mode 100644 index 00000000..276234c5 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.none.rar differ diff --git a/tests/TestArchives/Archives/Rar5.rar b/tests/TestArchives/Archives/Rar5.rar new file mode 100644 index 00000000..0a05d520 Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.rar differ diff --git a/tests/TestArchives/Archives/Rar5.solid.rar b/tests/TestArchives/Archives/Rar5.solid.rar new file mode 100644 index 00000000..e16a01da Binary files /dev/null and b/tests/TestArchives/Archives/Rar5.solid.rar differ