diff options
Diffstat (limited to 'CPP/7zip/Archive/Zip/ZipIn.cpp')
-rw-r--r--[-rwxr-xr-x] | CPP/7zip/Archive/Zip/ZipIn.cpp | 1323 |
1 files changed, 867 insertions, 456 deletions
diff --git a/CPP/7zip/Archive/Zip/ZipIn.cpp b/CPP/7zip/Archive/Zip/ZipIn.cpp index e930488f..345fbf56 100755..100644 --- a/CPP/7zip/Archive/Zip/ZipIn.cpp +++ b/CPP/7zip/Archive/Zip/ZipIn.cpp @@ -2,13 +2,15 @@ #include "StdAfx.h" -#include "../../../../C/CpuArch.h" +// #include <stdio.h> -#include "Common/DynamicBuffer.h" +#include "../../../Common/DynamicBuffer.h" #include "../../Common/LimitedStreams.h" #include "../../Common/StreamUtils.h" +#include "../IArchive.h" + #include "ZipIn.h" #define Get16(p) GetUi16(p) @@ -17,100 +19,342 @@ namespace NArchive { namespace NZip { - + +struct CEcd +{ + UInt16 thisDiskNumber; + UInt16 startCDDiskNumber; + UInt16 numEntriesInCDOnThisDisk; + UInt16 numEntriesInCD; + UInt32 cdSize; + UInt32 cdStartOffset; + UInt16 commentSize; + + void Parse(const Byte *p); + + bool IsEmptyArc() + { + return thisDiskNumber == 0 && startCDDiskNumber == 0 && + numEntriesInCDOnThisDisk == 0 && numEntriesInCD == 0 && cdSize == 0 + && cdStartOffset == 0 // test it + ; + } +}; + +void CEcd::Parse(const Byte *p) +{ + thisDiskNumber = Get16(p); + startCDDiskNumber = Get16(p + 2); + numEntriesInCDOnThisDisk = Get16(p + 4); + numEntriesInCD = Get16(p + 6); + cdSize = Get32(p + 8); + cdStartOffset = Get32(p + 12); + commentSize = Get16(p + 16); +} + +struct CEcd64 +{ + UInt16 versionMade; + UInt16 versionNeedExtract; + UInt32 thisDiskNumber; + UInt32 startCDDiskNumber; + UInt64 numEntriesInCDOnThisDisk; + UInt64 numEntriesInCD; + UInt64 cdSize; + UInt64 cdStartOffset; + + void Parse(const Byte *p); + CEcd64() { memset(this, 0, sizeof(*this)); } +}; + +void CEcd64::Parse(const Byte *p) +{ + versionMade = Get16(p); + versionNeedExtract = Get16(p + 2); + thisDiskNumber = Get32(p + 4); + startCDDiskNumber = Get32(p + 8); + numEntriesInCDOnThisDisk = Get64(p + 12); + numEntriesInCD = Get64(p + 20); + cdSize = Get64(p + 28); + cdStartOffset = Get64(p + 36); +} + HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { _inBufMode = false; Close(); - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &m_StreamStartPosition)); - m_Position = m_StreamStartPosition; + RINOK(stream->Seek(0, STREAM_SEEK_CUR, &m_Position)); + RINOK(stream->Seek(0, STREAM_SEEK_END, &ArcInfo.FileEndPos)); + RINOK(stream->Seek(m_Position, STREAM_SEEK_SET, NULL)); + + // printf("\nOpen offset = %d", (int)m_Position); RINOK(FindAndReadMarker(stream, searchHeaderSizeLimit)); RINOK(stream->Seek(m_Position, STREAM_SEEK_SET, NULL)); - m_Stream = stream; + Stream = stream; return S_OK; } void CInArchive::Close() { - _inBuffer.ReleaseStream(); - m_Stream.Release(); + IsArc = false; + HeadersError = false; + HeadersWarning = false; + ExtraMinorError = false; + UnexpectedEnd = false; + NoCentralDir = false; + IsZip64 = false; + Stream.Release(); } HRESULT CInArchive::Seek(UInt64 offset) { - return m_Stream->Seek(offset, STREAM_SEEK_SET, NULL); + return Stream->Seek(offset, STREAM_SEEK_SET, NULL); } -////////////////////////////////////// -// Markers +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; +} -static inline bool TestMarkerCandidate(const Byte *p, UInt32 &value) +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 value = Get32(p); + + if (value == NSignature::kNoSpan) + { + p += 4; + size -= 4; + } + value = Get32(p); - return - (value == NSignature::kLocalFileHeader) || - (value == NSignature::kEndOfCentralDir); + + if (value == 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 (value != 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); + unsigned nameSize = Get16(p + 22); + unsigned extraSize = Get16(p + 24); + UInt32 extraOffset = kLocalHeaderSize + (UInt32)nameSize; + 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) + 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) + return k_IsArc_Res_NO; + if (dataSize > size) + return k_IsArc_Res_NEED_MORE; + size -= dataSize; + extraSize -= dataSize; + p += dataSize; + } + } + + return k_IsArc_Res_YES; } -static const UInt32 kNumMarkerAddtionalBytes = 2; -static inline bool TestMarkerCandidate2(const Byte *p, UInt32 &value) +static UInt32 IsArc_Zip_2(const Byte *p, size_t size, bool isFinal) { - value = Get32(p); - if (value == NSignature::kEndOfCentralDir) - return (Get16(p + 4) == 0); - return (value == NSignature::kLocalFileHeader && p[4] < 128); + UInt32 res = IsArc_Zip(p, size); + if (res == k_IsArc_Res_NEED_MORE && isFinal) + return k_IsArc_Res_NO; + return res; } -HRESULT CInArchive::FindAndReadMarker(IInStream *stream, const UInt64 *searchHeaderSizeLimit) +HRESULT CInArchive::FindAndReadMarker(IInStream *stream, const UInt64 *searchLimit) { ArcInfo.Clear(); - m_Position = m_StreamStartPosition; + ArcInfo.MarkerPos = m_Position; + ArcInfo.MarkerPos2 = m_Position; - Byte marker[NSignature::kMarkerSize]; - RINOK(ReadStream_FALSE(stream, marker, NSignature::kMarkerSize)); - m_Position += NSignature::kMarkerSize; - if (TestMarkerCandidate(marker, m_Signature)) - return S_OK; + if (searchLimit && *searchLimit == 0) + { + const unsigned kStartBufSize = kMarkerSize; + Byte startBuf[kStartBufSize]; + size_t processed = kStartBufSize; + RINOK(ReadStream(stream, startBuf, &processed)); + m_Position += processed; + if (processed < kMarkerSize) + return S_FALSE; + m_Signature = Get32(startBuf); + if (m_Signature != NSignature::kEcd && + m_Signature != NSignature::kLocalFileHeader) + { + if (m_Signature != NSignature::kNoSpan) + return S_FALSE; + size_t processed = kStartBufSize; + RINOK(ReadStream(stream, startBuf, &processed)); + m_Position += processed; + if (processed < kMarkerSize) + return S_FALSE; + m_Signature = Get32(startBuf); + if (m_Signature != NSignature::kEcd && + m_Signature != NSignature::kLocalFileHeader) + return S_FALSE; + ArcInfo.MarkerPos2 += 4; + } + + // we use weak test in case of *searchLimit == 0) + // since error will be detected later in Open function + // m_Position = ArcInfo.MarkerPos2 + 4; + return S_OK; // maybe we need to search backward. + } + + const size_t kBufSize = (size_t)1 << 18; // must be larger than kCheckSize + const size_t kCheckSize = (size_t)1 << 16; // must be smaller than kBufSize + CByteArr buffer(kBufSize); + + size_t numBytesInBuffer = 0; + UInt64 curScanPos = 0; - CByteDynamicBuffer dynamicBuffer; - const UInt32 kSearchMarkerBufferSize = 0x10000; - dynamicBuffer.EnsureCapacity(kSearchMarkerBufferSize); - Byte *buffer = dynamicBuffer; - UInt32 numBytesPrev = NSignature::kMarkerSize - 1; - memcpy(buffer, marker + 1, numBytesPrev); - UInt64 curTestPos = m_StreamStartPosition + 1; for (;;) { - if (searchHeaderSizeLimit != NULL) - if (curTestPos - m_StreamStartPosition > *searchHeaderSizeLimit) - break; - size_t numReadBytes = kSearchMarkerBufferSize - numBytesPrev; - RINOK(ReadStream(stream, buffer + numBytesPrev, &numReadBytes)); + size_t numReadBytes = kBufSize - numBytesInBuffer; + RINOK(ReadStream(stream, buffer + numBytesInBuffer, &numReadBytes)); m_Position += numReadBytes; - UInt32 numBytesInBuffer = numBytesPrev + (UInt32)numReadBytes; - const UInt32 kMarker2Size = NSignature::kMarkerSize + kNumMarkerAddtionalBytes; - if (numBytesInBuffer < kMarker2Size) + numBytesInBuffer += numReadBytes; + bool isFinished = (numBytesInBuffer != kBufSize); + + size_t limit = (isFinished ? numBytesInBuffer : numBytesInBuffer - kCheckSize); + + if (searchLimit && curScanPos + limit > *searchLimit) + limit = (size_t)(*searchLimit - curScanPos + 1); + + if (limit < 1) break; - UInt32 numTests = numBytesInBuffer - kMarker2Size + 1; - for (UInt32 pos = 0; pos < numTests; pos++) + + const Byte *buf = buffer; + for (size_t pos = 0; pos < limit; pos++) { - if (buffer[pos] != 0x50) + if (buf[pos] != 0x50) + continue; + if (buf[pos + 1] != 0x4B) continue; - if (TestMarkerCandidate2(buffer + pos, m_Signature)) + size_t rem = numBytesInBuffer - pos; + UInt32 res = IsArc_Zip_2(buf + pos, rem, isFinished); + if (res != k_IsArc_Res_NO) { - curTestPos += pos; - ArcInfo.StartPosition = curTestPos; - m_Position = curTestPos + NSignature::kMarkerSize; + if (rem < kMarkerSize) + return S_FALSE; + m_Signature = Get32(buf + pos); + ArcInfo.MarkerPos += curScanPos + pos; + ArcInfo.MarkerPos2 = ArcInfo.MarkerPos; + if (m_Signature == NSignature::kNoSpan) + { + m_Signature = Get32(buf + pos + 4); + ArcInfo.MarkerPos2 += 4; + } + m_Position = ArcInfo.MarkerPos2 + kMarkerSize; return S_OK; } } - curTestPos += numTests; - numBytesPrev = numBytesInBuffer - numTests; - memmove(buffer, buffer + numTests, numBytesPrev); + + if (isFinished) + break; + + curScanPos += limit; + numBytesInBuffer -= limit; + memmove(buffer, buffer + limit, numBytesInBuffer); } + return S_FALSE; } +HRESULT CInArchive::IncreaseRealPosition(UInt64 addValue) +{ + return Stream->Seek(addValue, STREAM_SEEK_CUR, &m_Position); +} + +class CUnexpectEnd {}; + HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 *processedSize) { size_t realProcessedSize = size; @@ -121,42 +365,35 @@ HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 *processedSize) catch (const CInBufferException &e) { return e.ErrorCode; } } else - result = ReadStream(m_Stream, data, &realProcessedSize); - if (processedSize != NULL) + result = ReadStream(Stream, data, &realProcessedSize); + if (processedSize) *processedSize = (UInt32)realProcessedSize; m_Position += realProcessedSize; return result; } -void CInArchive::Skip(UInt64 num) -{ - for (UInt64 i = 0; i < num; i++) - ReadByte(); -} - -void CInArchive::IncreaseRealPosition(UInt64 addValue) -{ - if (m_Stream->Seek(addValue, STREAM_SEEK_CUR, &m_Position) != S_OK) - throw CInArchiveException(CInArchiveException::kSeekStreamError); -} - -bool CInArchive::ReadBytesAndTestSize(void *data, UInt32 size) -{ - UInt32 realProcessedSize; - if (ReadBytes(data, size, &realProcessedSize) != S_OK) - throw CInArchiveException(CInArchiveException::kReadStreamError); - return (realProcessedSize == size); -} - -void CInArchive::SafeReadBytes(void *data, UInt32 size) +void CInArchive::SafeReadBytes(void *data, unsigned size) { - if (!ReadBytesAndTestSize(data, size)) - throw CInArchiveException(CInArchiveException::kUnexpectedEndOfArchive); + size_t processed = size; + if (_inBufMode) + { + processed = _inBuffer.ReadBytes((Byte *)data, size); + m_Position += processed; + } + else + { + HRESULT result = ReadStream(Stream, data, &processed); + m_Position += processed; + if (result != S_OK) + throw CSystemException(result); + } + if (processed != size) + throw CUnexpectEnd(); } -void CInArchive::ReadBuffer(CByteBuffer &buffer, UInt32 size) +void CInArchive::ReadBuffer(CByteBuffer &buffer, unsigned size) { - buffer.SetCapacity(size); + buffer.Alloc(size); if (size > 0) SafeReadBytes(buffer, size); } @@ -168,65 +405,73 @@ Byte CInArchive::ReadByte() return b; } -UInt16 CInArchive::ReadUInt16() -{ - Byte buf[2]; - SafeReadBytes(buf, 2); - return Get16(buf); -} +UInt16 CInArchive::ReadUInt16() { Byte buf[2]; SafeReadBytes(buf, 2); return Get16(buf); } +UInt32 CInArchive::ReadUInt32() { Byte buf[4]; SafeReadBytes(buf, 4); return Get32(buf); } +UInt64 CInArchive::ReadUInt64() { Byte buf[8]; SafeReadBytes(buf, 8); return Get64(buf); } -UInt32 CInArchive::ReadUInt32() +void CInArchive::Skip(unsigned num) { - Byte buf[4]; - SafeReadBytes(buf, 4); - return Get32(buf); + if (_inBufMode) + { + size_t skip = _inBuffer.Skip(num); + m_Position += skip; + if (skip != num) + throw CUnexpectEnd(); + } + else + { + for (unsigned i = 0; i < num; i++) + ReadByte(); + } } -UInt64 CInArchive::ReadUInt64() +void CInArchive::Skip64(UInt64 num) { - Byte buf[8]; - SafeReadBytes(buf, 8); - return Get64(buf); + for (UInt64 i = 0; i < num; i++) + ReadByte(); } -bool CInArchive::ReadUInt32(UInt32 &value) -{ - Byte buf[4]; - if (!ReadBytesAndTestSize(buf, 4)) - return false; - value = Get32(buf); - return true; -} -void CInArchive::ReadFileName(UInt32 nameSize, AString &dest) +void CInArchive::ReadFileName(unsigned size, AString &s) { - if (nameSize == 0) - dest.Empty(); - char *p = dest.GetBuffer((int)nameSize); - SafeReadBytes(p, nameSize); - p[nameSize] = 0; - dest.ReleaseBuffer(); + if (size == 0) + { + s.Empty(); + return; + } + char *p = s.GetBuffer(size); + SafeReadBytes(p, size); + p[size] = 0; + s.ReleaseBuffer(); } -void CInArchive::ReadExtra(UInt32 extraSize, CExtraBlock &extraBlock, +bool CInArchive::ReadExtra(unsigned extraSize, CExtraBlock &extraBlock, UInt64 &unpackSize, UInt64 &packSize, UInt64 &localHeaderOffset, UInt32 &diskStartNumber) { extraBlock.Clear(); UInt32 remain = extraSize; - while(remain >= 4) + while (remain >= 4) { CExtraSubBlock subBlock; subBlock.ID = ReadUInt16(); - UInt32 dataSize = ReadUInt16(); + unsigned dataSize = ReadUInt16(); remain -= 4; if (dataSize > remain) // it's bug - dataSize = remain; + { + HeadersWarning = true; + Skip(remain); + return false; + } if (subBlock.ID == NFileHeader::NExtraID::kZip64) { if (unpackSize == 0xFFFFFFFF) { if (dataSize < 8) - break; + { + HeadersWarning = true; + Skip(remain); + return false; + } unpackSize = ReadUInt64(); remain -= 8; dataSize -= 8; @@ -255,8 +500,7 @@ void CInArchive::ReadExtra(UInt32 extraSize, CExtraBlock &extraBlock, remain -= 4; dataSize -= 4; } - for (UInt32 i = 0; i < dataSize; i++) - ReadByte(); + Skip(dataSize); } else { @@ -265,88 +509,125 @@ void CInArchive::ReadExtra(UInt32 extraSize, CExtraBlock &extraBlock, } remain -= dataSize; } + if (remain != 0) + { + ExtraMinorError = 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(remain); + return true; } -HRESULT CInArchive::ReadLocalItem(CItemEx &item) +bool CInArchive::ReadLocalItem(CItemEx &item) { - const int kBufSize = 26; - Byte p[kBufSize]; - SafeReadBytes(p, kBufSize); + const unsigned kPureHeaderSize = kLocalHeaderSize - 4; + Byte p[kPureHeaderSize]; + SafeReadBytes(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]; item.Flags = Get16(p + 2); - item.CompressionMethod = Get16(p + 4); + item.Method = Get16(p + 4); item.Time = Get32(p + 6); - item.FileCRC = Get32(p + 10); + item.Crc = Get32(p + 10); item.PackSize = Get32(p + 14); - item.UnPackSize = Get32(p + 18); - UInt32 fileNameSize = Get16(p + 22); - item.LocalExtraSize = Get16(p + 24); - ReadFileName(fileNameSize, item.Name); - item.FileHeaderWithNameSize = 4 + NFileHeader::kLocalBlockSize + fileNameSize; - if (item.LocalExtraSize > 0) + item.Size = Get32(p + 18); + unsigned nameSize = Get16(p + 22); + unsigned extraSize = Get16(p + 24); + ReadFileName(nameSize, item.Name); + item.LocalFullHeaderSize = kLocalHeaderSize + (UInt32)nameSize + extraSize; + + /* + if (item.IsDir()) + item.Size = 0; // check It + */ + + if (extraSize > 0) { UInt64 localHeaderOffset = 0; UInt32 diskStartNumber = 0; - ReadExtra(item.LocalExtraSize, item.LocalExtra, item.UnPackSize, item.PackSize, - localHeaderOffset, diskStartNumber); + if (!ReadExtra(extraSize, item.LocalExtra, item.Size, item.PackSize, + localHeaderOffset, diskStartNumber)) + return false; } - /* - if (item.IsDir()) - item.UnPackSize = 0; // check It - */ - return S_OK; + if (!CheckDosTime(item.Time)) + { + HeadersWarning = true; + // return false; + } + if (item.Name.Len() != nameSize) + return false; + return item.LocalFullHeaderSize <= ((UInt32)1 << 16); } -static bool FlagsAreSame(CItem &i1, CItem &i2) +static bool FlagsAreSame(const CItem &i1, const CItem &i2) { - if (i1.CompressionMethod != i2.CompressionMethod) + if (i1.Method != i2.Method) return false; - // i1.Time - if (i1.Flags == i2.Flags) return true; UInt32 mask = 0xFFFF; - switch(i1.CompressionMethod) + switch(i1.Method) { case NFileHeader::NCompressionMethod::kDeflated: mask = 0x7FF9; break; default: - if (i1.CompressionMethod <= NFileHeader::NCompressionMethod::kImploded) + if (i1.Method <= NFileHeader::NCompressionMethod::kImploded) mask = 0x7FFF; } return ((i1.Flags & mask) == (i2.Flags & mask)); } +static bool AreItemsEqual(const CItemEx &localItem, const CItemEx &cdItem) +{ + if (!FlagsAreSame(cdItem, localItem)) + return false; + if (!localItem.HasDescriptor()) + { + if (cdItem.Crc != localItem.Crc || + cdItem.PackSize != localItem.PackSize || + cdItem.Size != localItem.Size) + 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) + return false; + return true; +} + HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item) { if (item.FromLocal) return S_OK; try { - RINOK(Seek(ArcInfo.Base + item.LocalHeaderPosition)); + UInt64 offset = ArcInfo.Base + item.LocalHeaderPos; + if (ArcInfo.Base < 0 && (Int64)offset < 0) + return S_FALSE; + RINOK(Seek(offset)); CItemEx localItem; if (ReadUInt32() != NSignature::kLocalFileHeader) return S_FALSE; - RINOK(ReadLocalItem(localItem)); - if (!FlagsAreSame(item, localItem)) + ReadLocalItem(localItem); + if (!AreItemsEqual(localItem, item)) return S_FALSE; - - if ((!localItem.HasDescriptor() && - ( - item.FileCRC != localItem.FileCRC || - item.PackSize != localItem.PackSize || - item.UnPackSize != localItem.UnPackSize - ) - ) || - item.Name.Length() != localItem.Name.Length() - ) - return S_FALSE; - item.FileHeaderWithNameSize = localItem.FileHeaderWithNameSize; - item.LocalExtraSize = localItem.LocalExtraSize; + item.LocalFullHeaderSize = localItem.LocalFullHeaderSize; item.LocalExtra = localItem.LocalExtra; item.FromLocal = true; } @@ -356,53 +637,45 @@ HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item) HRESULT CInArchive::ReadLocalItemDescriptor(CItemEx &item) { - if (item.HasDescriptor()) + const unsigned kBufSize = (1 << 12); + Byte buf[kBufSize]; + + UInt32 numBytesInBuffer = 0; + UInt32 packedSize = 0; + + for (;;) { - const int kBufferSize = (1 << 12); - Byte buffer[kBufferSize]; - - UInt32 numBytesInBuffer = 0; - UInt32 packedSize = 0; - - bool descriptorWasFound = false; - for (;;) + UInt32 processedSize; + RINOK(ReadBytes(buf + numBytesInBuffer, kBufSize - numBytesInBuffer, &processedSize)); + numBytesInBuffer += processedSize; + if (numBytesInBuffer < kDataDescriptorSize) + return S_FALSE; + UInt32 i; + for (i = 0; i <= numBytesInBuffer - kDataDescriptorSize; i++) { - UInt32 processedSize; - RINOK(ReadBytes(buffer + numBytesInBuffer, kBufferSize - numBytesInBuffer, &processedSize)); - numBytesInBuffer += processedSize; - if (numBytesInBuffer < NFileHeader::kDataDescriptorSize) - return S_FALSE; - UInt32 i; - for (i = 0; i <= numBytesInBuffer - NFileHeader::kDataDescriptorSize; i++) + // descriptor signature field is Info-ZIP's extension to pkware Zip specification. + // New ZIP specification also allows descriptorSignature. + if (buf[i] != 0x50) + continue; + // !!!! It must be fixed for Zip64 archives + if (Get32(buf + i) == NSignature::kDataDescriptor) { - // descriptorSignature field is Info-ZIP's extension - // to Zip specification. - UInt32 descriptorSignature = Get32(buffer + i); - - // !!!! It must be fixed for Zip64 archives - UInt32 descriptorPackSize = Get32(buffer + i + 8); - if (descriptorSignature== NSignature::kDataDescriptor && descriptorPackSize == packedSize + i) + UInt32 descriptorPackSize = Get32(buf + i + 8); + if (descriptorPackSize == packedSize + i) { - descriptorWasFound = true; - item.FileCRC = Get32(buffer + i + 4); + item.Crc = Get32(buf + i + 4); item.PackSize = descriptorPackSize; - item.UnPackSize = Get32(buffer + i + 12); - IncreaseRealPosition(Int64(Int32(0 - (numBytesInBuffer - i - NFileHeader::kDataDescriptorSize)))); - break; + item.Size = Get32(buf + i + 12); + return IncreaseRealPosition(Int64(Int32(0 - (numBytesInBuffer - i - kDataDescriptorSize)))); } } - if (descriptorWasFound) - break; - packedSize += i; - int j; - for (j = 0; i < numBytesInBuffer; i++, j++) - buffer[j] = buffer[i]; - numBytesInBuffer = j; } + packedSize += i; + unsigned j; + for (j = 0; i < numBytesInBuffer; i++, j++) + buf[j] = buf[i]; + numBytesInBuffer = j; } - else - IncreaseRealPosition(item.PackSize); - return S_OK; } HRESULT CInArchive::ReadLocalItemAfterCdItemFull(CItemEx &item) @@ -433,7 +706,7 @@ HRESULT CInArchive::ReadLocalItemAfterCdItemFull(CItemEx &item) unpackSize = ReadUInt32(); } - if (crc != item.FileCRC || item.PackSize != packSize || item.UnPackSize != unpackSize) + if (crc != item.Crc || item.PackSize != packSize || item.Size != unpackSize) return S_FALSE; } } @@ -444,106 +717,161 @@ HRESULT CInArchive::ReadLocalItemAfterCdItemFull(CItemEx &item) HRESULT CInArchive::ReadCdItem(CItemEx &item) { item.FromCentral = true; - const int kBufSize = 42; - Byte p[kBufSize]; - SafeReadBytes(p, kBufSize); + Byte p[kCentralHeaderSize - 4]; + SafeReadBytes(p, kCentralHeaderSize - 4); + item.MadeByVersion.Version = p[0]; item.MadeByVersion.HostOS = p[1]; item.ExtractVersion.Version = p[2]; item.ExtractVersion.HostOS = p[3]; item.Flags = Get16(p + 4); - item.CompressionMethod = Get16(p + 6); + item.Method = Get16(p + 6); item.Time = Get32(p + 8); - item.FileCRC = Get32(p + 12); + item.Crc = Get32(p + 12); item.PackSize = Get32(p + 16); - item.UnPackSize = Get32(p + 20); - UInt16 headerNameSize = Get16(p + 24); - UInt16 headerExtraSize = Get16(p + 26); - UInt16 headerCommentSize = Get16(p + 28); - UInt32 headerDiskNumberStart = Get16(p + 30); - item.InternalAttributes = Get16(p + 32); - item.ExternalAttributes = Get32(p + 34); - item.LocalHeaderPosition = Get32(p + 38); - ReadFileName(headerNameSize, item.Name); + item.Size = Get32(p + 20); + unsigned nameSize = Get16(p + 24); + UInt16 extraSize = Get16(p + 26); + UInt16 commentSize = Get16(p + 28); + UInt32 diskNumberStart = Get16(p + 30); + item.InternalAttrib = Get16(p + 32); + item.ExternalAttrib = Get32(p + 34); + item.LocalHeaderPos = Get32(p + 38); + ReadFileName(nameSize, item.Name); - if (headerExtraSize > 0) + if (extraSize > 0) { - ReadExtra(headerExtraSize, item.CentralExtra, item.UnPackSize, item.PackSize, - item.LocalHeaderPosition, headerDiskNumberStart); + ReadExtra(extraSize, item.CentralExtra, item.Size, item.PackSize, + item.LocalHeaderPos, diskNumberStart); } - if (headerDiskNumberStart != 0) - throw CInArchiveException(CInArchiveException::kMultiVolumeArchiveAreNotSupported); + if (diskNumberStart != 0) + return E_NOTIMPL; // May be these strings must be deleted /* if (item.IsDir()) - item.UnPackSize = 0; + item.Size = 0; */ - ReadBuffer(item.Comment, headerCommentSize); + ReadBuffer(item.Comment, commentSize); return S_OK; } +void CCdInfo::ParseEcd(const Byte *p) +{ + NumEntries = Get16(p + 10); + Size = Get32(p + 12); + Offset = Get32(p + 16); +} + +void CCdInfo::ParseEcd64(const Byte *p) +{ + NumEntries = Get64(p + 24); + Size = Get64(p + 40); + Offset = Get64(p + 48); +} + HRESULT CInArchive::TryEcd64(UInt64 offset, CCdInfo &cdInfo) { + if (offset >= ((UInt64)1 << 63)) + return S_FALSE; RINOK(Seek(offset)); - const UInt32 kEcd64Size = 56; - Byte buf[kEcd64Size]; - if (!ReadBytesAndTestSize(buf, kEcd64Size)) + Byte buf[kEcd64_FullSize]; + + RINOK(ReadStream_FALSE(Stream, buf, kEcd64_FullSize)); + + if (Get32(buf) != NSignature::kEcd64) return S_FALSE; - if (Get32(buf) != NSignature::kZip64EndOfCentralDir) + UInt64 mainSize = Get64(buf + 4); + if (mainSize < kEcd64_MainSize || mainSize > ((UInt64)1 << 32)) return S_FALSE; - // cdInfo.NumEntries = Get64(buf + 24); - cdInfo.Size = Get64(buf + 40); - cdInfo.Offset = Get64(buf + 48); + cdInfo.ParseEcd64(buf); return S_OK; } HRESULT CInArchive::FindCd(CCdInfo &cdInfo) { UInt64 endPosition; - RINOK(m_Stream->Seek(0, STREAM_SEEK_END, &endPosition)); - const UInt32 kBufSizeMax = (1 << 16) + kEcdSize + kZip64EcdLocatorSize; - CByteBuffer byteBuffer; - byteBuffer.SetCapacity(kBufSizeMax); - Byte *buf = byteBuffer; + RINOK(Stream->Seek(0, STREAM_SEEK_END, &endPosition)); + + const UInt32 kBufSizeMax = ((UInt32)1 << 16) + kEcdSize + kEcd64Locator_Size + kEcd64_FullSize; UInt32 bufSize = (endPosition < kBufSizeMax) ? (UInt32)endPosition : kBufSizeMax; if (bufSize < kEcdSize) return S_FALSE; + CByteArr byteBuffer(bufSize); + UInt64 startPosition = endPosition - bufSize; - RINOK(m_Stream->Seek(startPosition, STREAM_SEEK_SET, &m_Position)); + RINOK(Stream->Seek(startPosition, STREAM_SEEK_SET, &m_Position)); if (m_Position != startPosition) return S_FALSE; - if (!ReadBytesAndTestSize(buf, bufSize)) - return S_FALSE; - for (int i = (int)(bufSize - kEcdSize); i >= 0; i--) + + RINOK(ReadStream_FALSE(Stream, byteBuffer, bufSize)); + + const Byte *buf = byteBuffer; + for (UInt32 i = bufSize - kEcdSize;; i--) { - if (Get32(buf + i) == NSignature::kEndOfCentralDir) + if (buf[i] != 0x50) { - if (i >= kZip64EcdLocatorSize) + if (i == 0) return S_FALSE; + i--; + if (buf[i] != 0x50) { - const Byte *locator = buf + i - kZip64EcdLocatorSize; - if (Get32(locator) == NSignature::kZip64EndOfCentralDirLocator) + if (i == 0) return S_FALSE; + continue; + } + } + if (Get32(buf + i) == NSignature::kEcd) + { + if (i >= kEcd64_FullSize + kEcd64Locator_Size) + { + const Byte *locator = buf + i - kEcd64Locator_Size; + if (Get32(locator) == NSignature::kEcd64Locator && + Get32(locator + 4) == 0) // number of the disk with the start of the zip64 ECD { + // Most of the zip64 use fixed size Zip64 ECD + UInt64 ecd64Offset = Get64(locator + 8); - if (TryEcd64(ecd64Offset, cdInfo) == S_OK) - return S_OK; - if (TryEcd64(ArcInfo.StartPosition + ecd64Offset, cdInfo) == S_OK) + UInt64 absEcd64 = endPosition - bufSize + i - (kEcd64Locator_Size + kEcd64_FullSize); + { + const Byte *ecd64 = locator - kEcd64_FullSize; + if (Get32(ecd64) == NSignature::kEcd64 && + Get64(ecd64 + 4) == kEcd64_MainSize) + { + cdInfo.ParseEcd64(ecd64); + ArcInfo.Base = absEcd64 - ecd64Offset; + return S_OK; + } + } + + // some zip64 use variable size Zip64 ECD. + // we try to find it + if (absEcd64 != ecd64Offset) + { + if (TryEcd64(ecd64Offset, cdInfo) == S_OK) + { + ArcInfo.Base = 0; + return S_OK; + } + } + if (ArcInfo.MarkerPos != 0 && + ArcInfo.MarkerPos + ecd64Offset != absEcd64) { - ArcInfo.Base = ArcInfo.StartPosition; - return S_OK; + if (TryEcd64(ArcInfo.MarkerPos + ecd64Offset, cdInfo) == S_OK) + { + ArcInfo.Base = ArcInfo.MarkerPos; + return S_OK; + } } } } - if (Get32(buf + i + 4) == 0) + if (Get32(buf + i + 4) == 0) // ThisDiskNumber, StartCentralDirectoryDiskNumber; { - // cdInfo.NumEntries = GetUInt16(buf + i + 10); - cdInfo.Size = Get32(buf + i + 12); - cdInfo.Offset = Get32(buf + i + 16); - UInt64 curPos = endPosition - bufSize + i; + cdInfo.ParseEcd(buf + i); + UInt64 absEcdPos = endPosition - bufSize + i; UInt64 cdEnd = cdInfo.Size + cdInfo.Offset; - if (curPos != cdEnd) + ArcInfo.Base = 0; + if (absEcdPos != cdEnd) { /* if (cdInfo.Offset <= 16 && cdInfo.Size != 0) @@ -553,290 +881,301 @@ HRESULT CInArchive::FindCd(CCdInfo &cdInfo) } else */ - ArcInfo.Base = curPos - cdEnd; + ArcInfo.Base = absEcdPos - cdEnd; } return S_OK; } } + if (i == 0) + return S_FALSE; } - return S_FALSE; } + HRESULT CInArchive::TryReadCd(CObjectVector<CItemEx> &items, UInt64 cdOffset, UInt64 cdSize, CProgressVirt *progress) { items.Clear(); - RINOK(m_Stream->Seek(cdOffset, STREAM_SEEK_SET, &m_Position)); + RINOK(Stream->Seek(cdOffset, STREAM_SEEK_SET, &m_Position)); if (m_Position != cdOffset) return S_FALSE; - if (!_inBuffer.Create(1 << 15)) - return E_OUTOFMEMORY; - _inBuffer.SetStream(m_Stream); _inBuffer.Init(); _inBufMode = true; - while(m_Position - cdOffset < cdSize) + while (m_Position - cdOffset < cdSize) { if (ReadUInt32() != NSignature::kCentralFileHeader) return S_FALSE; CItemEx cdItem; RINOK(ReadCdItem(cdItem)); items.Add(cdItem); - if (progress && items.Size() % 1000 == 0) - RINOK(progress->SetCompleted(items.Size())); + if (progress && items.Size() % 1 == 0) + RINOK(progress->SetCompletedCD(items.Size())); } return (m_Position - cdOffset == cdSize) ? S_OK : S_FALSE; } HRESULT CInArchive::ReadCd(CObjectVector<CItemEx> &items, UInt64 &cdOffset, UInt64 &cdSize, CProgressVirt *progress) { - ArcInfo.Base = 0; CCdInfo cdInfo; RINOK(FindCd(cdInfo)); HRESULT res = S_FALSE; cdSize = cdInfo.Size; cdOffset = cdInfo.Offset; + if (progress) + progress->SetTotalCD(cdInfo.NumEntries); res = TryReadCd(items, ArcInfo.Base + cdOffset, cdSize, progress); if (res == S_FALSE && ArcInfo.Base == 0) { - res = TryReadCd(items, cdInfo.Offset + ArcInfo.StartPosition, cdSize, progress); + res = TryReadCd(items, ArcInfo.MarkerPos + cdOffset, cdSize, progress); if (res == S_OK) - ArcInfo.Base = ArcInfo.StartPosition; + ArcInfo.Base = ArcInfo.MarkerPos; } - if (!ReadUInt32(m_Signature)) - return S_FALSE; return res; } -HRESULT CInArchive::ReadLocalsAndCd(CObjectVector<CItemEx> &items, CProgressVirt *progress, UInt64 &cdOffset, int &numCdItems) +static HRESULT FindItem(const CObjectVector<CItemEx> &items, UInt64 offset) { - items.Clear(); - numCdItems = 0; - while (m_Signature == NSignature::kLocalFileHeader) + unsigned left = 0, right = items.Size(); + for (;;) { - // FSeek points to next byte after signature - // NFileHeader::CLocalBlock localHeader; - CItemEx item; - item.LocalHeaderPosition = m_Position - m_StreamStartPosition - 4; // points to signature; - RINOK(ReadLocalItem(item)); - item.FromLocal = true; - ReadLocalItemDescriptor(item); - items.Add(item); - if (progress && items.Size() % 100 == 0) - RINOK(progress->SetCompleted(items.Size())); - if (!ReadUInt32(m_Signature)) - break; + if (left >= right) + return -1; + unsigned index = (left + right) / 2; + UInt64 position = items[index].LocalHeaderPos; + if (offset == position) + return index; + if (offset < position) + right = index; + else + left = index + 1; } - cdOffset = m_Position - 4; - int i; - for (i = 0; i < items.Size(); i++, numCdItems++) - { - if (progress && i % 1000 == 0) - RINOK(progress->SetCompleted(items.Size())); - if (m_Signature == NSignature::kEndOfCentralDir) - break; - - if (m_Signature != NSignature::kCentralFileHeader) - return S_FALSE; +} - CItemEx cdItem; - RINOK(ReadCdItem(cdItem)); +bool IsStrangeItem(const CItem &item) +{ + return item.Name.Len() > (1 << 14) || item.Method > (1 << 8); +} - if (i == 0) +HRESULT CInArchive::ReadLocals( + CObjectVector<CItemEx> &items, CProgressVirt *progress) +{ + items.Clear(); + while (m_Signature == NSignature::kLocalFileHeader) + { + CItemEx item; + item.LocalHeaderPos = m_Position - 4 - ArcInfo.MarkerPos; + // we write ralative LocalHeaderPos here. Later we can correct it to real Base. + try { - int j; - for (j = 0; j < items.Size(); j++) + ReadLocalItem(item); + item.FromLocal = true; + if (item.HasDescriptor()) + ReadLocalItemDescriptor(item); + else { - CItemEx &item = items[j]; - if (item.Name == cdItem.Name) - { - ArcInfo.Base = item.LocalHeaderPosition - cdItem.LocalHeaderPosition; - break; - } + RINOK(IncreaseRealPosition(item.PackSize)); } - if (j == items.Size()) - return S_FALSE; + items.Add(item); + m_Signature = ReadUInt32(); } - - int index; - int left = 0, right = items.Size(); - for (;;) + catch (CUnexpectEnd &) { - if (left >= right) + if (items.IsEmpty() || items.Size() == 1 && IsStrangeItem(items[0])) return S_FALSE; - index = (left + right) / 2; - UInt64 position = items[index].LocalHeaderPosition - ArcInfo.Base; - if (cdItem.LocalHeaderPosition == position) - break; - if (cdItem.LocalHeaderPosition < position) - right = index; - else - left = index + 1; + throw; } - CItemEx &item = items[index]; - // item.LocalHeaderPosition = cdItem.LocalHeaderPosition; - item.MadeByVersion = cdItem.MadeByVersion; - item.CentralExtra = cdItem.CentralExtra; - - if ( - // item.ExtractVersion != cdItem.ExtractVersion || - !FlagsAreSame(item, cdItem) || - item.FileCRC != cdItem.FileCRC) - return S_FALSE; + if (progress && items.Size() % 1 == 0) + RINOK(progress->SetCompletedLocal(items.Size(), item.LocalHeaderPos)); + } - if (item.Name.Length() != cdItem.Name.Length() || - item.PackSize != cdItem.PackSize || - item.UnPackSize != cdItem.UnPackSize - ) - return S_FALSE; - item.Name = cdItem.Name; - item.InternalAttributes = cdItem.InternalAttributes; - item.ExternalAttributes = cdItem.ExternalAttributes; - item.Comment = cdItem.Comment; - item.FromCentral = cdItem.FromCentral; - if (!ReadUInt32(m_Signature)) + if (items.Size() == 1 && m_Signature != NSignature::kCentralFileHeader) + if (IsStrangeItem(items[0])) return S_FALSE; - } - for (i = 0; i < items.Size(); i++) - items[i].LocalHeaderPosition -= ArcInfo.Base; return S_OK; } -struct CEcd -{ - UInt16 thisDiskNumber; - UInt16 startCDDiskNumber; - UInt16 numEntriesInCDOnThisDisk; - UInt16 numEntriesInCD; - UInt32 cdSize; - UInt32 cdStartOffset; - UInt16 commentSize; - void Parse(const Byte *p); -}; - -void CEcd::Parse(const Byte *p) -{ - thisDiskNumber = Get16(p); - startCDDiskNumber = Get16(p + 2); - numEntriesInCDOnThisDisk = Get16(p + 4); - numEntriesInCD = Get16(p + 6); - cdSize = Get32(p + 8); - cdStartOffset = Get32(p + 12); - commentSize = Get16(p + 16); -} - -struct CEcd64 -{ - UInt16 versionMade; - UInt16 versionNeedExtract; - UInt32 thisDiskNumber; - UInt32 startCDDiskNumber; - UInt64 numEntriesInCDOnThisDisk; - UInt64 numEntriesInCD; - UInt64 cdSize; - UInt64 cdStartOffset; - void Parse(const Byte *p); - CEcd64() { memset(this, 0, sizeof(*this)); } -}; - -void CEcd64::Parse(const Byte *p) -{ - versionMade = Get16(p); - versionNeedExtract = Get16(p + 2); - thisDiskNumber = Get32(p + 4); - startCDDiskNumber = Get32(p + 8); - numEntriesInCDOnThisDisk = Get64(p + 12); - numEntriesInCD = Get64(p + 20); - cdSize = Get64(p + 28); - cdStartOffset = Get64(p + 36); -} #define COPY_ECD_ITEM_16(n) if (!isZip64 || ecd. n != 0xFFFF) ecd64. n = ecd. n; #define COPY_ECD_ITEM_32(n) if (!isZip64 || ecd. n != 0xFFFFFFFF) ecd64. n = ecd. n; -HRESULT CInArchive::ReadHeaders(CObjectVector<CItemEx> &items, CProgressVirt *progress) +HRESULT CInArchive::ReadHeaders2(CObjectVector<CItemEx> &items, CProgressVirt *progress) { - IsOkHeaders = true; - // m_Signature must be kLocalFileHeaderSignature or - // kEndOfCentralDirSignature + items.Clear(); + + // m_Signature must be kLocalFileHeader or kEcd // m_Position points to next byte after signature + RINOK(Stream->Seek(m_Position, STREAM_SEEK_SET, NULL)); - IsZip64 = false; - items.Clear(); + if (!_inBuffer.Create(1 << 15)) + return E_OUTOFMEMORY; + _inBuffer.SetStream(Stream); - UInt64 cdSize, cdStartOffset; - HRESULT res; - try + bool needReadCd = true; + bool localsWereRead = false; + if (m_Signature == NSignature::kEcd) { - res = ReadCd(items, cdStartOffset, cdSize, progress); + // It must be empty archive or backware archive + // we don't support backware archive still + + const unsigned kBufSize = kEcdSize - 4; + Byte buf[kBufSize]; + SafeReadBytes(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 = ArcInfo.MarkerPos; + needReadCd = false; + IsArc = true; // check it: we need more tests? + RINOK(Stream->Seek(ArcInfo.MarkerPos2 + 4, STREAM_SEEK_SET, &m_Position)); } - catch(CInArchiveException &) + + UInt64 cdSize = 0, cdRelatOffset = 0, cdAbsOffset = 0; + HRESULT res = S_OK; + + if (needReadCd) { - res = S_FALSE; + CItemEx firstItem; + // try + { + try + { + if (!ReadLocalItem(firstItem)) + return S_FALSE; + } + catch(CUnexpectEnd &) + { + return S_FALSE; + } + + IsArc = true; + res = ReadCd(items, cdRelatOffset, cdSize, progress); + if (res == S_OK) + m_Signature = ReadUInt32(); + } + // catch() { 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 + firstItem.LocalHeaderPos = ArcInfo.MarkerPos2 - ArcInfo.Base; + int index = FindItem(items, firstItem.LocalHeaderPos); + if (index == -1) + res = S_FALSE; + else if (!AreItemsEqual(firstItem, items[index])) + res = S_FALSE; + ArcInfo.CdWasRead = true; + ArcInfo.FirstItemRelatOffset = items[0].LocalHeaderPos; + } } - if (res != S_FALSE && res != S_OK) - return res; - /* - if (res != S_OK) - return res; - res = S_FALSE; - */ + CObjectVector<CItemEx> cdItems; - int numCdItems = items.Size(); + bool needSetBase = false; + unsigned numCdItems = items.Size(); + if (res == S_FALSE) { + // CD doesn't match firstItem so we clear items and read Locals. + items.Clear(); + localsWereRead = true; _inBufMode = false; - ArcInfo.Base = 0; - RINOK(m_Stream->Seek(ArcInfo.StartPosition, STREAM_SEEK_SET, &m_Position)); - if (m_Position != ArcInfo.StartPosition) - return S_FALSE; - if (!ReadUInt32(m_Signature)) - return S_FALSE; - RINOK(ReadLocalsAndCd(items, progress, cdStartOffset, numCdItems)); - cdSize = (m_Position - 4) - cdStartOffset; - cdStartOffset -= ArcInfo.Base; + ArcInfo.Base = ArcInfo.MarkerPos; + RINOK(Stream->Seek(ArcInfo.MarkerPos2, STREAM_SEEK_SET, &m_Position)); + m_Signature = ReadUInt32(); + + RINOK(ReadLocals(items, progress)); + + if (m_Signature != NSignature::kCentralFileHeader) + { + m_Position -= 4; + NoCentralDir = true; + HeadersError = true; + return S_OK; + } + _inBufMode = true; + _inBuffer.Init(); + cdAbsOffset = m_Position - 4; + for (;;) + { + CItemEx cdItem; + RINOK(ReadCdItem(cdItem)); + cdItems.Add(cdItem); + if (progress && cdItems.Size() % 1 == 0) + RINOK(progress->SetCompletedCD(items.Size())); + m_Signature = ReadUInt32(); + if (m_Signature != NSignature::kCentralFileHeader) + break; + } + + cdSize = (m_Position - 4) - cdAbsOffset; + needSetBase = true; + numCdItems = cdItems.Size(); + + if (!cdItems.IsEmpty()) + { + ArcInfo.CdWasRead = true; + ArcInfo.FirstItemRelatOffset = cdItems[0].LocalHeaderPos; + } } CEcd64 ecd64; bool isZip64 = false; - UInt64 zip64EcdStartOffset = m_Position - 4 - ArcInfo.Base; - if (m_Signature == NSignature::kZip64EndOfCentralDir) + UInt64 ecd64AbsOffset = m_Position - 4; + if (m_Signature == NSignature::kEcd64) { IsZip64 = isZip64 = true; UInt64 recordSize = ReadUInt64(); - const int kBufSize = kZip64EcdSize; + const unsigned kBufSize = kEcd64_MainSize; Byte buf[kBufSize]; SafeReadBytes(buf, kBufSize); ecd64.Parse(buf); - Skip(recordSize - kZip64EcdSize); - if (!ReadUInt32(m_Signature)) - return S_FALSE; + Skip64(recordSize - kEcd64_MainSize); + m_Signature = ReadUInt32(); + if (ecd64.thisDiskNumber != 0 || ecd64.startCDDiskNumber != 0) - throw CInArchiveException(CInArchiveException::kMultiVolumeArchiveAreNotSupported); + return E_NOTIMPL; + + if (needSetBase) + { + ArcInfo.Base = cdAbsOffset - ecd64.cdStartOffset; + cdRelatOffset = ecd64.cdStartOffset; + needSetBase = false; + } + if (ecd64.numEntriesInCDOnThisDisk != numCdItems || ecd64.numEntriesInCD != numCdItems || ecd64.cdSize != cdSize || - (ecd64.cdStartOffset != cdStartOffset && + (ecd64.cdStartOffset != cdRelatOffset && (!items.IsEmpty()))) return S_FALSE; } - if (m_Signature == NSignature::kZip64EndOfCentralDirLocator) + if (m_Signature == NSignature::kEcd64Locator) { + if (!isZip64) + return S_FALSE; /* UInt32 startEndCDDiskNumber = */ ReadUInt32(); - UInt64 endCDStartOffset = ReadUInt64(); + UInt64 ecd64RelatOffset = ReadUInt64(); /* UInt32 numberOfDisks = */ ReadUInt32(); - if (zip64EcdStartOffset != endCDStartOffset) - return S_FALSE; - if (!ReadUInt32(m_Signature)) + if (ecd64AbsOffset != ArcInfo.Base + ecd64RelatOffset) return S_FALSE; + m_Signature = ReadUInt32(); } - if (m_Signature != NSignature::kEndOfCentralDir) + if (m_Signature != NSignature::kEcd) return S_FALSE; - const int kBufSize = kEcdSize - 4; + const unsigned kBufSize = kEcdSize - 4; Byte buf[kBufSize]; SafeReadBytes(buf, kBufSize); CEcd ecd; @@ -849,47 +1188,119 @@ HRESULT CInArchive::ReadHeaders(CObjectVector<CItemEx> &items, CProgressVirt *pr COPY_ECD_ITEM_32(cdSize); COPY_ECD_ITEM_32(cdStartOffset); - ReadBuffer(ArcInfo.Comment, ecd.commentSize); + if (needSetBase) + { + ArcInfo.Base = cdAbsOffset - ecd64.cdStartOffset; + cdRelatOffset = ecd64.cdStartOffset; + needSetBase = false; + } + + if (localsWereRead && (UInt64)ArcInfo.Base != ArcInfo.MarkerPos) + { + UInt64 delta = ArcInfo.MarkerPos - ArcInfo.Base; + for (unsigned i = 0; i < items.Size(); i++) + items[i].LocalHeaderPos += delta; + } + + + // ---------- merge Central Directory Items ---------- + + if (!cdItems.IsEmpty()) + { + for (unsigned i = 0; i < cdItems.Size(); i++) + { + const CItemEx &cdItem = cdItems[i]; + int index = FindItem(items, cdItem.LocalHeaderPos); + if (index == -1) + { + items.Add(cdItem); + continue; + } + CItemEx &item = items[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) + continue; + + // item.LocalHeaderPos = cdItem.LocalHeaderPos; + // 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; + } + } if (ecd64.thisDiskNumber != 0 || ecd64.startCDDiskNumber != 0) - throw CInArchiveException(CInArchiveException::kMultiVolumeArchiveAreNotSupported); - if (numCdItems != items.Size()) - IsOkHeaders = false; - if ((UInt16)ecd64.numEntriesInCDOnThisDisk != ((UInt16)numCdItems) || + return E_NOTIMPL; + + if (isZip64) + { + if (ecd64.numEntriesInCDOnThisDisk != items.Size()) + HeadersError = true; + } + else + { + // old 7-zip could store 32-bit number of CD items to 16-bit field. + if ((UInt16)ecd64.numEntriesInCDOnThisDisk != (UInt16)numCdItems || + (UInt16)ecd64.numEntriesInCDOnThisDisk != (UInt16)items.Size()) + HeadersError = true; + } + + ReadBuffer(ArcInfo.Comment, ecd.commentSize); + _inBufMode = false; + _inBuffer.Free(); + + if ( (UInt16)ecd64.numEntriesInCD != ((UInt16)numCdItems) || (UInt32)ecd64.cdSize != (UInt32)cdSize || - ((UInt32)(ecd64.cdStartOffset) != (UInt32)cdStartOffset && + ((UInt32)(ecd64.cdStartOffset) != (UInt32)cdRelatOffset && (!items.IsEmpty()))) return S_FALSE; - _inBufMode = false; - _inBuffer.Free(); - ArcInfo.FinishPosition = m_Position; + // printf("\nOpen OK"); return S_OK; } +HRESULT CInArchive::ReadHeaders(CObjectVector<CItemEx> &items, CProgressVirt *progress) +{ + HRESULT res; + try + { + res = ReadHeaders2(items, progress); + } + catch (const CInBufferException &e) { res = e.ErrorCode; } + catch (const CUnexpectEnd &) + { + if (items.IsEmpty()) + return S_FALSE; + UnexpectedEnd = true; + res = S_OK; + } + catch (...) + { + _inBufMode = false; + throw; + } + ArcInfo.FinishPos = m_Position; + _inBufMode = false; + return res; +} + ISequentialInStream* CInArchive::CreateLimitedStream(UInt64 position, UInt64 size) { CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr<ISequentialInStream> stream(streamSpec); - SeekInArchive(ArcInfo.Base + position); - streamSpec->SetStream(m_Stream); + Stream->Seek(ArcInfo.Base + position, STREAM_SEEK_SET, NULL); + streamSpec->SetStream(Stream); streamSpec->Init(size); return stream.Detach(); } -IInStream* CInArchive::CreateStream() -{ - CMyComPtr<IInStream> stream = m_Stream; - return stream.Detach(); -} - -bool CInArchive::SeekInArchive(UInt64 position) -{ - UInt64 newPosition; - if (m_Stream->Seek(position, STREAM_SEEK_SET, &newPosition) != S_OK) - return false; - return (newPosition == position); -} - }} |