libclamunrar/archive.cpp
01eebc13
 #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;
 
 
   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.
   bool StartFound=false; // Main or encryption headers found.
   // 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.
     StartFound=Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT;
     if (StartFound)
       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 || !StartFound) // Main archive header is corrupt or missing.
   {
     if (!FailedHeaderDecryption) // If not reported a wrong password already.
       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);
 }
 
 
 
 
 #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