// Archive/CabIn.cpp #include "StdAfx.h" // #include #include "../../../../C/CpuArch.h" #include "../../Common/LimitedStreams.h" #include "../../Common/StreamUtils.h" #include "CabIn.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) namespace NArchive { namespace NCab { struct CUnexpectedEndException {}; void CInArchive::Skip(unsigned size) { if (_inBuffer.Skip(size) != size) throw CUnexpectedEndException(); } void CInArchive::Read(Byte *data, unsigned size) { if (_inBuffer.ReadBytes(data, size) != size) throw CUnexpectedEndException(); } void CInArchive::ReadName(AString &s) { for (size_t i = 0; i < ((size_t)1 << 13); i++) { Byte b; if (!_inBuffer.ReadByte(b)) throw CUnexpectedEndException(); if (b == 0) { s.SetFrom((const char *)(const Byte *)_tempBuf, (unsigned)i); return; } if (_tempBuf.Size() == i) _tempBuf.ChangeSize_KeepData(i * 2, i); _tempBuf[i] = b; } for (;;) { Byte b; if (!_inBuffer.ReadByte(b)) throw CUnexpectedEndException(); if (b == 0) break; } ErrorInNames = true; s = "[ERROR-LONG-PATH]"; } void CInArchive::ReadOtherArc(COtherArc &oa) { ReadName(oa.FileName); ReadName(oa.DiskName); } struct CSignatureFinder { Byte *Buf; UInt32 Pos; UInt32 End; const Byte *Signature; UInt32 SignatureSize; UInt32 _HeaderSize; UInt32 _AlignSize; UInt32 _BufUseCapacity; ISequentialInStream *Stream; UInt64 Processed; // Global offset of start of Buf const UInt64 *SearchLimit; UInt32 GetTotalCapacity(UInt32 basicSize, UInt32 headerSize) { _HeaderSize = headerSize; for (_AlignSize = (1 << 5); _AlignSize < _HeaderSize; _AlignSize <<= 1); _BufUseCapacity = basicSize + _AlignSize; return _BufUseCapacity + 16; } /* returns: S_OK - signature found (at Pos) S_FALSE - signature not found */ HRESULT Find(); }; HRESULT CSignatureFinder::Find() { for (;;) { Buf[End] = Signature[0]; // it's for fast search; while (End - Pos >= _HeaderSize) { const Byte *p = Buf + Pos; Byte b = Signature[0]; for (;;) { if (*p == b) break; p++; if (*p == b) break; p++; } Pos = (UInt32)(p - Buf); if (End - Pos < _HeaderSize) { Pos = End - _HeaderSize + 1; break; } UInt32 i; for (i = 1; i < SignatureSize && p[i] == Signature[i]; i++); if (i == SignatureSize) return S_OK; Pos++; } if (Pos >= _AlignSize) { UInt32 num = (Pos & ~(_AlignSize - 1)); Processed += num; Pos -= num; End -= num; memmove(Buf, Buf + num, End); } UInt32 rem = _BufUseCapacity - End; if (SearchLimit) { if (Processed + Pos > *SearchLimit) return S_FALSE; UInt64 rem2 = *SearchLimit - (Processed + End) + _HeaderSize; if (rem > rem2) rem = (UInt32)rem2; } UInt32 processedSize; if (Processed == 0 && rem == _BufUseCapacity - _HeaderSize) rem -= _AlignSize; // to make reads more aligned. RINOK(Stream->Read(Buf + End, rem, &processedSize)); if (processedSize == 0) return S_FALSE; End += processedSize; } } bool CInArcInfo::Parse(const Byte *p) { if (Get32(p + 0x0C) != 0 || Get32(p + 0x14) != 0) return false; Size = Get32(p + 8); if (Size < 36) return false; Flags = Get16(p + 0x1E); if (Flags > 7) return false; FileHeadersOffset = Get32(p + 0x10); if (FileHeadersOffset != 0 && FileHeadersOffset > Size) return false; VersionMinor = p[0x18]; VersionMajor = p[0x19]; NumFolders = Get16(p + 0x1A); NumFiles = Get16(p + 0x1C); return true; } HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) { IsArc = false; ErrorInNames = false; UnexpectedEnd = false; HeaderError = false; db.Clear(); RINOK(db.Stream->Seek(0, STREAM_SEEK_CUR, &db.StartPosition)); // UInt64 temp = db.StartPosition; CByteBuffer buffer; CInArcInfo &ai = db.ArcInfo; UInt64 startInBuf = 0; CLimitedSequentialInStream *limitedStreamSpec = NULL; CMyComPtr limitedStream; // for (int iii = 0; iii < 10000; iii++) { // db.StartPosition = temp; RINOK(db.Stream->Seek(db.StartPosition, STREAM_SEEK_SET, NULL)); const UInt32 kMainHeaderSize = 32; Byte header[kMainHeaderSize]; const UInt32 kBufSize = 1 << 15; RINOK(ReadStream_FALSE(db.Stream, header, kMainHeaderSize)); if (memcmp(header, NHeader::kMarker, NHeader::kMarkerSize) == 0 && ai.Parse(header)) { limitedStreamSpec = new CLimitedSequentialInStream; limitedStream = limitedStreamSpec; limitedStreamSpec->SetStream(db.Stream); limitedStreamSpec->Init(ai.Size - NHeader::kMarkerSize); buffer.Alloc(kBufSize); memcpy(buffer, header, kMainHeaderSize); UInt32 numProcessedBytes; RINOK(limitedStream->Read(buffer + kMainHeaderSize, kBufSize - kMainHeaderSize, &numProcessedBytes)); _inBuffer.SetBuf(buffer, (UInt32)kBufSize, kMainHeaderSize + numProcessedBytes, kMainHeaderSize); } else { if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0) return S_FALSE; CSignatureFinder finder; finder.Stream = db.Stream; finder.Signature = NHeader::kMarker; finder.SignatureSize = NHeader::kMarkerSize; finder.SearchLimit = searchHeaderSizeLimit; buffer.Alloc(finder.GetTotalCapacity(kBufSize, kMainHeaderSize)); finder.Buf = buffer; memcpy(buffer, header, kMainHeaderSize); finder.Processed = db.StartPosition; finder.End = kMainHeaderSize; finder.Pos = 1; for (;;) { RINOK(finder.Find()); if (ai.Parse(finder.Buf + finder.Pos)) { db.StartPosition = finder.Processed + finder.Pos; limitedStreamSpec = new CLimitedSequentialInStream; limitedStreamSpec->SetStream(db.Stream); limitedStream = limitedStreamSpec; UInt32 remInFinder = finder.End - finder.Pos; if (ai.Size <= remInFinder) { limitedStreamSpec->Init(0); finder.End = finder.Pos + ai.Size; } else limitedStreamSpec->Init(ai.Size - remInFinder); startInBuf = finder.Pos; _inBuffer.SetBuf(buffer, (UInt32)kBufSize, finder.End, finder.Pos + kMainHeaderSize); break; } finder.Pos++; } } } IsArc = true; _inBuffer.SetStream(limitedStream); if (_tempBuf.Size() == 0) _tempBuf.Alloc(1 << 12); Byte p[16]; unsigned nextSize = 4 + (ai.ReserveBlockPresent() ? 4 : 0); Read(p, nextSize); ai.SetID = Get16(p); ai.CabinetNumber = Get16(p + 2); if (ai.ReserveBlockPresent()) { ai.PerCabinet_AreaSize = Get16(p + 4); ai.PerFolder_AreaSize = p[6]; ai.PerDataBlock_AreaSize = p[7]; Skip(ai.PerCabinet_AreaSize); } if (ai.IsTherePrev()) ReadOtherArc(ai.PrevArc); if (ai.IsThereNext()) ReadOtherArc(ai.NextArc); UInt32 i; db.Folders.ClearAndReserve(ai.NumFolders); for (i = 0; i < ai.NumFolders; i++) { Read(p, 8); CFolder folder; folder.DataStart = Get32(p); folder.NumDataBlocks = Get16(p + 4); folder.MethodMajor = p[6]; folder.MethodMinor = p[7]; Skip(ai.PerFolder_AreaSize); db.Folders.AddInReserved(folder); } // for (int iii = 0; iii < 10000; iii++) { if (_inBuffer.GetProcessedSize() - startInBuf != ai.FileHeadersOffset) { // printf("\n!!! Seek Error !!!!\n"); // fflush(stdout); RINOK(db.Stream->Seek(db.StartPosition + ai.FileHeadersOffset, STREAM_SEEK_SET, NULL)); limitedStreamSpec->Init(ai.Size - ai.FileHeadersOffset); _inBuffer.Init(); } db.Items.ClearAndReserve(ai.NumFiles); for (i = 0; i < ai.NumFiles; i++) { Read(p, 16); CItem &item = db.Items.AddNewInReserved(); item.Size = Get32(p); item.Offset = Get32(p + 4); item.FolderIndex = Get16(p + 8); UInt16 pureDate = Get16(p + 10); UInt16 pureTime = Get16(p + 12); item.Time = (((UInt32)pureDate << 16)) | pureTime; item.Attributes = Get16(p + 14); ReadName(item.Name); if (item.GetFolderIndex(db.Folders.Size()) >= (int)db.Folders.Size()) { HeaderError = true; return S_FALSE; } } // } return S_OK; } HRESULT CInArchive::Open(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) { try { return Open2(db, searchHeaderSizeLimit); } catch(const CInBufferException &e) { return e.ErrorCode; } catch(CUnexpectedEndException &) { UnexpectedEnd = true; return S_FALSE; } } #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } static int CompareMvItems(const CMvItem *p1, const CMvItem *p2, void *param) { const CMvDatabaseEx &mvDb = *(const CMvDatabaseEx *)param; const CDatabaseEx &db1 = mvDb.Volumes[p1->VolumeIndex]; const CDatabaseEx &db2 = mvDb.Volumes[p2->VolumeIndex]; const CItem &item1 = db1.Items[p1->ItemIndex]; const CItem &item2 = db2.Items[p2->ItemIndex];; bool isDir1 = item1.IsDir(); bool isDir2 = item2.IsDir(); if (isDir1 && !isDir2) return -1; if (isDir2 && !isDir1) return 1; int f1 = mvDb.GetFolderIndex(p1); int f2 = mvDb.GetFolderIndex(p2); RINOZ(MyCompare(f1, f2)); RINOZ(MyCompare(item1.Offset, item2.Offset)); RINOZ(MyCompare(item1.Size, item2.Size)); RINOZ(MyCompare(p1->VolumeIndex, p2->VolumeIndex)); return MyCompare(p1->ItemIndex, p2->ItemIndex); } bool CMvDatabaseEx::AreItemsEqual(unsigned i1, unsigned i2) { const CMvItem *p1 = &Items[i1]; const CMvItem *p2 = &Items[i2]; const CDatabaseEx &db1 = Volumes[p1->VolumeIndex]; const CDatabaseEx &db2 = Volumes[p2->VolumeIndex]; const CItem &item1 = db1.Items[p1->ItemIndex]; const CItem &item2 = db2.Items[p2->ItemIndex];; return GetFolderIndex(p1) == GetFolderIndex(p2) && item1.Offset == item2.Offset && item1.Size == item2.Size && item1.Name == item2.Name; } void CMvDatabaseEx::FillSortAndShrink() { Items.Clear(); StartFolderOfVol.Clear(); FolderStartFileIndex.Clear(); int offset = 0; FOR_VECTOR (v, Volumes) { const CDatabaseEx &db = Volumes[v]; int curOffset = offset; if (db.IsTherePrevFolder()) curOffset--; StartFolderOfVol.Add(curOffset); offset += db.GetNumberOfNewFolders(); CMvItem mvItem; mvItem.VolumeIndex = v; FOR_VECTOR (i, db.Items) { mvItem.ItemIndex = i; Items.Add(mvItem); } } if (Items.Size() > 1) { Items.Sort(CompareMvItems, (void *)this); unsigned j = 1; unsigned i = 1; for (; i < Items.Size(); i++) if (!AreItemsEqual(i, i - 1)) Items[j++] = Items[i]; Items.DeleteFrom(j); } FOR_VECTOR (i, Items) { int folderIndex = GetFolderIndex(&Items[i]); while (folderIndex >= (int)FolderStartFileIndex.Size()) FolderStartFileIndex.Add(i); } } bool CMvDatabaseEx::Check() { for (unsigned v = 1; v < Volumes.Size(); v++) { const CDatabaseEx &db1 = Volumes[v]; if (db1.IsTherePrevFolder()) { const CDatabaseEx &db0 = Volumes[v - 1]; if (db0.Folders.IsEmpty() || db1.Folders.IsEmpty()) return false; const CFolder &f0 = db0.Folders.Back(); const CFolder &f1 = db1.Folders.Front(); if (f0.MethodMajor != f1.MethodMajor || f0.MethodMinor != f1.MethodMinor) return false; } } UInt32 beginPos = 0; UInt64 endPos = 0; int prevFolder = -2; FOR_VECTOR (i, Items) { const CMvItem &mvItem = Items[i]; int fIndex = GetFolderIndex(&mvItem); if (fIndex >= (int)FolderStartFileIndex.Size()) return false; const CItem &item = Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex]; if (item.IsDir()) continue; int folderIndex = GetFolderIndex(&mvItem); if (folderIndex != prevFolder) prevFolder = folderIndex; else if (item.Offset < endPos && (item.Offset != beginPos || item.GetEndOffset() != endPos)) return false; beginPos = item.Offset; endPos = item.GetEndOffset(); } return true; } }}