libclamunrar/arcread.cpp
d39cb658
 #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;
   }
 
   // It is important to check ReadSize>0 here, because it is normal
   // for RAR2 and RAR3 archives without end of archive block to have
   // NextBlockPos==CurBlockPos after the end of archive has reached.
   if (ReadSize>0 && NextBlockPos<=CurBlockPos)
   {
     BrokenHeaderMsg();
     ReadSize=0;
   }
 
   if (ReadSize==0)
     CurHeaderType=HEAD_UNKNOWN;
 
   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);
 }
 
 
 // Return f in case of signed integer overflow or negative parameters
 // or v1+v2 otherwise. We use it for file offsets, which are signed
 // for compatibility with off_t in POSIX file functions and third party code.
 // Signed integer overflow is the undefined behavior according to
 // C++ standard and it causes fuzzers to complain.
 inline int64 SafeAdd(int64 v1,int64 v2,int64 f)
 {
   return v1>=0 && v2>=0 && v1<=MAX_INT64-v2 ? v1+v2 : f;
 }
 
 
 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.HeadSize<SIZEOF_SHORTBLOCKHEAD)
   {
     BrokenHeaderMsg();
     return 0;
   }
 
   // For simpler further processing we map header types common
   // for RAR 1.5 and 5.0 formats to RAR 5.0 values. It does not include
   // header types specific for RAR 1.5 - 4.x only.
   switch(ShortBlock.HeaderType)
   {
     case HEAD3_MAIN:    ShortBlock.HeaderType=HEAD_MAIN;     break;
     case HEAD3_FILE:    ShortBlock.HeaderType=HEAD_FILE;     break;
     case HEAD3_SERVICE: ShortBlock.HeaderType=HEAD_SERVICE;  break;
     case HEAD3_ENDARC:  ShortBlock.HeaderType=HEAD_ENDARC;   break;
   }
   CurHeaderType=ShortBlock.HeaderType;
 
   if (ShortBlock.HeaderType==HEAD3_CMT)
   {
     // Old style (up to RAR 2.9) comment header embedded into main
     // or file header. We must not read the entire ShortBlock.HeadSize here
     // to not break the comment processing logic later.
     Raw.Read(SIZEOF_COMMHEAD-SIZEOF_SHORTBLOCKHEAD);
   }
   else
     if (ShortBlock.HeaderType==HEAD_MAIN && (ShortBlock.Flags & MHD_COMMENT)!=0)
     {
       // Old style (up to RAR 2.9) main archive comment embedded into
       // the main archive header found. While we can read the entire 
       // ShortBlock.HeadSize here and remove this part of "if", it would be
       // waste of memory, because we'll read and process this comment data
       // in other function anyway and we do not need them here now.
       Raw.Read(SIZEOF_MAINHEAD3-SIZEOF_SHORTBLOCKHEAD);
     }
     else
       Raw.Read(ShortBlock.HeadSize-SIZEOF_SHORTBLOCKHEAD);
 
   NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize);
 
   switch(ShortBlock.HeaderType)
   {
     case HEAD_MAIN:
       MainHead.Reset();
       *(BaseBlock *)&MainHead=ShortBlock;
       MainHead.HighPosAV=Raw.Get2();
       MainHead.PosAV=Raw.Get4();
 
       Volume=(MainHead.Flags & MHD_VOLUME)!=0;
       Solid=(MainHead.Flags & MHD_SOLID)!=0;
       Locked=(MainHead.Flags & MHD_LOCK)!=0;
       Protected=(MainHead.Flags & MHD_PROTECT)!=0;
       Encrypted=(MainHead.Flags & MHD_PASSWORD)!=0;
       Signed=MainHead.PosAV!=0 || MainHead.HighPosAV!=0;
       MainHead.CommentInHeader=(MainHead.Flags & MHD_COMMENT)!=0;
     
       // Only for encrypted 3.0+ archives. 2.x archives did not have this
       // flag, so for non-encrypted archives, we'll set it later based on
       // file attributes.
       FirstVolume=(MainHead.Flags & MHD_FIRSTVOLUME)!=0;
 
       NewNumbering=(MainHead.Flags & MHD_NEWNUMBERING)!=0;
       break;
     case HEAD_FILE:
     case HEAD_SERVICE:
       {
         bool FileBlock=ShortBlock.HeaderType==HEAD_FILE;
         FileHeader *hd=FileBlock ? &FileHead:&SubHead;
         hd->Reset();
 
         *(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();
 
         // RAR15 did not use the special dictionary size to mark dirs.
         if (hd->UnpVer<20 && (hd->FileAttr & 0x10)!=0)
           hd->Dir=true;
 
         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->HostOS<HOST_MAX)
             hd->HSType=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,ReadNameSize,(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_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;J<count;J++)
             {
               byte CurByte=Raw.Get1();
               rlt.Reminder|=(((uint)CurByte)<<((J+3-count)*8));
             }
             // Convert from 100ns RAR precision to REMINDER_PRECISION.
             rlt.Reminder*=RarTime::REMINDER_PRECISION/10000000;
             CurTime->SetLocal(&rlt);
           }
         }
         // Set to 0 in case of overflow, so end of ReadHeader cares about it.
         NextBlockPos=SafeAdd(NextBlockPos,hd->PackSize,0);
 
         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_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;
       break;
     case HEAD3_OLDSERVICE: // RAR 2.9 and earlier.
       *(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 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;
       }
     }
   }
 
   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
 
     byte HeadersInitV[SIZE_INITV];
     if (Read(HeadersInitV,SIZE_INITV)!=SIZE_INITV)
     {
       UnexpEndArcMsg();
       return 0;
     }
 
     // We repeat the password request only for manually entered passwords
     // and not for -p<pwd>. Wrong password can be intentionally provided
     // in -p<pwd> to not stop batch processing for encrypted archives.
     bool GlobalPassword=Cmd->Password.IsSet();
 
     while (true) // Repeat the password prompt for wrong passwords.
     {
       RequestArcPassword();
 
       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)
       {
         if (GlobalPassword) // For -p<pwd> or Ctrl+P.
         {
           // This message is used by Android GUI to reset cached passwords.
           // Update appropriate code if changed.
           uiMsg(UIERROR_BADPSW,FileName);
           FailedHeaderDecryption=true;
           ErrHandler.SetErrorCode(RARX_BADPWD);
           return 0;
         }
         else // For passwords entered manually.
         {
           // This message is used by Android GUI and Windows GUI and SFX to
           // reset cached passwords. Update appropriate code if changed.
           uiMsg(UIWAIT_BADPSW,FileName);
           Cmd->Password.Clean();
         }
 
 #ifdef RARDLL
         // Avoid new requests for unrar.dll to prevent the infinite loop
         // if app always returns the same password.
         ErrHandler.SetErrorCode(RARX_BADPWD);
         Cmd->DllError=ERAR_BAD_PASSWORD;
         ErrHandler.Exit(RARX_BADPWD);
 #else
         continue; // Request a password again.
 #endif
       }
       break;
     }
 
     Raw.SetCrypt(&HeadersCrypt);
 #endif
   }
 
   // Header size must not occupy more than 3 variable length integer bytes
   // resulting in 2 MB maximum header size (MAX_HEADER_SIZE_RAR5),
   // 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)<FirstReadSize)
   {
     UnexpEndArcMsg();
     return 0;
   }
 
   ShortBlock.Reset();
   ShortBlock.HeadCRC=Raw.Get4();
   uint SizeBytes=Raw.GetVSize(4);
   uint64 BlockSize=Raw.GetV();
 
   if (BlockSize==0 || SizeBytes==0)
   {
     BrokenHeaderMsg();
     return 0;
   }
 
   int SizeToRead=int(BlockSize);
   SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any.
   uint HeaderSize=4+SizeBytes+(uint)BlockSize;
 
   if (SizeToRead<0 || HeaderSize<SIZEOF_SHORTBLOCKHEAD5)
   {
     BrokenHeaderMsg();
     return 0;
   }
   
   Raw.Read(SizeToRead);
 
   if (Raw.Size()<HeaderSize)
   {
     UnexpEndArcMsg();
     return 0;
   }
 
   uint HeaderCRC=Raw.GetCRC50();
 
   ShortBlock.HeaderType=(HEADER_TYPE)Raw.GetV();
   ShortBlock.Flags=(uint)Raw.GetV();
   ShortBlock.SkipIfUnknown=(ShortBlock.Flags & HFL_SKIPIFUNKNOWN)!=0;
   ShortBlock.HeadSize=HeaderSize;
 
   CurHeaderType=ShortBlock.HeaderType;
 
   bool BadCRC=(ShortBlock.HeadCRC!=HeaderCRC);
   if (BadCRC)
   {
     BrokenHeaderMsg(); // Report, but attempt to process.
 
     BrokenHeader=true;
     ErrHandler.SetErrorCode(RARX_CRC);
 
     if (Decrypt)
     {
       uiMsg(UIERROR_CHECKSUMENC,FileName,FileName);
       FailedHeaderDecryption=true;
       return 0;
     }
   }
   
   uint64 ExtraSize=0;
   if ((ShortBlock.Flags & HFL_EXTRA)!=0)
   {
     ExtraSize=Raw.GetV();
     if (ExtraSize>=ShortBlock.HeadSize)
     {
       BrokenHeaderMsg();
       return 0;
     }
   }
 
   uint64 DataSize=0;
   if ((ShortBlock.Flags & HFL_DATA)!=0)
     DataSize=Raw.GetV();
 
   NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize);
   // Set to 0 in case of overflow, so end of ReadHeader cares about it.
   NextBlockPos=SafeAdd(NextBlockPos,DataSize,0);
 
   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 (!ProhibitQOpen && 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;
   }
 
   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))
     {
       Close();
       uiMsg(UIERROR_INCERRCOUNT); // Prevent archive deleting if delete after extraction is on.
       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 (ExtraStart<Raw->GetPos())
     return;
   Raw->SetPos(ExtraStart);
   while (Raw->DataLeft()>=2)
   {
     int64 FieldSize=Raw->GetV(); // Needs to be signed for check below and can be negative.
     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 (FieldSize<0) // FieldType is longer than expected extra field size.
       break;
 
     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(hd->FileName,VerText,ASIZE(hd->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 (NameSize<ASIZE(UtfName)-1)
             {
               Raw->GetB(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 the 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++;
 
             // We cannot allocate too much memory here, because above
             // we check FieldSize againt Raw size and we control that Raw size
             // is sensible when reading headers.
             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();
     if (HeadSize<7)
       return false;
     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();
     if (FileHead.HeadSize<21)
       return false;
     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.Dir=(FileHead.FileAttr & 0x10)!=0;
 
     FileHead.HostOS=HOST_MSDOS;
     FileHead.HSType=HSYS_WINDOWS;
 
     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 (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<byte> *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;
 }