diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs index ef133fbdb..32074e4df 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs @@ -48,5 +48,15 @@ internal override void Write(BinaryWriter writer) public byte[] Comment { get; private set; } public ushort TotalNumberOfEntries { get; private set; } + + public bool IsZip64 + { + get + { + return TotalNumberOfEntriesInDisk == ushort.MaxValue + || DirectorySize == uint.MaxValue + || DirectoryStartOffsetRelativeToDisk == uint.MaxValue; + } + } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs new file mode 100644 index 000000000..607ac8cea --- /dev/null +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; + +namespace SharpCompress.Common.Zip.Headers +{ + internal class Zip64DirectoryEndHeader : ZipHeader + { + public Zip64DirectoryEndHeader() + : base(ZipHeaderType.Zip64DirectoryEnd) + { + } + + internal override void Read(BinaryReader reader) + { + SizeOfDirectoryEndRecord = (long)reader.ReadUInt64(); + VersionMadeBy = reader.ReadUInt16(); + VersionNeededToExtract = reader.ReadUInt16(); + VolumeNumber = reader.ReadUInt32(); + FirstVolumeWithDirectory = reader.ReadUInt32(); + TotalNumberOfEntriesInDisk = (long)reader.ReadUInt64(); + TotalNumberOfEntries = (long)reader.ReadUInt64(); + DirectorySize = (long)reader.ReadUInt64(); + DirectoryStartOffsetRelativeToDisk = (long)reader.ReadUInt64(); + DataSector = reader.ReadBytes((int)(SizeOfDirectoryEndRecord - SizeOfFixedHeaderDataExceptSignatureAndSizeFields)); + } + + const int SizeOfFixedHeaderDataExceptSignatureAndSizeFields = 44; + + internal override void Write(BinaryWriter writer) + { + throw new System.NotImplementedException(); + } + + public long SizeOfDirectoryEndRecord { get; private set; } + + public ushort VersionMadeBy { get; private set; } + + public ushort VersionNeededToExtract { get; private set; } + + public uint VolumeNumber { get; private set; } + + public uint FirstVolumeWithDirectory { get; private set; } + + public long TotalNumberOfEntriesInDisk { get; private set; } + + public long TotalNumberOfEntries { get; private set; } + + public long DirectorySize { get; private set; } + + public long DirectoryStartOffsetRelativeToDisk { get; private set; } + + public byte[] DataSector { get; private set; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs new file mode 100644 index 000000000..a00fcb94c --- /dev/null +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs @@ -0,0 +1,30 @@ +using System.IO; + +namespace SharpCompress.Common.Zip.Headers +{ + internal class Zip64DirectoryEndLocatorHeader : ZipHeader + { + public Zip64DirectoryEndLocatorHeader() + : base(ZipHeaderType.Zip64DirectoryEndLocator) + { + } + + internal override void Read(BinaryReader reader) + { + FirstVolumeWithDirectory = reader.ReadUInt32(); + RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64(); + TotalNumberOfVolumes = reader.ReadUInt32(); + } + + internal override void Write(BinaryWriter writer) + { + throw new System.NotImplementedException(); + } + + public uint FirstVolumeWithDirectory { get; private set; } + + public long RelativeOffsetOfTheEndOfDirectoryRecord { get; private set; } + + public uint TotalNumberOfVolumes { get; private set; } + } +} \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs index c099dba09..924ade938 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs @@ -57,11 +57,11 @@ protected byte[] EncodeString(string str) internal ZipCompressionMethod CompressionMethod { get; set; } - internal uint CompressedSize { get; set; } + internal long CompressedSize { get; set; } internal long? DataStartPosition { get; set; } - internal uint UncompressedSize { get; set; } + internal long UncompressedSize { get; set; } internal List Extra { get; set; } @@ -112,5 +112,7 @@ protected void LoadExtra(byte[] extra) } internal ZipFilePart Part { get; set; } + + internal bool IsZip64 { get { return CompressedSize == uint.MaxValue; } } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs b/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs index 75cba925b..a4286dedc 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs @@ -6,6 +6,8 @@ internal enum ZipHeaderType LocalEntry, DirectoryEntry, DirectoryEnd, - Split + Split, + Zip64DirectoryEnd, + Zip64DirectoryEndLocator } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs index 874c88f1e..3d78ce3b8 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -9,6 +9,7 @@ namespace SharpCompress.Common.Zip internal class SeekableZipHeaderFactory : ZipHeaderFactory { private const int MAX_ITERATIONS_FOR_DIRECTORY_HEADER = 4096; + private bool zip64; internal SeekableZipHeaderFactory(string password) : base(StreamingMode.Seekable, password) @@ -17,40 +18,39 @@ internal SeekableZipHeaderFactory(string password) internal IEnumerable ReadSeekableHeader(Stream stream) { - long offset = 0; - uint signature; - BinaryReader reader = new BinaryReader(stream); - - int iterationCount = 0; - do - { - if ((stream.Length + offset) - 4 < 0) - { - throw new ArchiveException("Failed to locate the Zip Header"); - } - stream.Seek(offset - 4, SeekOrigin.End); - signature = reader.ReadUInt32(); - offset--; - iterationCount++; - if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER) - { - throw new ArchiveException( - "Could not find Zip file Directory at the end of the file. File may be corrupted."); - } - } - while (signature != DIRECTORY_END_HEADER_BYTES); + var reader = new BinaryReader(stream); + SeekBackToHeader(stream, reader, DIRECTORY_END_HEADER_BYTES); var entry = new DirectoryEndHeader(); entry.Read(reader); - stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); - DirectoryEntryHeader directoryEntryHeader = null; + if (entry.IsZip64) + { + zip64 = true; + SeekBackToHeader(stream, reader, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR); + var zip64Locator = new Zip64DirectoryEndLocatorHeader(); + zip64Locator.Read(reader); + + stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin); + uint zip64Signature = reader.ReadUInt32(); + if(zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY) + throw new ArchiveException("Failed to locate the Zip64 Header"); + + var zip64Entry = new Zip64DirectoryEndHeader(); + zip64Entry.Read(reader); + stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); + } + else + { + stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); + } + long position = stream.Position; while (true) { stream.Position = position; - signature = reader.ReadUInt32(); - directoryEntryHeader = ReadHeader(signature, reader) as DirectoryEntryHeader; + uint signature = reader.ReadUInt32(); + var directoryEntryHeader = ReadHeader(signature, reader, zip64) as DirectoryEntryHeader; position = stream.Position; if (directoryEntryHeader == null) { @@ -63,12 +63,35 @@ internal IEnumerable ReadSeekableHeader(Stream stream) } } + private static void SeekBackToHeader(Stream stream, BinaryReader reader, uint headerSignature) + { + long offset = 0; + uint signature; + int iterationCount = 0; + do + { + if ((stream.Length + offset) - 4 < 0) + { + throw new ArchiveException("Failed to locate the Zip Header"); + } + stream.Seek(offset - 4, SeekOrigin.End); + signature = reader.ReadUInt32(); + offset--; + iterationCount++; + if (iterationCount > MAX_ITERATIONS_FOR_DIRECTORY_HEADER) + { + throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted."); + } + } + while (signature != headerSignature); + } + internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader) { stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin); BinaryReader reader = new BinaryReader(stream); uint signature = reader.ReadUInt32(); - var localEntryHeader = ReadHeader(signature, reader) as LocalEntryHeader; + var localEntryHeader = ReadHeader(signature, reader, zip64) as LocalEntryHeader; if (localEntryHeader == null) { throw new InvalidOperationException(); diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index ebb55c647..712b606c1 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -133,8 +133,9 @@ protected Stream GetCryptoStream(Stream plainStream) throw new NotSupportedException("Cannot encrypt file with unknown size at start."); } - if ((Header.CompressedSize == 0) + if ((Header.CompressedSize == 0 && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)) + || Header.IsZip64) { plainStream = new NonDisposingStream(plainStream); //make sure AES doesn't close } diff --git a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs index cb01f134c..9eeb087d8 100644 --- a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs @@ -17,8 +17,8 @@ internal class ZipHeaderFactory internal const uint DIGITAL_SIGNATURE = 0x05054b50; internal const uint SPLIT_ARCHIVE_HEADER_BYTES = 0x30304b50; - private const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; - private const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50; + internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; + internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50; protected LocalEntryHeader lastEntryHeader; private readonly string password; @@ -30,7 +30,7 @@ protected ZipHeaderFactory(StreamingMode mode, string password) this.password = password; } - protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader) + protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false) { switch (headerBytes) { @@ -54,14 +54,12 @@ protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader) if (FlagUtility.HasFlag(lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor)) { lastEntryHeader.Crc = reader.ReadUInt32(); - lastEntryHeader.CompressedSize = reader.ReadUInt32(); - lastEntryHeader.UncompressedSize = reader.ReadUInt32(); + lastEntryHeader.CompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32(); + lastEntryHeader.UncompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32(); } else { - reader.ReadUInt32(); - reader.ReadUInt32(); - reader.ReadUInt32(); + reader.ReadBytes(zip64 ? 20 : 12); } return null; } @@ -78,9 +76,14 @@ protected ZipHeader ReadHeader(uint headerBytes, BinaryReader reader) return new SplitHeader(); } case ZIP64_END_OF_CENTRAL_DIRECTORY: + { + var entry = new Zip64DirectoryEndHeader(); + entry.Read(reader); + return entry; + } case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR: { - var entry = new IgnoreHeader(ZipHeaderType.Ignore); + var entry = new Zip64DirectoryEndLocatorHeader(); entry.Read(reader); return entry; } diff --git a/test/SharpCompress.Test/Zip/ZipArchiveTests.cs b/test/SharpCompress.Test/Zip/ZipArchiveTests.cs index 3f4692415..11882ef4d 100644 --- a/test/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/test/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -25,7 +25,6 @@ public void Zip_ZipX_ArchiveStreamRead() ArchiveStreamRead("Zip.zipx"); } - [Fact] public void Zip_BZip2_Streamed_ArchiveStreamRead() { @@ -130,6 +129,18 @@ public void Zip_None_ArchiveFileRead() ArchiveFileRead("Zip.none.zip"); } + [Fact] + public void Zip_Zip64_ArchiveStreamRead() + { + ArchiveStreamRead("Zip.zip64.zip"); + } + + [Fact] + public void Zip_Zip64_ArchiveFileRead() + { + ArchiveFileRead("Zip.zip64.zip"); + } + [Fact] public void Zip_Random_Write_Remove() { diff --git a/test/TestArchives/Archives/Zip.zip64.zip b/test/TestArchives/Archives/Zip.zip64.zip new file mode 100644 index 000000000..7a581c1f5 Binary files /dev/null and b/test/TestArchives/Archives/Zip.zip64.zip differ