diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs index ef133fbd..32074e4d 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs @@ -48,5 +48,15 @@ namespace SharpCompress.Common.Zip.Headers 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 00000000..607ac8ce --- /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 00000000..a00fcb94 --- /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 c099dba0..924ade93 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs @@ -57,11 +57,11 @@ namespace SharpCompress.Common.Zip.Headers 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 @@ namespace SharpCompress.Common.Zip.Headers } 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 75cba925..a4286ded 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs @@ -6,6 +6,8 @@ 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 874c88f1..3d78ce3b 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) @@ -16,11 +17,56 @@ namespace SharpCompress.Common.Zip } internal IEnumerable ReadSeekableHeader(Stream stream) + { + var reader = new BinaryReader(stream); + + SeekBackToHeader(stream, reader, DIRECTORY_END_HEADER_BYTES); + var entry = new DirectoryEndHeader(); + entry.Read(reader); + + 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; + uint signature = reader.ReadUInt32(); + var directoryEntryHeader = ReadHeader(signature, reader, zip64) as DirectoryEntryHeader; + position = stream.Position; + if (directoryEntryHeader == null) + { + yield break; + } + + //entry could be zero bytes so we need to know that. + directoryEntryHeader.HasData = directoryEntryHeader.CompressedSize != 0; + yield return directoryEntryHeader; + } + } + + private static void SeekBackToHeader(Stream stream, BinaryReader reader, uint headerSignature) { long offset = 0; uint signature; - BinaryReader reader = new BinaryReader(stream); - int iterationCount = 0; do { @@ -34,33 +80,10 @@ namespace SharpCompress.Common.Zip 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."); + 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 entry = new DirectoryEndHeader(); - entry.Read(reader); - stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); - - DirectoryEntryHeader directoryEntryHeader = null; - long position = stream.Position; - while (true) - { - stream.Position = position; - signature = reader.ReadUInt32(); - directoryEntryHeader = ReadHeader(signature, reader) as DirectoryEntryHeader; - position = stream.Position; - if (directoryEntryHeader == null) - { - yield break; - } - - //entry could be zero bytes so we need to know that. - directoryEntryHeader.HasData = directoryEntryHeader.CompressedSize != 0; - yield return directoryEntryHeader; - } + while (signature != headerSignature); } internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader) @@ -68,7 +91,7 @@ namespace SharpCompress.Common.Zip 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 ebb55c64..712b606c 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -133,8 +133,9 @@ namespace SharpCompress.Common.Zip 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 cb01f134..9eeb087d 100644 --- a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs @@ -17,8 +17,8 @@ namespace SharpCompress.Common.Zip 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 @@ namespace SharpCompress.Common.Zip 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 @@ namespace SharpCompress.Common.Zip 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 @@ namespace SharpCompress.Common.Zip 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 3f469241..11882ef4 100644 --- a/test/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/test/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -25,7 +25,6 @@ namespace SharpCompress.Test ArchiveStreamRead("Zip.zipx"); } - [Fact] public void Zip_BZip2_Streamed_ArchiveStreamRead() { @@ -130,6 +129,18 @@ namespace SharpCompress.Test 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 00000000..7a581c1f Binary files /dev/null and b/test/TestArchives/Archives/Zip.zip64.zip differ