#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.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(); // 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->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,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;JSetLocal(&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. Wrong password can be intentionally provided // in -p 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 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)=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 (ExtraStartGetPos()) 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 (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 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 *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; }