// Archive/ZipIn.cpp #include "StdAfx.h" // #include #include "../../../Common/DynamicBuffer.h" #include "../../../Common/IntToString.h" #include "../../../Common/MyException.h" #include "../../../Common/StringToInt.h" #include "../../../Windows/PropVariant.h" #include "../../Common/StreamUtils.h" #include "../IArchive.h" #include "ZipIn.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) #define G16(offs, v) v = Get16(p + (offs)) #define G32(offs, v) v = Get32(p + (offs)) #define G64(offs, v) v = Get64(p + (offs)) namespace NArchive { namespace NZip { // (kBufferSize >= kDataDescriptorSize64 + 4) static const size_t kSeqBufferSize = (size_t)1 << 14; /* if (not defined ZIP_SELF_CHECK) : it reads CD and if error in first pass CD reading, it reads LOCALS-CD-MODE if ( defined ZIP_SELF_CHECK) : it always reads CD and LOCALS-CD-MODE use ZIP_SELF_CHECK to check LOCALS-CD-MODE for any zip archive */ // #define ZIP_SELF_CHECK struct CEcd { UInt16 ThisDisk; UInt16 CdDisk; UInt16 NumEntries_in_ThisDisk; UInt16 NumEntries; UInt32 Size; UInt32 Offset; UInt16 CommentSize; bool IsEmptyArc() const { return ThisDisk == 0 && CdDisk == 0 && NumEntries_in_ThisDisk == 0 && NumEntries == 0 && Size == 0 && Offset == 0 // test it ; } void Parse(const Byte *p); // (p) doesn't include signature }; void CEcd::Parse(const Byte *p) { // (p) doesn't include signature G16(0, ThisDisk); G16(2, CdDisk); G16(4, NumEntries_in_ThisDisk); G16(6, NumEntries); G32(8, Size); G32(12, Offset); G16(16, CommentSize); } void CCdInfo::ParseEcd32(const Byte *p) { IsFromEcd64 = false; // (p) includes signature p += 4; G16(0, ThisDisk); G16(2, CdDisk); G16(4, NumEntries_in_ThisDisk); G16(6, NumEntries); G32(8, Size); G32(12, Offset); G16(16, CommentSize); } void CCdInfo::ParseEcd64e(const Byte *p) { IsFromEcd64 = true; // (p) exclude signature G16(0, VersionMade); G16(2, VersionNeedExtract); G32(4, ThisDisk); G32(8, CdDisk); G64(12, NumEntries_in_ThisDisk); G64(20, NumEntries); G64(28, Size); G64(36, Offset); } struct CLocator { UInt32 Ecd64Disk; UInt32 NumDisks; UInt64 Ecd64Offset; CLocator(): Ecd64Disk(0), NumDisks(0), Ecd64Offset(0) {} void Parse(const Byte *p) { G32(0, Ecd64Disk); G64(4, Ecd64Offset); G32(12, NumDisks); } bool IsEmptyArc() const { return Ecd64Disk == 0 && NumDisks == 0 && Ecd64Offset == 0; } }; void CInArchive::ClearRefs() { StreamRef.Release(); Stream = NULL; StartStream = NULL; Callback = NULL; Vols.Clear(); } void CInArchive::Close() { _cnt = 0; DisableBufMode(); IsArcOpen = false; IsArc = false; IsZip64 = false; IsApk = false; IsCdUnsorted = false; HeadersError = false; HeadersWarning = false; ExtraMinorError = false; UnexpectedEnd = false; LocalsWereRead = false; LocalsCenterMerged = false; NoCentralDir = false; Overflow32bit = false; Cd_NumEntries_Overflow_16bit = false; MarkerIsFound = false; MarkerIsSafe = false; IsMultiVol = false; UseDisk_in_SingleVol = false; EcdVolIndex = 0; ArcInfo.Clear(); ClearRefs(); } HRESULT CInArchive::Seek_SavePos(UInt64 offset) { // InitBuf(); // if (!Stream) return S_FALSE; return Stream->Seek((Int64)offset, STREAM_SEEK_SET, &_streamPos); } HRESULT CInArchive::SeekToVol(int volIndex, UInt64 offset) { if (volIndex != Vols.StreamIndex) { InitBuf(); if (IsMultiVol && volIndex >= 0) { if ((unsigned)volIndex >= Vols.Streams.Size()) return S_FALSE; if (!Vols.Streams[(unsigned)volIndex].Stream) return S_FALSE; Stream = Vols.Streams[(unsigned)volIndex].Stream; } else if (volIndex == -2) { if (!Vols.ZipStream) return S_FALSE; Stream = Vols.ZipStream; } else Stream = StartStream; Vols.StreamIndex = volIndex; } else { if (offset <= _streamPos) { const UInt64 back = _streamPos - offset; if (back <= _bufCached) { _bufPos = _bufCached - (size_t)back; return S_OK; } } InitBuf(); } return Seek_SavePos(offset); } // ---------- ReadFromCache ---------- // reads from cache and from Stream // move to next volume can be allowed if (CanStartNewVol) and only before first byte reading HRESULT CInArchive::ReadFromCache(Byte *data, unsigned size, unsigned &processed) { HRESULT result = S_OK; processed = 0; for (;;) { if (size == 0) return S_OK; const size_t avail = GetAvail(); if (avail != 0) { unsigned cur = size; if (cur > avail) cur = (unsigned)avail; memcpy(data, (const Byte *)Buffer + _bufPos, cur); data += cur; size -= cur; processed += cur; _bufPos += cur; _cnt += cur; CanStartNewVol = false; continue; } InitBuf(); if (_inBufMode) { UInt32 cur = 0; result = Stream->Read(Buffer, (UInt32)Buffer.Size(), &cur); _bufPos = 0; _bufCached = cur; _streamPos += cur; if (cur != 0) CanStartNewVol = false; if (result != S_OK) break; if (cur != 0) continue; } else { size_t cur = size; result = ReadStream(Stream, data, &cur); data += cur; size -= (unsigned)cur; processed += (unsigned)cur; _streamPos += cur; _cnt += cur; if (cur != 0) { CanStartNewVol = false; break; } if (result != S_OK) break; } if ( !IsMultiVol || !CanStartNewVol || Vols.StreamIndex < 0 || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) break; const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; if (!s.Stream) break; result = s.SeekToStart(); if (result != S_OK) break; Vols.StreamIndex++; _streamPos = 0; // Vols.NeedSeek = false; Stream = s.Stream; } return result; } HRESULT CInArchive::ReadFromCache_FALSE(Byte *data, unsigned size) { unsigned processed; HRESULT res = ReadFromCache(data, size, processed); if (res == S_OK && size != processed) return S_FALSE; return res; } static bool CheckDosTime(UInt32 dosTime) { if (dosTime == 0) return true; unsigned month = (dosTime >> 21) & 0xF; unsigned day = (dosTime >> 16) & 0x1F; unsigned hour = (dosTime >> 11) & 0x1F; unsigned min = (dosTime >> 5) & 0x3F; unsigned sec = (dosTime & 0x1F) * 2; if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || min > 59 || sec > 59) return false; return true; } API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) { if (size < 8) return k_IsArc_Res_NEED_MORE; if (p[0] != 'P') return k_IsArc_Res_NO; UInt32 sig = Get32(p); if (sig == NSignature::kNoSpan || sig == NSignature::kSpan) { p += 4; size -= 4; } sig = Get32(p); if (sig == NSignature::kEcd64) { if (size < kEcd64_FullSize) return k_IsArc_Res_NEED_MORE; const UInt64 recordSize = Get64(p + 4); if ( recordSize < kEcd64_MainSize || recordSize > kEcd64_MainSize + (1 << 20)) return k_IsArc_Res_NO; CCdInfo cdInfo; cdInfo.ParseEcd64e(p + 12); if (!cdInfo.IsEmptyArc()) return k_IsArc_Res_NO; return k_IsArc_Res_YES; // k_IsArc_Res_YES_2; } if (sig == NSignature::kEcd) { if (size < kEcdSize) return k_IsArc_Res_NEED_MORE; CEcd ecd; ecd.Parse(p + 4); // if (ecd.cdSize != 0) if (!ecd.IsEmptyArc()) return k_IsArc_Res_NO; return k_IsArc_Res_YES; // k_IsArc_Res_YES_2; } if (sig != NSignature::kLocalFileHeader) return k_IsArc_Res_NO; if (size < kLocalHeaderSize) return k_IsArc_Res_NEED_MORE; p += 4; { const unsigned kPureHeaderSize = kLocalHeaderSize - 4; unsigned i; for (i = 0; i < kPureHeaderSize && p[i] == 0; i++); if (i == kPureHeaderSize) return k_IsArc_Res_NEED_MORE; } /* if (p[0] >= 128) // ExtractVersion.Version; return k_IsArc_Res_NO; */ // ExtractVersion.Version = p[0]; // ExtractVersion.HostOS = p[1]; // Flags = Get16(p + 2); // Method = Get16(p + 4); /* // 9.33: some zip archives contain incorrect value in timestamp. So we don't check it now UInt32 dosTime = Get32(p + 6); if (!CheckDosTime(dosTime)) return k_IsArc_Res_NO; */ // Crc = Get32(p + 10); // PackSize = Get32(p + 14); // Size = Get32(p + 18); const unsigned nameSize = Get16(p + 22); unsigned extraSize = Get16(p + 24); const UInt32 extraOffset = kLocalHeaderSize + (UInt32)nameSize; /* // 21.02: fixed. we don't use the following check if (extraOffset + extraSize > (1 << 16)) return k_IsArc_Res_NO; */ p -= 4; { size_t rem = size - kLocalHeaderSize; if (rem > nameSize) rem = nameSize; const Byte *p2 = p + kLocalHeaderSize; for (size_t i = 0; i < rem; i++) if (p2[i] == 0) { // we support some "bad" zip archives that contain zeros after name for (size_t k = i + 1; k < rem; k++) if (p2[k] != 0) return k_IsArc_Res_NO; break; /* if (i != nameSize - 1) return k_IsArc_Res_NO; */ } } if (size < extraOffset) return k_IsArc_Res_NEED_MORE; if (extraSize > 0) { p += extraOffset; size -= extraOffset; while (extraSize != 0) { if (extraSize < 4) { // 7-Zip before 9.31 created incorrect WsAES Extra in folder's local headers. // so we return k_IsArc_Res_YES to support such archives. // return k_IsArc_Res_NO; // do we need to support such extra ? return k_IsArc_Res_YES; } if (size < 4) return k_IsArc_Res_NEED_MORE; unsigned dataSize = Get16(p + 2); size -= 4; extraSize -= 4; p += 4; if (dataSize > extraSize) { // It can be error on header. // We want to support such rare case bad archives. // We use additional checks to reduce false-positive probability. if (nameSize == 0 || nameSize > (1 << 9) || extraSize > (1 << 9)) return k_IsArc_Res_NO; return k_IsArc_Res_YES; } if (dataSize > size) return k_IsArc_Res_NEED_MORE; size -= dataSize; extraSize -= dataSize; p += dataSize; } } return k_IsArc_Res_YES; } static UInt32 IsArc_Zip_2(const Byte *p, size_t size, bool isFinal) { UInt32 res = IsArc_Zip(p, size); if (res == k_IsArc_Res_NEED_MORE && isFinal) return k_IsArc_Res_NO; return res; } MY_NO_INLINE static const Byte *FindPK(const Byte *p, const Byte *limit) { for (;;) { for (;;) { Byte b0; b0 = p[0]; if (p >= limit) return p; p++; if (b0 == 0x50) break; b0 = p[0]; if (p >= limit) return p; p++; if (b0 == 0x50) break; } if (p[0] == 0x4B) return p - 1; } } /* ---------- FindMarker ---------- returns: S_OK: ArcInfo.MarkerVolIndex : volume of marker ArcInfo.MarkerPos : Pos of first signature ArcInfo.MarkerPos2 : Pos of main signature (local item signature in most cases) _streamPos : stream pos _cnt : The number of virtal Bytes after start of search to offset after signature _signature : main signature S_FALSE: can't find marker, or there is some non-zip data after marker Error code: stream reading error. */ HRESULT CInArchive::FindMarker(const UInt64 *searchLimit) { ArcInfo.MarkerPos = GetVirtStreamPos(); ArcInfo.MarkerPos2 = ArcInfo.MarkerPos; ArcInfo.MarkerVolIndex = Vols.StreamIndex; _cnt = 0; CanStartNewVol = false; if (searchLimit && *searchLimit == 0) { Byte startBuf[kMarkerSize]; RINOK(ReadFromCache_FALSE(startBuf, kMarkerSize)); UInt32 marker = Get32(startBuf); _signature = marker; if ( marker == NSignature::kNoSpan || marker == NSignature::kSpan) { RINOK(ReadFromCache_FALSE(startBuf, kMarkerSize)); _signature = Get32(startBuf); } if ( _signature != NSignature::kEcd && _signature != NSignature::kEcd64 && _signature != NSignature::kLocalFileHeader) return S_FALSE; ArcInfo.MarkerPos2 = GetVirtStreamPos() - 4; ArcInfo.IsSpanMode = (marker == NSignature::kSpan); // we use weak test in case of (*searchLimit == 0) // since error will be detected later in Open function return S_OK; } const size_t kCheckSize = (size_t)1 << 16; // must be smaller than kBufSize const size_t kBufSize = (size_t)1 << 17; // must be larger than kCheckSize if (Buffer.Size() < kBufSize) { InitBuf(); Buffer.AllocAtLeast(kBufSize); if (!Buffer.IsAllocated()) return E_OUTOFMEMORY; } _inBufMode = true; UInt64 progressPrev = 0; for (;;) { RINOK(LookAhead(kBufSize)); const size_t avail = GetAvail(); size_t limitPos; const bool isFinished = (avail != kBufSize); if (isFinished) { const unsigned kMinAllowed = 4; if (avail <= kMinAllowed) { if ( !IsMultiVol || Vols.StreamIndex < 0 || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) break; SkipLookahed(avail); const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; if (!s.Stream) break; RINOK(s.SeekToStart()); InitBuf(); Vols.StreamIndex++; _streamPos = 0; Stream = s.Stream; continue; } limitPos = avail - kMinAllowed; } else limitPos = (avail - kCheckSize); // we don't check at (limitPos) for good fast aligned operations if (searchLimit) { if (_cnt > *searchLimit) break; UInt64 rem = *searchLimit - _cnt; if (limitPos > rem) limitPos = (size_t)rem + 1; } if (limitPos == 0) break; const Byte * const pStart = Buffer + _bufPos; const Byte * p = pStart; const Byte * const limit = pStart + limitPos; for (;; p++) { p = FindPK(p, limit); if (p >= limit) break; const size_t rem = (size_t)(pStart + avail - p); UInt32 res = IsArc_Zip_2(p, rem, isFinished); if (res != k_IsArc_Res_NO) { if (rem < kMarkerSize) return S_FALSE; _signature = Get32(p); SkipLookahed((size_t)(p - pStart)); ArcInfo.MarkerVolIndex = Vols.StreamIndex; ArcInfo.MarkerPos = GetVirtStreamPos(); ArcInfo.MarkerPos2 = ArcInfo.MarkerPos; SkipLookahed(4); if ( _signature == NSignature::kNoSpan || _signature == NSignature::kSpan) { if (rem < kMarkerSize * 2) return S_FALSE; ArcInfo.IsSpanMode = (_signature == NSignature::kSpan); _signature = Get32(p + 4); ArcInfo.MarkerPos2 += 4; SkipLookahed(4); } return S_OK; } } if (!IsMultiVol && isFinished) break; SkipLookahed((size_t)(p - pStart)); if (Callback && (_cnt - progressPrev) >= ((UInt32)1 << 23)) { progressPrev = _cnt; // const UInt64 numFiles64 = 0; RINOK(Callback->SetCompleted(NULL, &_cnt)); } } return S_FALSE; } /* ---------- IncreaseRealPosition ---------- moves virtual offset in virtual stream. changing to new volumes is allowed */ HRESULT CInArchive::IncreaseRealPosition(UInt64 offset, bool &isFinished) { isFinished = false; for (;;) { const size_t avail = GetAvail(); if (offset <= avail) { _bufPos += (size_t)offset; _cnt += offset; return S_OK; } _cnt += avail; offset -= avail; _bufCached = 0; _bufPos = 0; if (!_inBufMode) break; CanStartNewVol = true; LookAhead(1); if (GetAvail() == 0) return S_OK; } if (!IsMultiVol) { _cnt += offset; return Stream->Seek((Int64)offset, STREAM_SEEK_CUR, &_streamPos); } for (;;) { if (offset == 0) return S_OK; if (Vols.StreamIndex < 0) return S_FALSE; if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) { isFinished = true; return S_OK; } { const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex]; if (!s.Stream) { isFinished = true; return S_OK; } if (_streamPos > s.Size) return S_FALSE; const UInt64 rem = s.Size - _streamPos; if ((UInt64)offset <= rem) { _cnt += offset; return Stream->Seek((Int64)offset, STREAM_SEEK_CUR, &_streamPos); } RINOK(Seek_SavePos(s.Size)); offset -= rem; _cnt += rem; } Stream = NULL; _streamPos = 0; Vols.StreamIndex++; if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) { isFinished = true; return S_OK; } const CVols::CSubStreamInfo &s2 = Vols.Streams[(unsigned)Vols.StreamIndex]; if (!s2.Stream) { isFinished = true; return S_OK; } Stream = s2.Stream; RINOK(Seek_SavePos(0)); } } /* ---------- LookAhead ---------- Reads data to buffer, if required. It can read from volumes as long as Buffer.Size(). But it moves to new volume, only if it's required to provide minRequired bytes in buffer. in: (minRequired <= Buffer.Size()) return: S_OK : if (GetAvail() < minRequired) after function return, it's end of stream(s) data, or no new volume stream. Error codes: IInStream::Read() error or IInStream::Seek() error for multivol */ HRESULT CInArchive::LookAhead(size_t minRequired) { for (;;) { const size_t avail = GetAvail(); if (minRequired <= avail) return S_OK; if (_bufPos != 0) { if (avail != 0) memmove(Buffer, Buffer + _bufPos, avail); _bufPos = 0; _bufCached = avail; } const size_t pos = _bufCached; UInt32 processed = 0; HRESULT res = Stream->Read(Buffer + pos, (UInt32)(Buffer.Size() - pos), &processed); _streamPos += processed; _bufCached += processed; if (res != S_OK) return res; if (processed != 0) continue; if ( !IsMultiVol || !CanStartNewVol || Vols.StreamIndex < 0 || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) return S_OK; const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; if (!s.Stream) return S_OK; RINOK(s.SeekToStart()); Vols.StreamIndex++; _streamPos = 0; Stream = s.Stream; // Vols.NeedSeek = false; } } class CUnexpectEnd {}; /* ---------- SafeRead ---------- reads data of exact size from stream(s) in: _inBufMode if (CanStartNewVol) it can go to next volume before first byte reading, if there is end of volume data. in, out: _streamPos : position in Stream Stream Vols : if (IsMultiVol) _cnt out: (CanStartNewVol == false), if some data was read return: S_OK : success reading of requested data exceptions: CSystemException() - stream reading error CUnexpectEnd() : could not read data of requested size */ void CInArchive::SafeRead(Byte *data, unsigned size) { unsigned processed; HRESULT result = ReadFromCache(data, size, processed); if (result != S_OK) throw CSystemException(result); if (size != processed) throw CUnexpectEnd(); } void CInArchive::ReadBuffer(CByteBuffer &buffer, unsigned size) { buffer.Alloc(size); if (size != 0) SafeRead(buffer, size); } // Byte CInArchive::ReadByte () { Byte b; SafeRead(&b, 1); return b; } // UInt16 CInArchive::ReadUInt16() { Byte buf[2]; SafeRead(buf, 2); return Get16(buf); } UInt32 CInArchive::ReadUInt32() { Byte buf[4]; SafeRead(buf, 4); return Get32(buf); } UInt64 CInArchive::ReadUInt64() { Byte buf[8]; SafeRead(buf, 8); return Get64(buf); } void CInArchive::ReadSignature() { CanStartNewVol = true; _signature = ReadUInt32(); // CanStartNewVol = false; // it's already changed in SafeRead } // we Skip() inside headers only, so no need for stream change in multivol. void CInArchive::Skip(size_t num) { while (num != 0) { const unsigned kBufSize = (size_t)1 << 10; Byte buf[kBufSize]; unsigned step = kBufSize; if (step > num) step = (unsigned)num; SafeRead(buf, step); num -= step; } } /* HRESULT CInArchive::Callback_Completed(unsigned numFiles) { const UInt64 numFiles64 = numFiles; return Callback->SetCompleted(&numFiles64, &_cnt); } */ HRESULT CInArchive::Skip64(UInt64 num, unsigned numFiles) { if (num == 0) return S_OK; for (;;) { size_t step = (size_t)1 << 24; if (step > num) step = (size_t)num; Skip(step); num -= step; if (num == 0) return S_OK; if (Callback) { const UInt64 numFiles64 = numFiles; RINOK(Callback->SetCompleted(&numFiles64, &_cnt)); } } } bool CInArchive::ReadFileName(unsigned size, AString &s) { if (size == 0) { s.Empty(); return true; } char *p = s.GetBuf(size); SafeRead((Byte *)p, size); unsigned i = size; do { if (p[i - 1] != 0) break; } while (--i); s.ReleaseBuf_CalcLen(size); return s.Len() == i; } #define ZIP64_IS_32_MAX(n) ((n) == 0xFFFFFFFF) #define ZIP64_IS_16_MAX(n) ((n) == 0xFFFF) bool CInArchive::ReadExtra(const CLocalItem &item, unsigned extraSize, CExtraBlock &extra, UInt64 &unpackSize, UInt64 &packSize, CItem *cdItem) { extra.Clear(); while (extraSize >= 4) { CExtraSubBlock subBlock; const UInt32 pair = ReadUInt32(); subBlock.ID = (pair & 0xFFFF); unsigned size = (unsigned)(pair >> 16); extraSize -= 4; if (size > extraSize) { // it's error in extra HeadersWarning = true; extra.Error = true; Skip(extraSize); return false; } extraSize -= size; if (subBlock.ID == NFileHeader::NExtraID::kZip64) { extra.IsZip64 = true; bool isOK = true; if (!cdItem && size == 16 && !ZIP64_IS_32_MAX(unpackSize) && !ZIP64_IS_32_MAX(packSize)) { /* Win10 Explorer's "Send to Zip" for big (3500 MiB) files creates Zip64 Extra in local file header. But if both uncompressed and compressed sizes are smaller than 4 GiB, Win10 doesn't store 0xFFFFFFFF in 32-bit fields as expected by zip specification. 21.04: we ignore these minor errors in Win10 zip archives. */ if (ReadUInt64() != unpackSize) isOK = false; if (ReadUInt64() != packSize) isOK = false; size = 0; } else { if (ZIP64_IS_32_MAX(unpackSize)) { if (size < 8) isOK = false; else { size -= 8; unpackSize = ReadUInt64(); }} if (isOK && ZIP64_IS_32_MAX(packSize)) { if (size < 8) isOK = false; else { size -= 8; packSize = ReadUInt64(); }} if (cdItem) { if (isOK) { if (ZIP64_IS_32_MAX(cdItem->LocalHeaderPos)) { if (size < 8) isOK = false; else { size -= 8; cdItem->LocalHeaderPos = ReadUInt64(); }} /* else if (size == 8) { size -= 8; const UInt64 v = ReadUInt64(); // soong_zip, an AOSP tool (written in the Go) writes incorrect value. // we can ignore that minor error here if (v != cdItem->LocalHeaderPos) isOK = false; // ignore error // isOK = false; // force error } */ } if (isOK && ZIP64_IS_16_MAX(cdItem->Disk)) { if (size < 4) isOK = false; else { size -= 4; cdItem->Disk = ReadUInt32(); }} } } if (!isOK || size != 0) { HeadersWarning = true; extra.Error = true; extra.IsZip64_Error = true; Skip(size); } } else { ReadBuffer(subBlock.Data, size); extra.SubBlocks.Add(subBlock); if (subBlock.ID == NFileHeader::NExtraID::kIzUnicodeName) { if (!subBlock.CheckIzUnicode(item.Name)) extra.Error = true; } } } if (extraSize != 0) { ExtraMinorError = true; extra.MinorError = true; // 7-Zip before 9.31 created incorrect WsAES Extra in folder's local headers. // so we don't return false, but just set warning flag // return false; Skip(extraSize); } return true; } bool CInArchive::ReadLocalItem(CItemEx &item) { item.Disk = 0; if (IsMultiVol && Vols.StreamIndex >= 0) item.Disk = (UInt32)Vols.StreamIndex; const unsigned kPureHeaderSize = kLocalHeaderSize - 4; Byte p[kPureHeaderSize]; SafeRead(p, kPureHeaderSize); { unsigned i; for (i = 0; i < kPureHeaderSize && p[i] == 0; i++); if (i == kPureHeaderSize) return false; } item.ExtractVersion.Version = p[0]; item.ExtractVersion.HostOS = p[1]; G16(2, item.Flags); G16(4, item.Method); G32(6, item.Time); G32(10, item.Crc); G32(14, item.PackSize); G32(18, item.Size); const unsigned nameSize = Get16(p + 22); const unsigned extraSize = Get16(p + 24); bool isOkName = ReadFileName(nameSize, item.Name); item.LocalFullHeaderSize = kLocalHeaderSize + (UInt32)nameSize + extraSize; item.DescriptorWasRead = false; /* if (item.IsDir()) item.Size = 0; // check It */ if (extraSize > 0) { if (!ReadExtra(item, extraSize, item.LocalExtra, item.Size, item.PackSize, NULL)) { /* Most of archives are OK for Extra. But there are some rare cases that have error. And if error in first item, it can't open archive. So we ignore that error */ // return false; } } if (!CheckDosTime(item.Time)) { HeadersWarning = true; // return false; } if (item.Name.Len() != nameSize) { // we support some "bad" zip archives that contain zeros after name if (!isOkName) return false; HeadersWarning = true; } // return item.LocalFullHeaderSize <= ((UInt32)1 << 16); return true; } static bool FlagsAreSame(const CItem &i1, const CItem &i2_cd) { if (i1.Method != i2_cd.Method) return false; UInt32 mask = i1.Flags ^ i2_cd.Flags; if (mask == 0) return true; switch (i1.Method) { case NFileHeader::NCompressionMethod::kDeflate: mask &= 0x7FF9; break; default: if (i1.Method <= NFileHeader::NCompressionMethod::kImplode) mask &= 0x7FFF; } // we can ignore utf8 flag, if name is ascii if (mask & NFileHeader::NFlags::kUtf8) if (i1.Name.IsAscii() && i2_cd.Name.IsAscii()) mask &= ~NFileHeader::NFlags::kUtf8; // some bad archive in rare case can use descriptor without descriptor flag in Central Dir // if (i1.HasDescriptor()) mask &= ~NFileHeader::NFlags::kDescriptorUsedMask; return (mask == 0); } // #ifdef _WIN32 static bool AreEqualPaths_IgnoreSlashes(const char *s1, const char *s2) { for (;;) { char c1 = *s1++; char c2 = *s2++; if (c1 == c2) { if (c1 == 0) return true; } else { if (c1 == '\\') c1 = '/'; if (c2 == '\\') c2 = '/'; if (c1 != c2) return false; } } } // #endif static bool AreItemsEqual(const CItemEx &localItem, const CItemEx &cdItem) { if (!FlagsAreSame(localItem, cdItem)) return false; if (!localItem.HasDescriptor()) { if (cdItem.PackSize != localItem.PackSize || cdItem.Size != localItem.Size || (cdItem.Crc != localItem.Crc && cdItem.Crc != 0)) // some program writes 0 to crc field in central directory return false; } /* pkzip 2.50 creates incorrect archives. It uses - WIN encoding for name in local header - OEM encoding for name in central header We don't support these strange items. */ /* if (cdItem.Name.Len() != localItem.Name.Len()) return false; */ if (cdItem.Name != localItem.Name) { // #ifdef _WIN32 // some xap files use backslash in central dir items. // we can ignore such errors in windows, where all slashes are converted to backslashes unsigned hostOs = cdItem.GetHostOS(); if (hostOs == NFileHeader::NHostOS::kFAT || hostOs == NFileHeader::NHostOS::kNTFS) { if (!AreEqualPaths_IgnoreSlashes(cdItem.Name, localItem.Name)) { // pkzip 2.50 uses DOS encoding in central dir and WIN encoding in local header. // so we ignore that error if (hostOs != NFileHeader::NHostOS::kFAT || cdItem.MadeByVersion.Version < 25 || cdItem.MadeByVersion.Version > 40) return false; } } /* else #endif return false; */ } return true; } HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail, bool &headersError) { InitBuf(); _inBufMode = false; isAvail = true; headersError = false; if (item.FromLocal) return S_OK; try { UInt64 offset = item.LocalHeaderPos; if (IsMultiVol) { if (item.Disk >= Vols.Streams.Size()) { isAvail = false; return S_FALSE; } Stream = Vols.Streams[item.Disk].Stream; Vols.StreamIndex = (int)item.Disk; if (!Stream) { isAvail = false; return S_FALSE; } } else { if (UseDisk_in_SingleVol && item.Disk != EcdVolIndex) { isAvail = false; return S_FALSE; } Stream = StreamRef; offset = (UInt64)((Int64)offset + ArcInfo.Base); if (ArcInfo.Base < 0 && (Int64)offset < 0) { isAvail = false; return S_FALSE; } } RINOK(Seek_SavePos(offset)); /* // we can check buf mode InitBuf(); _inBufMode = true; Buffer.AllocAtLeast(1 << 10); */ CItemEx localItem; if (ReadUInt32() != NSignature::kLocalFileHeader) return S_FALSE; ReadLocalItem(localItem); if (!AreItemsEqual(localItem, item)) return S_FALSE; item.LocalFullHeaderSize = localItem.LocalFullHeaderSize; item.LocalExtra = localItem.LocalExtra; if (item.Crc != localItem.Crc && !localItem.HasDescriptor()) { item.Crc = localItem.Crc; headersError = true; } if ((item.Flags ^ localItem.Flags) & NFileHeader::NFlags::kDescriptorUsedMask) { item.Flags = (UInt16)(item.Flags ^ NFileHeader::NFlags::kDescriptorUsedMask); headersError = true; } item.FromLocal = true; } catch(...) { return S_FALSE; } return S_OK; } /* ---------- FindDescriptor ---------- in: _streamPos : position in Stream Stream : Vols : if (IsMultiVol) action: searches descriptor in input stream(s). sets item.DescriptorWasRead = true; item.Size item.PackSize item.Crc if descriptor was found out: S_OK: if ( item.DescriptorWasRead) : if descriptor was found if (!item.DescriptorWasRead) : if descriptor was not found : unexpected end of stream(s) S_FALSE: if no items or there is just one item with strange properies that doesn't look like real archive. another error code: Callback error. exceptions : CSystemException() : stream reading error */ HRESULT CInArchive::FindDescriptor(CItemEx &item, unsigned numFiles) { // const size_t kBufSize = (size_t)1 << 5; // don't increase it too much. It reads data look ahead. // Buffer.Alloc(kBufSize); // Byte *buf = Buffer; UInt64 packedSize = 0; UInt64 progressPrev = _cnt; for (;;) { /* appnote specification claims that we must use 64-bit descriptor, if there is zip64 extra. But some old third-party xps archives used 64-bit descriptor without zip64 extra. */ // unsigned descriptorSize = kDataDescriptorSize64 + kNextSignatureSize; // const unsigned kNextSignatureSize = 0; // we can disable check for next signatuire const unsigned kNextSignatureSize = 4; // we check also for signature for next File headear const unsigned descriptorSize4 = item.GetDescriptorSize() + kNextSignatureSize; if (descriptorSize4 > Buffer.Size()) return E_FAIL; // size_t processedSize; CanStartNewVol = true; RINOK(LookAhead(descriptorSize4)); const size_t avail = GetAvail(); if (avail < descriptorSize4) { // we write to packSize all these available bytes. // later it's simpler to work with such value than with 0 // if (item.PackSize == 0) item.PackSize = packedSize + avail; if (item.Method == 0) item.Size = item.PackSize; SkipLookahed(avail); return S_OK; } const Byte * const pStart = Buffer + _bufPos; const Byte * p = pStart; const Byte * const limit = pStart + (avail - descriptorSize4); for (; p <= limit; p++) { // descriptor signature field is Info-ZIP's extension to pkware Zip specification. // New ZIP specification also allows descriptorSignature. p = FindPK(p, limit + 1); if (p > limit) break; /* if (*p != 0x50) continue; */ if (Get32(p) != NSignature::kDataDescriptor) continue; // we check next signatuire after descriptor // maybe we need check only 2 bytes "PK" instead of 4 bytes, if some another type of header is possible after descriptor const UInt32 sig = Get32(p + descriptorSize4 - kNextSignatureSize); if ( sig != NSignature::kLocalFileHeader && sig != NSignature::kCentralFileHeader) continue; const UInt64 packSizeCur = packedSize + (size_t)(p - pStart); if (descriptorSize4 == kDataDescriptorSize64 + kNextSignatureSize) // if (item.LocalExtra.IsZip64) { const UInt64 descriptorPackSize = Get64(p + 8); if (descriptorPackSize != packSizeCur) continue; item.Size = Get64(p + 16); } else { const UInt32 descriptorPackSize = Get32(p + 8); if (descriptorPackSize != (UInt32)packSizeCur) continue; item.Size = Get32(p + 12); // that item.Size can be truncated to 32-bit value here } // We write calculated 64-bit packSize, even if descriptor64 was not used item.PackSize = packSizeCur; item.DescriptorWasRead = true; item.Crc = Get32(p + 4); const size_t skip = (size_t)(p - pStart) + descriptorSize4 - kNextSignatureSize; SkipLookahed(skip); return S_OK; } const size_t skip = (size_t)(p - pStart); SkipLookahed(skip); packedSize += skip; if (Callback) if (_cnt - progressPrev >= ((UInt32)1 << 22)) { progressPrev = _cnt; const UInt64 numFiles64 = numFiles; RINOK(Callback->SetCompleted(&numFiles64, &_cnt)); } } } HRESULT CInArchive::CheckDescriptor(const CItemEx &item) { if (!item.HasDescriptor()) return S_OK; // pkzip's version without descriptor signature is not supported bool isFinished = false; RINOK(IncreaseRealPosition(item.PackSize, isFinished)); if (isFinished) return S_FALSE; /* if (!IsMultiVol) { RINOK(Seek_SavePos(ArcInfo.Base + item.GetDataPosition() + item.PackSize)); } */ Byte buf[kDataDescriptorSize64]; try { CanStartNewVol = true; SafeRead(buf, item.GetDescriptorSize()); } catch (const CSystemException &e) { return e.ErrorCode; } // catch (const CUnexpectEnd &) catch(...) { return S_FALSE; } // RINOK(ReadStream_FALSE(Stream, buf, item.GetDescriptorSize())); if (Get32(buf) != NSignature::kDataDescriptor) return S_FALSE; UInt32 crc = Get32(buf + 4); UInt64 packSize, unpackSize; if (item.LocalExtra.IsZip64) { packSize = Get64(buf + 8); unpackSize = Get64(buf + 16); } else { packSize = Get32(buf + 8); unpackSize = Get32(buf + 12); } if (crc != item.Crc || item.PackSize != packSize || item.Size != unpackSize) return S_FALSE; return S_OK; } HRESULT CInArchive::ReadLocalItemAfterCdItemFull(CItemEx &item) { if (item.FromLocal) return S_OK; try { bool isAvail = true; bool headersError = false; RINOK(ReadLocalItemAfterCdItem(item, isAvail, headersError)); if (headersError) return S_FALSE; if (item.HasDescriptor()) return CheckDescriptor(item); } catch(...) { return S_FALSE; } return S_OK; } HRESULT CInArchive::ReadCdItem(CItemEx &item) { item.FromCentral = true; Byte p[kCentralHeaderSize - 4]; SafeRead(p, kCentralHeaderSize - 4); item.MadeByVersion.Version = p[0]; item.MadeByVersion.HostOS = p[1]; item.ExtractVersion.Version = p[2]; item.ExtractVersion.HostOS = p[3]; G16(4, item.Flags); G16(6, item.Method); G32(8, item.Time); G32(12, item.Crc); G32(16, item.PackSize); G32(20, item.Size); const unsigned nameSize = Get16(p + 24); const unsigned extraSize = Get16(p + 26); const unsigned commentSize = Get16(p + 28); G16(30, item.Disk); G16(32, item.InternalAttrib); G32(34, item.ExternalAttrib); G32(38, item.LocalHeaderPos); ReadFileName(nameSize, item.Name); if (extraSize > 0) ReadExtra(item, extraSize, item.CentralExtra, item.Size, item.PackSize, &item); // May be these strings must be deleted /* if (item.IsDir()) item.Size = 0; */ ReadBuffer(item.Comment, commentSize); return S_OK; } HRESULT CInArchive::TryEcd64(UInt64 offset, CCdInfo &cdInfo) { if (offset >= ((UInt64)1 << 63)) return S_FALSE; Byte buf[kEcd64_FullSize]; RINOK(SeekToVol(Vols.StreamIndex, offset)); RINOK(ReadFromCache_FALSE(buf, kEcd64_FullSize)); if (Get32(buf) != NSignature::kEcd64) return S_FALSE; UInt64 mainSize = Get64(buf + 4); if (mainSize < kEcd64_MainSize || mainSize > ((UInt64)1 << 40)) return S_FALSE; cdInfo.ParseEcd64e(buf + 12); return S_OK; } HRESULT CInArchive::FindCd(bool checkOffsetMode) { CCdInfo &cdInfo = Vols.ecd; UInt64 endPos; // There are no useful data in cache in most cases here. // So here we don't use cache data from previous operations . InitBuf(); RINOK(Stream->Seek(0, STREAM_SEEK_END, &endPos)); _streamPos = endPos; // const UInt32 kBufSizeMax2 = ((UInt32)1 << 16) + kEcdSize + kEcd64Locator_Size + kEcd64_FullSize; const size_t kBufSizeMax = ((size_t)1 << 17); // must be larger than kBufSizeMax2 const size_t bufSize = (endPos < kBufSizeMax) ? (size_t)endPos : kBufSizeMax; if (bufSize < kEcdSize) return S_FALSE; // CByteArr byteBuffer(bufSize); if (Buffer.Size() < kBufSizeMax) { // InitBuf(); Buffer.AllocAtLeast(kBufSizeMax); if (!Buffer.IsAllocated()) return E_OUTOFMEMORY; } RINOK(Seek_SavePos(endPos - bufSize)); size_t processed = bufSize; HRESULT res = ReadStream(Stream, Buffer, &processed); _streamPos += processed; _bufCached = processed; _bufPos = 0; _cnt += processed; if (res != S_OK) return res; if (processed != bufSize) return S_FALSE; for (size_t i = bufSize - kEcdSize + 1;;) { if (i == 0) return S_FALSE; const Byte *buf = Buffer; for (;;) { i--; if (buf[i] == 0x50) break; if (i == 0) return S_FALSE; } if (Get32(buf + i) != NSignature::kEcd) continue; cdInfo.ParseEcd32(buf + i); if (i >= kEcd64Locator_Size) { const size_t locatorIndex = i - kEcd64Locator_Size; if (Get32(buf + locatorIndex) == NSignature::kEcd64Locator) { CLocator locator; locator.Parse(buf + locatorIndex + 4); UInt32 numDisks = locator.NumDisks; // we ignore the error, where some zip creators use (NumDisks == 0) if (numDisks == 0) numDisks = 1; if ((cdInfo.ThisDisk == numDisks - 1 || ZIP64_IS_16_MAX(cdInfo.ThisDisk)) && locator.Ecd64Disk < numDisks) { if (locator.Ecd64Disk != cdInfo.ThisDisk && !ZIP64_IS_16_MAX(cdInfo.ThisDisk)) return E_NOTIMPL; // Most of the zip64 use fixed size Zip64 ECD // we try relative backward reading. UInt64 absEcd64 = endPos - bufSize + i - (kEcd64Locator_Size + kEcd64_FullSize); if (locatorIndex >= kEcd64_FullSize) if (checkOffsetMode || absEcd64 == locator.Ecd64Offset) { const Byte *ecd64 = buf + locatorIndex - kEcd64_FullSize; if (Get32(ecd64) == NSignature::kEcd64) { UInt64 mainEcd64Size = Get64(ecd64 + 4); if (mainEcd64Size == kEcd64_MainSize) { cdInfo.ParseEcd64e(ecd64 + 12); ArcInfo.Base = (Int64)(absEcd64 - locator.Ecd64Offset); // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; return S_OK; } } } // some zip64 use variable size Zip64 ECD. // we try to use absolute offset from locator. if (absEcd64 != locator.Ecd64Offset) { if (TryEcd64(locator.Ecd64Offset, cdInfo) == S_OK) { ArcInfo.Base = 0; // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; return S_OK; } } // for variable Zip64 ECD with for archives with offset != 0. if (checkOffsetMode && ArcInfo.MarkerPos != 0 && ArcInfo.MarkerPos + locator.Ecd64Offset != absEcd64) { if (TryEcd64(ArcInfo.MarkerPos + locator.Ecd64Offset, cdInfo) == S_OK) { ArcInfo.Base = (Int64)ArcInfo.MarkerPos; // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; return S_OK; } } } } } // bool isVolMode = (Vols.EndVolIndex != -1); // UInt32 searchDisk = (isVolMode ? Vols.EndVolIndex : 0); if (/* searchDisk == thisDisk && */ cdInfo.CdDisk <= cdInfo.ThisDisk) { // if (isVolMode) { if (cdInfo.CdDisk != cdInfo.ThisDisk) return S_OK; } UInt64 absEcdPos = endPos - bufSize + i; UInt64 cdEnd = cdInfo.Size + cdInfo.Offset; ArcInfo.Base = 0; // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; if (absEcdPos != cdEnd) { /* if (cdInfo.Offset <= 16 && cdInfo.Size != 0) { // here we support some rare ZIP files with Central directory at the start ArcInfo.Base = 0; } else */ ArcInfo.Base = (Int64)(absEcdPos - cdEnd); } return S_OK; } } } HRESULT CInArchive::TryReadCd(CObjectVector &items, const CCdInfo &cdInfo, UInt64 cdOffset, UInt64 cdSize) { items.Clear(); IsCdUnsorted = false; // _startLocalFromCd_Disk = (UInt32)(Int32)-1; // _startLocalFromCd_Offset = (UInt64)(Int64)-1; RINOK(SeekToVol(IsMultiVol ? (int)cdInfo.CdDisk : -1, cdOffset)); _inBufMode = true; _cnt = 0; if (Callback) { RINOK(Callback->SetTotal(&cdInfo.NumEntries, IsMultiVol ? &Vols.TotalBytesSize : NULL)); } UInt64 numFileExpected = cdInfo.NumEntries; const UInt64 *totalFilesPtr = &numFileExpected; bool isCorrect_NumEntries = (cdInfo.IsFromEcd64 || numFileExpected >= ((UInt32)1 << 16)); while (_cnt < cdSize) { CanStartNewVol = true; if (ReadUInt32() != NSignature::kCentralFileHeader) return S_FALSE; CanStartNewVol = false; { CItemEx cdItem; RINOK(ReadCdItem(cdItem)); /* if (cdItem.Disk < _startLocalFromCd_Disk || cdItem.Disk == _startLocalFromCd_Disk && cdItem.LocalHeaderPos < _startLocalFromCd_Offset) { _startLocalFromCd_Disk = cdItem.Disk; _startLocalFromCd_Offset = cdItem.LocalHeaderPos; } */ if (items.Size() > 0 && !IsCdUnsorted) { const CItemEx &prev = items.Back(); if (cdItem.Disk < prev.Disk || (cdItem.Disk == prev.Disk && cdItem.LocalHeaderPos < prev.LocalHeaderPos)) IsCdUnsorted = true; } items.Add(cdItem); } if (Callback && (items.Size() & 0xFFF) == 0) { const UInt64 numFiles = items.Size(); if (numFiles > numFileExpected && totalFilesPtr) { if (isCorrect_NumEntries) totalFilesPtr = NULL; else while (numFiles > numFileExpected) numFileExpected += (UInt32)1 << 16; RINOK(Callback->SetTotal(totalFilesPtr, NULL)); } RINOK(Callback->SetCompleted(&numFiles, &_cnt)); } } CanStartNewVol = true; return (_cnt == cdSize) ? S_OK : S_FALSE; } /* static int CompareCdItems(void *const *elem1, void *const *elem2, void *) { const CItemEx *i1 = *(const CItemEx **)elem1; const CItemEx *i2 = *(const CItemEx **)elem2; if (i1->Disk < i2->Disk) return -1; if (i1->Disk > i2->Disk) return 1; if (i1->LocalHeaderPos < i2->LocalHeaderPos) return -1; if (i1->LocalHeaderPos > i2->LocalHeaderPos) return 1; if (i1 < i2) return -1; if (i1 > i2) return 1; return 0; } */ HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 &cdOffset, UInt64 &cdSize) { bool checkOffsetMode = true; if (IsMultiVol) { if (Vols.EndVolIndex == -1) return S_FALSE; Stream = Vols.Streams[(unsigned)Vols.EndVolIndex].Stream; if (!Vols.StartIsZip) checkOffsetMode = false; } else Stream = StartStream; if (!Vols.ecd_wasRead) { RINOK(FindCd(checkOffsetMode)); } CCdInfo &cdInfo = Vols.ecd; HRESULT res = S_FALSE; cdSize = cdInfo.Size; cdOffset = cdInfo.Offset; cdDisk = cdInfo.CdDisk; if (!IsMultiVol) { if (cdInfo.ThisDisk != cdInfo.CdDisk) return S_FALSE; } const UInt64 base = (IsMultiVol ? 0 : (UInt64)ArcInfo.Base); res = TryReadCd(items, cdInfo, base + cdOffset, cdSize); if (res == S_FALSE && !IsMultiVol && base != ArcInfo.MarkerPos) { // do we need that additional attempt to read cd? res = TryReadCd(items, cdInfo, ArcInfo.MarkerPos + cdOffset, cdSize); if (res == S_OK) ArcInfo.Base = (Int64)ArcInfo.MarkerPos; } // Some rare case files are unsorted // items.Sort(CompareCdItems, NULL); return res; } static int FindItem(const CObjectVector &items, const CItemEx &item) { unsigned left = 0, right = items.Size(); for (;;) { if (left >= right) return -1; const unsigned index = (unsigned)(((size_t)left + (size_t)right) / 2); const CItemEx &item2 = items[index]; if (item.Disk < item2.Disk) right = index; else if (item.Disk > item2.Disk) left = index + 1; else if (item.LocalHeaderPos == item2.LocalHeaderPos) return (int)index; else if (item.LocalHeaderPos < item2.LocalHeaderPos) right = index; else left = index + 1; } } static bool IsStrangeItem(const CItem &item) { return item.Name.Len() > (1 << 14) || item.Method > (1 << 8); } /* ---------- ReadLocals ---------- in: (_signature == NSignature::kLocalFileHeader) VirtStreamPos : after _signature : position in Stream Stream : Vols : if (IsMultiVol) (_inBufMode == false) action: it parses local items. if ( IsMultiVol) it writes absolute offsets to CItemEx::LocalHeaderPos if (!IsMultiVol) it writes relative (from ArcInfo.Base) offsets to CItemEx::LocalHeaderPos later we can correct CItemEx::LocalHeaderPos values, if some new value for ArcInfo.Base will be detected out: S_OK: (_signature != NSignature::kLocalFileHeade) _streamPos : after _signature S_FALSE: if no items or there is just one item with strange properies that doesn't look like real archive. another error code: stream reading error or Callback error. CUnexpectEnd() exception : it's not fatal exception here. It means that reading was interrupted by unexpected end of input stream, but some CItemEx items were parsed OK. We can stop further archive parsing. But we can use all filled CItemEx items. */ HRESULT CInArchive::ReadLocals(CObjectVector &items) { items.Clear(); UInt64 progressPrev = _cnt; if (Callback) { RINOK(Callback->SetTotal(NULL, IsMultiVol ? &Vols.TotalBytesSize : NULL)); } while (_signature == NSignature::kLocalFileHeader) { CItemEx item; item.LocalHeaderPos = GetVirtStreamPos() - 4; if (!IsMultiVol) item.LocalHeaderPos = (UInt64)((Int64)item.LocalHeaderPos - ArcInfo.Base); try { ReadLocalItem(item); item.FromLocal = true; bool isFinished = false; if (item.HasDescriptor()) { RINOK(FindDescriptor(item, items.Size())); isFinished = !item.DescriptorWasRead; } else { if (item.PackSize >= ((UInt64)1 << 62)) throw CUnexpectEnd(); RINOK(IncreaseRealPosition(item.PackSize, isFinished)); } items.Add(item); if (isFinished) throw CUnexpectEnd(); ReadSignature(); } catch (CUnexpectEnd &) { if (items.IsEmpty() || (items.Size() == 1 && IsStrangeItem(items[0]))) return S_FALSE; throw; } if (Callback) if ((items.Size() & 0xFF) == 0 || _cnt - progressPrev >= ((UInt32)1 << 22)) { progressPrev = _cnt; const UInt64 numFiles = items.Size(); RINOK(Callback->SetCompleted(&numFiles, &_cnt)); } } if (items.Size() == 1 && _signature != NSignature::kCentralFileHeader) if (IsStrangeItem(items[0])) return S_FALSE; return S_OK; } HRESULT CVols::ParseArcName(IArchiveOpenVolumeCallback *volCallback) { UString name; { NWindows::NCOM::CPropVariant prop; RINOK(volCallback->GetProperty(kpidName, &prop)); if (prop.vt != VT_BSTR) return S_OK; name = prop.bstrVal; } const int dotPos = name.ReverseFind_Dot(); if (dotPos < 0) return S_OK; const UString ext = name.Ptr((unsigned)(dotPos + 1)); name.DeleteFrom((unsigned)(dotPos + 1)); StartVolIndex = (Int32)(-1); if (ext.IsEmpty()) return S_OK; { wchar_t c = ext[0]; IsUpperCase = (c >= 'A' && c <= 'Z'); if (ext.IsEqualTo_Ascii_NoCase("zip")) { BaseName = name; StartIsZ = true; StartIsZip = true; return S_OK; } else if (ext.IsEqualTo_Ascii_NoCase("exe")) { /* possible cases: - exe with zip inside - sfx: a.exe, a.z02, a.z03,... , a.zip a.exe is start volume. - zip renamed to exe */ StartIsExe = true; BaseName = name; StartVolIndex = 0; /* sfx-zip can use both arc.exe and arc.zip We can open arc.zip, if it was requesed to open arc.exe. But it's possible that arc.exe and arc.zip are not parts of same archive. So we can disable such operation */ // 18.04: we still want to open zip renamed to exe. /* { UString volName = name; volName += IsUpperCase ? "Z01" : "z01"; { CMyComPtr stream; HRESULT res2 = volCallback->GetStream(volName, &stream); if (res2 == S_OK) DisableVolsSearch = true; } } */ DisableVolsSearch = true; return S_OK; } else if (ext[0] == 'z' || ext[0] == 'Z') { if (ext.Len() < 3) return S_OK; const wchar_t *end = NULL; UInt32 volNum = ConvertStringToUInt32(ext.Ptr(1), &end); if (*end != 0 || volNum < 1 || volNum > ((UInt32)1 << 30)) return S_OK; StartVolIndex = (Int32)(volNum - 1); BaseName = name; StartIsZ = true; } else return S_OK; } UString volName = BaseName; volName += (IsUpperCase ? "ZIP" : "zip"); HRESULT res = volCallback->GetStream(volName, &ZipStream); if (res == S_FALSE || !ZipStream) { if (MissingName.IsEmpty()) { MissingZip = true; MissingName = volName; } return S_OK; } return res; } HRESULT CInArchive::ReadVols2(IArchiveOpenVolumeCallback *volCallback, unsigned start, int lastDisk, int zipDisk, unsigned numMissingVolsMax, unsigned &numMissingVols) { if (Vols.DisableVolsSearch) return S_OK; numMissingVols = 0; for (unsigned i = start;; i++) { if (lastDisk >= 0 && i >= (unsigned)lastDisk) break; if (i < Vols.Streams.Size()) if (Vols.Streams[i].Stream) continue; CMyComPtr stream; if ((int)i == zipDisk) { stream = Vols.ZipStream; } else if ((int)i == Vols.StartVolIndex) { stream = StartStream; } else { UString volName = Vols.BaseName; { volName += (char)(Vols.IsUpperCase ? 'Z' : 'z'); unsigned v = i + 1; if (v < 10) volName += '0'; volName.Add_UInt32(v); } HRESULT res = volCallback->GetStream(volName, &stream); if (res != S_OK && res != S_FALSE) return res; if (res == S_FALSE || !stream) { if (i == 0) { UString volName_exe = Vols.BaseName; volName_exe += (Vols.IsUpperCase ? "EXE" : "exe"); HRESULT res2 = volCallback->GetStream(volName_exe, &stream); if (res2 != S_OK && res2 != S_FALSE) return res2; res = res2; } } if (res == S_FALSE || !stream) { if (i == 1 && Vols.StartIsExe) return S_OK; if (Vols.MissingName.IsEmpty()) Vols.MissingName = volName; numMissingVols++; if (numMissingVols > numMissingVolsMax) return S_OK; if (lastDisk == -1 && numMissingVols != 0) return S_OK; continue; } } UInt64 size; UInt64 pos; RINOK(stream->Seek(0, STREAM_SEEK_CUR, &pos)); RINOK(stream->Seek(0, STREAM_SEEK_END, &size)); RINOK(stream->Seek((Int64)pos, STREAM_SEEK_SET, NULL)); while (i >= Vols.Streams.Size()) Vols.Streams.AddNew(); CVols::CSubStreamInfo &ss = Vols.Streams[i]; Vols.NumVols++; Vols.TotalBytesSize += size; ss.Stream = stream; ss.Size = size; if ((int)i == zipDisk) { Vols.EndVolIndex = (int)(Vols.Streams.Size() - 1); break; } } return S_OK; } HRESULT CInArchive::ReadVols() { CMyComPtr volCallback; Callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volCallback); if (!volCallback) return S_OK; RINOK(Vols.ParseArcName(volCallback)); // const int startZIndex = Vols.StartVolIndex; if (!Vols.StartIsZ) { if (!Vols.StartIsExe) return S_OK; } int zipDisk = -1; int cdDisk = -1; if (Vols.StartIsZip) Vols.ZipStream = StartStream; if (Vols.ZipStream) { Stream = Vols.ZipStream; if (Vols.StartIsZip) Vols.StreamIndex = -1; else { Vols.StreamIndex = -2; InitBuf(); } HRESULT res = FindCd(true); CCdInfo &ecd = Vols.ecd; if (res == S_OK) { zipDisk = (int)ecd.ThisDisk; Vols.ecd_wasRead = true; // if is not multivol or bad multivol, we return to main single stream code if (ecd.ThisDisk == 0 || ecd.ThisDisk >= ((UInt32)1 << 30) || ecd.ThisDisk < ecd.CdDisk) return S_OK; cdDisk = (int)ecd.CdDisk; if (Vols.StartVolIndex < 0) Vols.StartVolIndex = (Int32)ecd.ThisDisk; else if ((UInt32)Vols.StartVolIndex >= ecd.ThisDisk) return S_OK; // Vols.StartVolIndex = ecd.ThisDisk; // Vols.EndVolIndex = ecd.ThisDisk; unsigned numMissingVols; if (cdDisk != zipDisk) { // get volumes required for cd. RINOK(ReadVols2(volCallback, (unsigned)cdDisk, zipDisk, zipDisk, 0, numMissingVols)); if (numMissingVols != 0) { // cdOK = false; } } } else if (res != S_FALSE) return res; } if (Vols.StartVolIndex < 0) { // is not mutivol; return S_OK; } /* if (!Vols.Streams.IsEmpty()) IsMultiVol = true; */ unsigned numMissingVols; if (cdDisk != 0) { // get volumes that were no requested still const unsigned kNumMissingVolsMax = 1 << 12; RINOK(ReadVols2(volCallback, 0, cdDisk < 0 ? -1 : cdDisk, zipDisk, kNumMissingVolsMax, numMissingVols)); } // if (Vols.StartVolIndex >= 0) { if (Vols.Streams.IsEmpty()) if (Vols.StartVolIndex > (1 << 20)) return S_OK; if ((unsigned)Vols.StartVolIndex >= Vols.Streams.Size() || !Vols.Streams[(unsigned)Vols.StartVolIndex].Stream) { // we get volumes starting from StartVolIndex, if they we not requested before know the volume index (if FindCd() was ok) RINOK(ReadVols2(volCallback, (unsigned)Vols.StartVolIndex, zipDisk, zipDisk, 0, numMissingVols)); } } if (Vols.ZipStream) { // if there is no another volumes and volumeIndex is too big, we don't use multivol mode if (Vols.Streams.IsEmpty()) if (zipDisk > (1 << 10)) return S_OK; if (zipDisk >= 0) { // we create item in Streams for ZipStream, if we know the volume index (if FindCd() was ok) RINOK(ReadVols2(volCallback, (unsigned)zipDisk, zipDisk + 1, zipDisk, 0, numMissingVols)); } } if (!Vols.Streams.IsEmpty()) { IsMultiVol = true; /* if (cdDisk) IsMultiVol = true; */ const int startZIndex = Vols.StartVolIndex; if (startZIndex >= 0) { // if all volumes before start volume are OK, we can start parsing from 0 // if there are missing volumes before startZIndex, we start parsing in current startZIndex if ((unsigned)startZIndex < Vols.Streams.Size()) { for (unsigned i = 0; i <= (unsigned)startZIndex; i++) if (!Vols.Streams[i].Stream) { Vols.StartParsingVol = startZIndex; break; } } } } return S_OK; } HRESULT CVols::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (size == 0) return S_OK; for (;;) { if (StreamIndex < 0) return S_OK; if ((unsigned)StreamIndex >= Streams.Size()) return S_OK; const CVols::CSubStreamInfo &s = Streams[(unsigned)StreamIndex]; if (!s.Stream) return S_FALSE; if (NeedSeek) { RINOK(s.SeekToStart()); NeedSeek = false; } UInt32 realProcessedSize = 0; HRESULT res = s.Stream->Read(data, size, &realProcessedSize); if (processedSize) *processedSize = realProcessedSize; if (res != S_OK) return res; if (realProcessedSize != 0) return res; StreamIndex++; NeedSeek = true; } } STDMETHODIMP CVolStream::Read(void *data, UInt32 size, UInt32 *processedSize) { return Vols->Read(data, size, processedSize); } #define COPY_ECD_ITEM_16(n) if (!isZip64 || !ZIP64_IS_16_MAX(ecd. n)) cdInfo. n = ecd. n; #define COPY_ECD_ITEM_32(n) if (!isZip64 || !ZIP64_IS_32_MAX(ecd. n)) cdInfo. n = ecd. n; HRESULT CInArchive::ReadHeaders(CObjectVector &items) { if (Buffer.Size() < kSeqBufferSize) { InitBuf(); Buffer.AllocAtLeast(kSeqBufferSize); if (!Buffer.IsAllocated()) return E_OUTOFMEMORY; } _inBufMode = false; HRESULT res = S_OK; bool localsWereRead = false; /* we try to open archive with the following modes: 1) CD-MODE : fast mode : we read backward ECD and CD, compare CD items with first Local item. 2) LOCALS-CD-MODE : slow mode, if CD-MODE fails : we sequentially read all Locals and then CD. Then we read sequentially ECD64, Locator, ECD again at the end. - in LOCALS-CD-MODE we use use the following variables (with real cd properties) to set Base archive offset and check real cd properties with values from ECD/ECD64. */ UInt64 cdSize = 0; UInt64 cdRelatOffset = 0; UInt32 cdDisk = 0; UInt64 cdAbsOffset = 0; // absolute cd offset, for LOCALS-CD-MODE only. if (!MarkerIsFound || !MarkerIsSafe) { IsArc = true; res = ReadCd(items, cdDisk, cdRelatOffset, cdSize); if (res == S_OK) ReadSignature(); else if (res != S_FALSE) return res; } else { // _signature must be kLocalFileHeader or kEcd or kEcd64 SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2 + 4); CanStartNewVol = false; if (_signature == NSignature::kEcd64) { // UInt64 ecd64Offset = GetVirtStreamPos() - 4; IsZip64 = true; { const UInt64 recordSize = ReadUInt64(); if (recordSize < kEcd64_MainSize) return S_FALSE; if (recordSize >= ((UInt64)1 << 62)) return S_FALSE; { const unsigned kBufSize = kEcd64_MainSize; Byte buf[kBufSize]; SafeRead(buf, kBufSize); CCdInfo cdInfo; cdInfo.ParseEcd64e(buf); if (!cdInfo.IsEmptyArc()) return S_FALSE; } RINOK(Skip64(recordSize - kEcd64_MainSize, 0)); } ReadSignature(); if (_signature != NSignature::kEcd64Locator) return S_FALSE; { const unsigned kBufSize = 16; Byte buf[kBufSize]; SafeRead(buf, kBufSize); CLocator locator; locator.Parse(buf); if (!locator.IsEmptyArc()) return S_FALSE; } ReadSignature(); if (_signature != NSignature::kEcd) return S_FALSE; } if (_signature == NSignature::kEcd) { // It must be empty archive or backware archive // we don't support backware archive still const unsigned kBufSize = kEcdSize - 4; Byte buf[kBufSize]; SafeRead(buf, kBufSize); CEcd ecd; ecd.Parse(buf); // if (ecd.cdSize != 0) // Do we need also to support the case where empty zip archive with PK00 uses cdOffset = 4 ?? if (!ecd.IsEmptyArc()) return S_FALSE; ArcInfo.Base = (Int64)ArcInfo.MarkerPos; IsArc = true; // check it: we need more tests? RINOK(SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2)); ReadSignature(); } else { CItemEx firstItem; try { try { if (!ReadLocalItem(firstItem)) return S_FALSE; } catch(CUnexpectEnd &) { return S_FALSE; } IsArc = true; res = ReadCd(items, cdDisk, cdRelatOffset, cdSize); if (res == S_OK) ReadSignature(); } catch(CUnexpectEnd &) { res = S_FALSE; } if (res != S_FALSE && res != S_OK) return res; if (res == S_OK && items.Size() == 0) res = S_FALSE; if (res == S_OK) { // we can't read local items here to keep _inBufMode state if ((Int64)ArcInfo.MarkerPos2 < ArcInfo.Base) res = S_FALSE; else { firstItem.LocalHeaderPos = (UInt64)((Int64)ArcInfo.MarkerPos2 - ArcInfo.Base); int index = -1; UInt32 min_Disk = (UInt32)(Int32)-1; UInt64 min_LocalHeaderPos = (UInt64)(Int64)-1; if (!IsCdUnsorted) index = FindItem(items, firstItem); else { FOR_VECTOR (i, items) { const CItemEx &cdItem = items[i]; if (cdItem.Disk == firstItem.Disk && (cdItem.LocalHeaderPos == firstItem.LocalHeaderPos)) index = (int)i; if (i == 0 || cdItem.Disk < min_Disk || (cdItem.Disk == min_Disk && cdItem.LocalHeaderPos < min_LocalHeaderPos)) { min_Disk = cdItem.Disk; min_LocalHeaderPos = cdItem.LocalHeaderPos; } } } if (index == -1) res = S_FALSE; else if (!AreItemsEqual(firstItem, items[(unsigned)index])) res = S_FALSE; else { ArcInfo.CdWasRead = true; if (IsCdUnsorted) ArcInfo.FirstItemRelatOffset = min_LocalHeaderPos; else ArcInfo.FirstItemRelatOffset = items[0].LocalHeaderPos; // ArcInfo.FirstItemRelatOffset = _startLocalFromCd_Offset; } } } } } CObjectVector cdItems; bool needSetBase = false; // we set needSetBase only for LOCALS_CD_MODE unsigned numCdItems = items.Size(); #ifdef ZIP_SELF_CHECK res = S_FALSE; // if uncommented, it uses additional LOCALS-CD-MODE mode to check the code #endif if (res != S_OK) { // ---------- LOCALS-CD-MODE ---------- // CD doesn't match firstItem, // so we clear items and read Locals and CD. items.Clear(); localsWereRead = true; HeadersError = false; HeadersWarning = false; ExtraMinorError = false; // we can use any mode: with buffer and without buffer // without buffer : skips packed data : fast for big files : slow for small files // with buffer : reads packed data : slow for big files : fast for small files _inBufMode = false; // _inBufMode = true; InitBuf(); ArcInfo.Base = 0; if (!MarkerIsFound) { if (!IsMultiVol) return S_FALSE; if (Vols.StartParsingVol != 0) return S_FALSE; // if (StartParsingVol == 0) and we didn't find marker, we use default zero marker. // so we suppose that there is no sfx stub RINOK(SeekToVol(0, ArcInfo.MarkerPos2)); } else { if (ArcInfo.MarkerPos != 0) { /* If multi-vol or there is (No)Span-marker at start of stream, we set (Base) as 0. In another caes: (No)Span-marker is supposed as false positive. So we set (Base) as main marker (MarkerPos2). The (Base) can be corrected later after ECD reading. But sfx volume with stub and (No)Span-marker in (!IsMultiVol) mode will have incorrect (Base) here. */ ArcInfo.Base = (Int64)ArcInfo.MarkerPos2; } RINOK(SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2)); } _cnt = 0; ReadSignature(); LocalsWereRead = true; RINOK(ReadLocals(items)); if (_signature != NSignature::kCentralFileHeader) { // GetVirtStreamPos() - 4 if (items.IsEmpty()) return S_FALSE; bool isError = true; const UInt32 apkSize = _signature; const unsigned kApkFooterSize = 16 + 8; if (apkSize >= kApkFooterSize && apkSize <= (1 << 20)) { if (ReadUInt32() == 0) { CByteBuffer apk; apk.Alloc(apkSize); SafeRead(apk, apkSize); ReadSignature(); const Byte *footer = apk + apkSize - kApkFooterSize; if (_signature == NSignature::kCentralFileHeader) if (GetUi64(footer) == apkSize) if (memcmp(footer + 8, "APK Sig Block 42", 16) == 0) { isError = false; IsApk = true; } } } if (isError) { NoCentralDir = true; HeadersError = true; return S_OK; } } _inBufMode = true; cdAbsOffset = GetVirtStreamPos() - 4; cdDisk = (UInt32)Vols.StreamIndex; #ifdef ZIP_SELF_CHECK if (!IsMultiVol && _cnt != GetVirtStreamPos() - ArcInfo.MarkerPos2) return E_FAIL; #endif const UInt64 processedCnt_start = _cnt; for (;;) { CItemEx cdItem; RINOK(ReadCdItem(cdItem)); cdItems.Add(cdItem); if (Callback && (cdItems.Size() & 0xFFF) == 0) { const UInt64 numFiles = items.Size(); const UInt64 numBytes = _cnt; RINOK(Callback->SetCompleted(&numFiles, &numBytes)); } ReadSignature(); if (_signature != NSignature::kCentralFileHeader) break; } cdSize = _cnt - processedCnt_start; #ifdef ZIP_SELF_CHECK if (!IsMultiVol) { if (_cnt != GetVirtStreamPos() - ArcInfo.MarkerPos2) return E_FAIL; if (cdSize != (GetVirtStreamPos() - 4) - cdAbsOffset) return E_FAIL; } #endif needSetBase = true; numCdItems = cdItems.Size(); cdRelatOffset = (UInt64)((Int64)cdAbsOffset - ArcInfo.Base); if (!cdItems.IsEmpty()) { ArcInfo.CdWasRead = true; ArcInfo.FirstItemRelatOffset = cdItems[0].LocalHeaderPos; } } CCdInfo cdInfo; CLocator locator; bool isZip64 = false; const UInt64 ecd64AbsOffset = GetVirtStreamPos() - 4; int ecd64Disk = -1; if (_signature == NSignature::kEcd64) { ecd64Disk = Vols.StreamIndex; IsZip64 = isZip64 = true; { const UInt64 recordSize = ReadUInt64(); if (recordSize < kEcd64_MainSize || recordSize >= ((UInt64)1 << 62)) { HeadersError = true; return S_OK; } { const unsigned kBufSize = kEcd64_MainSize; Byte buf[kBufSize]; SafeRead(buf, kBufSize); cdInfo.ParseEcd64e(buf); } RINOK(Skip64(recordSize - kEcd64_MainSize, items.Size())); } ReadSignature(); if (_signature != NSignature::kEcd64Locator) { HeadersError = true; return S_OK; } { const unsigned kBufSize = 16; Byte buf[kBufSize]; SafeRead(buf, kBufSize); locator.Parse(buf); // we ignore the error, where some zip creators use (NumDisks == 0) // if (locator.NumDisks == 0) HeadersWarning = true; } ReadSignature(); } if (_signature != NSignature::kEcd) { HeadersError = true; return S_OK; } CanStartNewVol = false; // ---------- ECD ---------- CEcd ecd; { const unsigned kBufSize = kEcdSize - 4; Byte buf[kBufSize]; SafeRead(buf, kBufSize); ecd.Parse(buf); } COPY_ECD_ITEM_16(ThisDisk); COPY_ECD_ITEM_16(CdDisk); COPY_ECD_ITEM_16(NumEntries_in_ThisDisk); COPY_ECD_ITEM_16(NumEntries); COPY_ECD_ITEM_32(Size); COPY_ECD_ITEM_32(Offset); bool cdOK = true; if ((UInt32)cdInfo.Size != (UInt32)cdSize) { // return S_FALSE; cdOK = false; } if (isZip64) { if (cdInfo.NumEntries != numCdItems || cdInfo.Size != cdSize) { cdOK = false; } } if (IsMultiVol) { if (cdDisk != cdInfo.CdDisk) HeadersError = true; } else if (needSetBase && cdOK) { const UInt64 oldBase = (UInt64)ArcInfo.Base; // localsWereRead == true // ArcInfo.Base == ArcInfo.MarkerPos2 // cdRelatOffset == (cdAbsOffset - ArcInfo.Base) if (isZip64) { if (ecd64Disk == Vols.StartVolIndex) { const Int64 newBase = (Int64)ecd64AbsOffset - (Int64)locator.Ecd64Offset; if (newBase <= (Int64)ecd64AbsOffset) { if (!localsWereRead || newBase <= (Int64)ArcInfo.MarkerPos2) { ArcInfo.Base = newBase; cdRelatOffset = (UInt64)((Int64)cdAbsOffset - newBase); } else cdOK = false; } } } else if (numCdItems != 0) // we can't use ecd.Offset in empty archive? { if ((int)cdDisk == Vols.StartVolIndex) { const Int64 newBase = (Int64)cdAbsOffset - (Int64)cdInfo.Offset; if (newBase <= (Int64)cdAbsOffset) { if (!localsWereRead || newBase <= (Int64)ArcInfo.MarkerPos2) { // cd can be more accurate, when it points before Locals // so we change Base and cdRelatOffset ArcInfo.Base = newBase; cdRelatOffset = cdInfo.Offset; } else { // const UInt64 delta = ((UInt64)cdRelatOffset - cdInfo.Offset); const UInt64 delta = ((UInt64)(newBase - ArcInfo.Base)); if ((UInt32)delta == 0) { // we set Overflow32bit mode, only if there is (x<<32) offset // between real_CD_offset_from_MarkerPos and CD_Offset_in_ECD. // Base and cdRelatOffset unchanged Overflow32bit = true; } else cdOK = false; } } else cdOK = false; } } // cdRelatOffset = cdAbsOffset - ArcInfo.Base; if (localsWereRead) { const UInt64 delta = (UInt64)((Int64)oldBase - ArcInfo.Base); if (delta != 0) { FOR_VECTOR (i, items) items[i].LocalHeaderPos += delta; } } } if (!cdOK) HeadersError = true; EcdVolIndex = cdInfo.ThisDisk; if (!IsMultiVol) { if (EcdVolIndex == 0 && Vols.MissingZip && Vols.StartIsExe) { Vols.MissingName.Empty(); Vols.MissingZip = false; } if (localsWereRead) { if (EcdVolIndex != 0) { FOR_VECTOR (i, items) items[i].Disk = EcdVolIndex; } } UseDisk_in_SingleVol = true; } if (isZip64) { if ((cdInfo.ThisDisk == 0 && ecd64AbsOffset != (UInt64)(ArcInfo.Base + (Int64)locator.Ecd64Offset)) // || cdInfo.NumEntries_in_ThisDisk != numCdItems || cdInfo.NumEntries != numCdItems || cdInfo.Size != cdSize || (cdInfo.Offset != cdRelatOffset && !items.IsEmpty())) { HeadersError = true; return S_OK; } } if (cdOK && !cdItems.IsEmpty()) { // ---------- merge Central Directory Items ---------- CRecordVector items2; int nextLocalIndex = 0; LocalsCenterMerged = true; FOR_VECTOR (i, cdItems) { if (Callback) if ((i & 0x3FFF) == 0) { const UInt64 numFiles64 = items.Size() + items2.Size(); RINOK(Callback->SetCompleted(&numFiles64, &_cnt)); } const CItemEx &cdItem = cdItems[i]; int index = -1; if (nextLocalIndex != -1) { if ((unsigned)nextLocalIndex < items.Size()) { CItemEx &item = items[(unsigned)nextLocalIndex]; if (item.Disk == cdItem.Disk && (item.LocalHeaderPos == cdItem.LocalHeaderPos || (Overflow32bit && (UInt32)item.LocalHeaderPos == cdItem.LocalHeaderPos))) index = nextLocalIndex++; else nextLocalIndex = -1; } } if (index == -1) index = FindItem(items, cdItem); // index = -1; if (index == -1) { items2.Add(i); HeadersError = true; continue; } CItemEx &item = items[(unsigned)index]; if (item.Name != cdItem.Name // || item.Name.Len() != cdItem.Name.Len() || item.PackSize != cdItem.PackSize || item.Size != cdItem.Size // item.ExtractVersion != cdItem.ExtractVersion || !FlagsAreSame(item, cdItem) || item.Crc != cdItem.Crc) { HeadersError = true; continue; } // item.Name = cdItem.Name; item.MadeByVersion = cdItem.MadeByVersion; item.CentralExtra = cdItem.CentralExtra; item.InternalAttrib = cdItem.InternalAttrib; item.ExternalAttrib = cdItem.ExternalAttrib; item.Comment = cdItem.Comment; item.FromCentral = cdItem.FromCentral; } FOR_VECTOR (k, items2) items.Add(cdItems[items2[k]]); } if (ecd.NumEntries < ecd.NumEntries_in_ThisDisk) HeadersError = true; if (ecd.ThisDisk == 0) { // if (isZip64) { if (ecd.NumEntries != ecd.NumEntries_in_ThisDisk) HeadersError = true; } } if (isZip64) { if (cdInfo.NumEntries != items.Size() || (ecd.NumEntries != items.Size() && ecd.NumEntries != 0xFFFF)) HeadersError = true; } else { // old 7-zip could store 32-bit number of CD items to 16-bit field. // if (ecd.NumEntries != items.Size()) if (ecd.NumEntries > items.Size()) HeadersError = true; if (cdInfo.NumEntries != numCdItems) { if ((UInt16)cdInfo.NumEntries != (UInt16)numCdItems) HeadersError = true; else Cd_NumEntries_Overflow_16bit = true; } } ReadBuffer(ArcInfo.Comment, ecd.CommentSize); _inBufMode = false; // DisableBufMode(); // Buffer.Free(); /* we can't clear buf varibles. we need them to calculate PhySize of archive */ if ((UInt16)cdInfo.NumEntries != (UInt16)numCdItems || (UInt32)cdInfo.Size != (UInt32)cdSize || ((UInt32)cdInfo.Offset != (UInt32)cdRelatOffset && !items.IsEmpty())) { // return S_FALSE; HeadersError = true; } #ifdef ZIP_SELF_CHECK if (localsWereRead) { const UInt64 endPos = ArcInfo.MarkerPos2 + _cnt; if (endPos != (IsMultiVol ? Vols.TotalBytesSize : ArcInfo.FileEndPos)) { // there are some data after the end of archive or error in code; return E_FAIL; } } #endif // printf("\nOpen OK"); return S_OK; } HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchLimit, IArchiveOpenCallback *callback, CObjectVector &items) { items.Clear(); Close(); UInt64 startPos; RINOK(stream->Seek(0, STREAM_SEEK_CUR, &startPos)); RINOK(stream->Seek(0, STREAM_SEEK_END, &ArcInfo.FileEndPos)); _streamPos = ArcInfo.FileEndPos; StartStream = stream; Stream = stream; Callback = callback; DisableBufMode(); bool volWasRequested = false; if (callback && (startPos == 0 || !searchLimit || *searchLimit != 0)) { // we try to read volumes only if it's first call (offset == 0) or scan is allowed. volWasRequested = true; RINOK(ReadVols()); } if (IsMultiVol && Vols.StartParsingVol == 0 && (unsigned)Vols.StartParsingVol < Vols.Streams.Size()) { // only StartParsingVol = 0 is safe search. RINOK(SeekToVol(0, 0)); // if (Stream) { // UInt64 limit = 1 << 22; // for sfx UInt64 limit = 0; // without sfx HRESULT res = FindMarker(&limit); if (res == S_OK) { MarkerIsFound = true; MarkerIsSafe = true; } else if (res != S_FALSE) return res; } } else { // printf("\nOpen offset = %u\n", (unsigned)startPos); if (IsMultiVol && (unsigned)Vols.StartParsingVol < Vols.Streams.Size() && Vols.Streams[(unsigned)Vols.StartParsingVol].Stream) { RINOK(SeekToVol(Vols.StartParsingVol, Vols.StreamIndex == Vols.StartVolIndex ? startPos : 0)); } else { RINOK(SeekToVol(-1, startPos)); } // UInt64 limit = 1 << 22; // HRESULT res = FindMarker(&limit); HRESULT res = FindMarker(searchLimit); // const UInt64 curPos = GetVirtStreamPos(); const UInt64 curPos = ArcInfo.MarkerPos2 + 4; if (res == S_OK) MarkerIsFound = true; else if (!IsMultiVol) { /* // if (startPos != 0), probably CD copuld be already tested with another call with (startPos == 0). // so we don't want to try to open CD again in that ase. if (startPos != 0) return res; // we can try to open CD, if there is no Marker and (startPos == 0). // is it OK to open such files as ZIP, or big number of false positive, when CD can be find in end of file ? */ return res; } if (ArcInfo.IsSpanMode && !volWasRequested) { RINOK(ReadVols()); if (IsMultiVol && MarkerIsFound && ArcInfo.MarkerVolIndex < 0) ArcInfo.MarkerVolIndex = Vols.StartVolIndex; } MarkerIsSafe = !IsMultiVol || (ArcInfo.MarkerVolIndex == 0 && ArcInfo.MarkerPos == 0) ; if (IsMultiVol) { if ((unsigned)Vols.StartVolIndex < Vols.Streams.Size()) { Stream = Vols.Streams[(unsigned)Vols.StartVolIndex].Stream; if (Stream) { RINOK(Seek_SavePos(curPos)); } else IsMultiVol = false; } else IsMultiVol = false; } if (!IsMultiVol) { if (Vols.StreamIndex != -1) { Stream = StartStream; Vols.StreamIndex = -1; InitBuf(); RINOK(Seek_SavePos(curPos)); } ArcInfo.MarkerVolIndex = -1; StreamRef = stream; Stream = stream; } } if (!IsMultiVol) Vols.ClearRefs(); { HRESULT res; try { res = ReadHeaders(items); } catch (const CSystemException &e) { res = e.ErrorCode; } catch (const CUnexpectEnd &) { if (items.IsEmpty()) return S_FALSE; UnexpectedEnd = true; res = S_OK; } catch (...) { DisableBufMode(); throw; } if (IsMultiVol) { ArcInfo.FinishPos = ArcInfo.FileEndPos; if ((unsigned)Vols.StreamIndex < Vols.Streams.Size()) if (GetVirtStreamPos() < Vols.Streams[(unsigned)Vols.StreamIndex].Size) ArcInfo.ThereIsTail = true; } else { ArcInfo.FinishPos = GetVirtStreamPos(); ArcInfo.ThereIsTail = (ArcInfo.FileEndPos > ArcInfo.FinishPos); } DisableBufMode(); IsArcOpen = true; if (!IsMultiVol) Vols.Streams.Clear(); return res; } } HRESULT CInArchive::GetItemStream(const CItemEx &item, bool seekPackData, CMyComPtr &stream) { stream.Release(); UInt64 pos = item.LocalHeaderPos; if (seekPackData) pos += item.LocalFullHeaderSize; if (!IsMultiVol) { if (UseDisk_in_SingleVol && item.Disk != EcdVolIndex) return S_OK; pos = (UInt64)((Int64)pos + ArcInfo.Base); RINOK(StreamRef->Seek((Int64)pos, STREAM_SEEK_SET, NULL)); stream = StreamRef; return S_OK; } if (item.Disk >= Vols.Streams.Size()) return S_OK; IInStream *str2 = Vols.Streams[item.Disk].Stream; if (!str2) return S_OK; RINOK(str2->Seek((Int64)pos, STREAM_SEEK_SET, NULL)); Vols.NeedSeek = false; Vols.StreamIndex = (int)item.Disk; CVolStream *volsStreamSpec = new CVolStream; volsStreamSpec->Vols = &Vols; stream = volsStreamSpec; return S_OK; } }}