#include "rar.hpp" QuickOpen::QuickOpen() { Buf=NULL; Init(NULL,false); } QuickOpen::~QuickOpen() { Close(); delete[] Buf; } void QuickOpen::Init(Archive *Arc,bool WriteMode) { if (Arc!=NULL) // Unless called from constructor. Close(); QuickOpen::Arc=Arc; QuickOpen::WriteMode=WriteMode; ListStart=NULL; ListEnd=NULL; if (Buf==NULL) Buf=new byte[MaxBufSize]; CurBufSize=0; // Current size of buffered data in write mode. Loaded=false; } void QuickOpen::Close() { QuickOpenItem *Item=ListStart; while (Item!=NULL) { QuickOpenItem *Next=Item->Next; delete[] Item->Header; delete Item; Item=Next; } } void QuickOpen::Load(uint64 BlockPos) { if (!Loaded) { // If loading for the first time, perform additional intialization. SeekPos=Arc->Tell(); UnsyncSeekPos=false; SaveFilePos SavePos(*Arc); Arc->Seek(BlockPos,SEEK_SET); // If BlockPos points to original main header, we'll have the infinite // recursion, because ReadHeader() for main header will attempt to load // QOpen and call QuickOpen::Load again. If BlockPos points to long chain // of other main headers, we'll have multiple recursive calls of this // function wasting resources. So we prohibit QOpen temporarily to // prevent this. ReadHeader() calls QOpen.Init and sets MainHead Locator // and QOpenOffset fields, so we cannot use them to prohibit QOpen. Arc->SetProhibitQOpen(true); size_t ReadSize=Arc->ReadHeader(); Arc->SetProhibitQOpen(false); if (ReadSize==0 || Arc->GetHeaderType()!=HEAD_SERVICE || !Arc->SubHead.CmpName(SUBHEAD_TYPE_QOPEN)) return; QLHeaderPos=Arc->CurBlockPos; RawDataStart=Arc->Tell(); RawDataSize=Arc->SubHead.UnpSize; Loaded=true; // Set only after all file processing calls like Tell, Seek, ReadHeader. } if (Arc->SubHead.Encrypted) { RAROptions *Cmd=Arc->GetRAROptions(); #ifndef RAR_NOCRYPT if (Cmd->Password.IsSet()) Crypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,Arc->SubHead.Salt, Arc->SubHead.InitV,Arc->SubHead.Lg2Count, Arc->SubHead.HashKey,Arc->SubHead.PswCheck); else #endif { Loaded=false; return; } } RawDataPos=0; ReadBufSize=0; ReadBufPos=0; LastReadHeader.Reset(); LastReadHeaderPos=0; ReadBuffer(); } bool QuickOpen::Read(void *Data,size_t Size,size_t &Result) { if (!Loaded) return false; // Find next suitable cached block. while (LastReadHeaderPos+LastReadHeader.Size()<=SeekPos) if (!ReadNext()) break; if (!Loaded) { // If something wrong happened, let's set the correct file pointer // and stop further quick open processing. if (UnsyncSeekPos) Arc->File::Seek(SeekPos,SEEK_SET); return false; } if (SeekPos>=LastReadHeaderPos && SeekPos+Size<=LastReadHeaderPos+LastReadHeader.Size()) { memcpy(Data,LastReadHeader+size_t(SeekPos-LastReadHeaderPos),Size); Result=Size; SeekPos+=Size; UnsyncSeekPos=true; } else { if (UnsyncSeekPos) { Arc->File::Seek(SeekPos,SEEK_SET); UnsyncSeekPos=false; } int ReadSize=Arc->File::Read(Data,Size); if (ReadSize<0) { Loaded=false; return false; } Result=ReadSize; SeekPos+=ReadSize; } return true; } bool QuickOpen::Seek(int64 Offset,int Method) { if (!Loaded) return false; // Normally we process an archive sequentially from beginning to end, // so we read quick open data sequentially. But some operations like // archive updating involve several passes. So if we detect that file // pointer is moved back, we reload quick open data from beginning. if (Method==SEEK_SET && (uint64)OffsetFile::Seek(Offset,SEEK_END); SeekPos=Arc->File::Tell(); UnsyncSeekPos=false; } return true; } bool QuickOpen::Tell(int64 *Pos) { if (!Loaded) return false; *Pos=SeekPos; return true; } uint QuickOpen::ReadBuffer() { SaveFilePos SavePos(*Arc); Arc->File::Seek(RawDataStart+RawDataPos,SEEK_SET); size_t SizeToRead=(size_t)Min(RawDataSize-RawDataPos,MaxBufSize-ReadBufSize); if (Arc->SubHead.Encrypted) SizeToRead &= ~CRYPT_BLOCK_MASK; if (SizeToRead==0) return 0; int ReadSize=Arc->File::Read(Buf+ReadBufSize,SizeToRead); if (ReadSize<=0) return 0; #ifndef RAR_NOCRYPT if (Arc->SubHead.Encrypted) Crypt.DecryptBlock(Buf+ReadBufSize,ReadSize & ~CRYPT_BLOCK_MASK); #endif RawDataPos+=ReadSize; ReadBufSize+=ReadSize; return ReadSize; } // Fill RawRead object from buffer. bool QuickOpen::ReadRaw(RawRead &Raw) { if (MaxBufSize-ReadBufPos<0x100) // We are close to end of buffer. { // Ensure that we have enough data to read CRC and header size. size_t DataLeft=ReadBufSize-ReadBufPos; memcpy(Buf,Buf+ReadBufPos,DataLeft); ReadBufPos=0; ReadBufSize=DataLeft; ReadBuffer(); } const size_t FirstReadSize=7; if (ReadBufPos+FirstReadSize>ReadBufSize) return false; Raw.Read(Buf+ReadBufPos,FirstReadSize); ReadBufPos+=FirstReadSize; uint SavedCRC=Raw.Get4(); uint SizeBytes=Raw.GetVSize(4); uint64 BlockSize=Raw.GetV(); int SizeToRead=int(BlockSize); SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any. if (SizeToRead<0 || SizeBytes==0 || BlockSize==0) { Loaded=false; // Invalid data. return false; } // If rest of block data crosses buffer boundary, read it in loop. size_t DataLeft=ReadBufSize-ReadBufPos; while (SizeToRead>0) { size_t CurSizeToRead=Min(DataLeft,(size_t)SizeToRead); Raw.Read(Buf+ReadBufPos,CurSizeToRead); ReadBufPos+=CurSizeToRead; SizeToRead-=int(CurSizeToRead); if (SizeToRead>0) // We read the entire buffer and still need more data. { ReadBufPos=0; ReadBufSize=0; if (ReadBuffer()==0) return false; } } return SavedCRC==Raw.GetCRC50(); } // Read next cached header. bool QuickOpen::ReadNext() { RawRead Raw(NULL); if (!ReadRaw(Raw)) // Read internal quick open header preceding stored block. return false; uint Flags=(uint)Raw.GetV(); uint64 Offset=Raw.GetV(); size_t HeaderSize=(size_t)Raw.GetV(); if (HeaderSize>MAX_HEADER_SIZE_RAR5) return false; LastReadHeader.Alloc(HeaderSize); Raw.GetB(&LastReadHeader[0],HeaderSize); // Calculate the absolute position as offset from quick open service header. LastReadHeaderPos=QLHeaderPos-Offset; return true; }