| #include "rar.hpp" |
| |
| #include "arccmt.cpp" |
| |
| |
| #ifdef USE_ARCMEM |
| #include "arcmem.cpp" |
| #endif |
| |
| 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; |
| |
| #ifdef USE_QOPEN |
| ProhibitQOpen=false; |
| #endif |
| |
| } |
| |
| |
| 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]>1 && D[6]<5) |
| 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<char> Buffer(MAXSFXSIZE); |
| long CurPos=(long)Tell(); |
| int ReadSize=Read(&Buffer[0],Buffer.Size()-16); |
| for (int I=0;I<ReadSize;I++) |
| if (Buffer[I]==0x52 && (Type=IsSignature((byte *)&Buffer[I],ReadSize-I))!=RARFMT_NONE) |
| { |
| Format=Type; |
| if (Format==RARFMT14 && I>0 && 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. |
| { |
| if (Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1)!=1 || 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 |
| |
| bool HeadersLeft; // Any headers left to read. |
| // Skip the archive encryption header if any and read the main header. |
| while ((HeadersLeft=(ReadHeader()!=0))==true) // Additional parentheses to silence Clang. |
| { |
| SeekToNext(); |
| |
| 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; |
| } |
| |
| // 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; |
| |
| 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 (HeadersLeft && (!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) |
| wcsncpyz(FirstVolumeName,FileName,ASIZE(FirstVolumeName)); |
| |
| 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); |
| } |
| |
| |
| |
| |
| bool Archive::Open(const wchar *Name,uint Mode) |
| { |
| #ifdef USE_QOPEN |
| // Important if we reuse Archive object and it has virtual QOpen |
| // file position not matching real. For example, for 'l -v volname'. |
| QOpen.Unload(); |
| #endif |
| |
| #ifdef USE_ARCMEM |
| if (Cmd->ArcInMem) |
| { |
| wcsncpyz(FileName,Name,ASIZE(FileName)); |
| ArcMem.Load(Cmd->ArcMemData,Cmd->ArcMemSize); |
| Cmd->SetArcInMem(NULL,0); // Return in memory data for first volume only, not for next volumes. |
| return true; |
| } |
| #endif |
| |
| return File::Open(Name,Mode); |
| } |
| |
| |
| |
| bool Archive::Close() |
| { |
| #ifdef USE_ARCMEM |
| if (ArcMem.Unload()) |
| return true; |
| #endif |
| return File::Close(); |
| } |
| |
| |
| |
| int Archive::Read(void *Data,size_t Size) |
| { |
| #ifdef USE_QOPEN |
| size_t QResult; |
| if (QOpen.Read(Data,Size,QResult)) |
| return (int)QResult; |
| #endif |
| #ifdef USE_ARCMEM |
| size_t AResult; |
| if (ArcMem.Read(Data,Size,AResult)) |
| return (int)AResult; |
| #endif |
| return File::Read(Data,Size); |
| } |
| |
| |
| void Archive::Seek(int64 Offset,int Method) |
| { |
| #ifdef USE_QOPEN |
| if (QOpen.Seek(Offset,Method)) |
| return; |
| #endif |
| #ifdef USE_ARCMEM |
| if (ArcMem.Seek(Offset,Method)) |
| return; |
| #endif |
| File::Seek(Offset,Method); |
| } |
| |
| |
| int64 Archive::Tell() |
| { |
| #ifdef USE_QOPEN |
| int64 QPos; |
| if (QOpen.Tell(&QPos)) |
| return QPos; |
| #endif |
| #ifdef USE_ARCMEM |
| int64 APos; |
| if (ArcMem.Tell(&APos)) |
| return APos; |
| #endif |
| return File::Tell(); |
| } |
| |
| |
| |
| bool Archive::IsOpened() |
| { |
| #ifdef USE_ARCMEM |
| if (ArcMem.IsLoaded()) |
| return true; |
| #endif |
| return File::IsOpened(); |
| }; |