From fdc33e91bd95f47327a69969c76ff9a5112be7e3 Mon Sep 17 00:00:00 2001 From: Daniel Nash Date: Mon, 26 Apr 2021 14:56:23 -0400 Subject: [PATCH] Conditionally parse Zip64 extra field based on specification The Zip64 extra field should look for values based on the corresponding values in the local entry header. Fixes adamhathcock/sharpcompress#595 --- .../Zip/Headers/DirectoryEntryHeader.cs | 2 + .../Common/Zip/Headers/LocalEntryHeader.cs | 2 + .../Headers/LocalEntryHeaderExtraFactory.cs | 64 +++++++++++++----- .../Common/Zip/Headers/ZipFileEntry.cs | 2 +- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 18 +++++ .../Archives/Zip.zip64.compressedonly.zip | Bin 0 -> 454 bytes 6 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 tests/TestArchives/Archives/Zip.zip64.compressedonly.zip diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs index 54553c810..57792e3fb 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs @@ -63,6 +63,8 @@ internal override void Read(BinaryReader reader) var zip64ExtraData = Extra.OfType().FirstOrDefault(); if (zip64ExtraData != null) { + zip64ExtraData.Process(UncompressedSize, CompressedSize, RelativeOffsetOfEntryHeader, DiskNumberStart); + if (CompressedSize == uint.MaxValue) { CompressedSize = zip64ExtraData.CompressedSize; diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs index 1cd156495..38f3a6f35 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs @@ -53,6 +53,8 @@ internal override void Read(BinaryReader reader) var zip64ExtraData = Extra.OfType().FirstOrDefault(); if (zip64ExtraData != null) { + zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0); + if (CompressedSize == uint.MaxValue) { CompressedSize = zip64ExtraData.CompressedSize; diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs index 31282db4d..efc7af3aa 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs @@ -66,46 +66,74 @@ internal sealed class Zip64ExtendedInformationExtraField : ExtraData public Zip64ExtendedInformationExtraField(ExtraDataType type, ushort length, byte[] dataBytes) : base(type, length, dataBytes) { - Process(); } - private void Process() + // From the spec, values are only in the extradata if the standard + // value is set to 0xFFFFFFFF (or 0xFFFF for the Disk Start Number). + // Values, if present, must appear in the following order: + // - Original Size + // - Compressed Size + // - Relative Header Offset + // - Disk Start Number + public void Process(long uncompressedFileSize, long compressedFileSize, long relativeHeaderOffset, ushort diskNumber) { - if (DataBytes.Length >= 8) + var bytesRequired = ((uncompressedFileSize == uint.MaxValue) ? 8 : 0) + + ((compressedFileSize == uint.MaxValue) ? 8 : 0) + + ((relativeHeaderOffset == uint.MaxValue) ? 8 : 0) + + ((diskNumber == ushort.MaxValue) ? 4 : 0); + var currentIndex = 0; + + if (bytesRequired > DataBytes.Length) { - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes); + throw new ArchiveException("Zip64 extended information extra field is not large enough for the required information"); } - if (DataBytes.Length >= 16) + if (uncompressedFileSize == uint.MaxValue) { - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8)); + UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); + currentIndex += 8; } - if (DataBytes.Length >= 24) + if (compressedFileSize == uint.MaxValue) { - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16)); + CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); + currentIndex += 8; } - if (DataBytes.Length >= 28) + if (relativeHeaderOffset == uint.MaxValue) { - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(24)); + RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); + currentIndex += 8; } - switch (DataBytes.Length) + if (diskNumber == ushort.MaxValue) { - case 8: - case 16: - case 24: - case 28: - break; - default: - throw new ArchiveException($"Unexpected size of of Zip64 extended information extra field: {DataBytes.Length}"); + VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(currentIndex)); } } + /// + /// Uncompressed file size. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// public long UncompressedSize { get; private set; } + + /// + /// Compressed file size. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// public long CompressedSize { get; private set; } + + /// + /// Relative offset of the entry header. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// public long RelativeOffsetOfEntryHeader { get; private set; } + + /// + /// Volume number. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFF value. + /// public uint VolumeNumber { get; private set; } } diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs index 902fca722..5c72d54cf 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs @@ -105,6 +105,6 @@ protected void LoadExtra(byte[] extra) internal ZipFilePart Part { get; set; } - internal bool IsZip64 => CompressedSize == uint.MaxValue; + internal bool IsZip64 => CompressedSize >= uint.MaxValue; } } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index d272aa9a9..ec710c761 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -576,5 +576,23 @@ public void Zip_LongComment_Read() Assert.Equal(1, count); } } + + [Fact] + public void Zip_Zip64_CompressedSizeExtraOnly_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.compressedonly.zip"); + + using (ZipArchive za = ZipArchive.Open(zipPath)) + { + var firstEntry = za.Entries.First(x => x.Key == "test/test.txt"); + + using (var memoryStream = new MemoryStream()) + using (var firstStream = firstEntry.OpenEntryStream()) + { + firstStream.CopyTo(memoryStream); + Assert.Equal(15, memoryStream.Length); + } + } + } } } diff --git a/tests/TestArchives/Archives/Zip.zip64.compressedonly.zip b/tests/TestArchives/Archives/Zip.zip64.compressedonly.zip new file mode 100644 index 0000000000000000000000000000000000000000..c9fed70058fede7947080fcd34406751f7433430 GIT binary patch literal 454 zcmWIWW@gc4U|`^2DDW%~h&_4u*nc442a51A7%-Hi7MJLQ2)&Yu5=I6A29N{@a4{%= zX+{PKh6^$mCy6j#W2{KI-j7UkpFE)x#<0kxb<&kbLbeP6-i%DT47lvF0UO5vHWuUp zG@E7MHbd2cDPmj^;LXOS1Jwkv9>QUSioxh|C_f8I1NE_km=B?R5EbCf$_AoA4y*^# zML^ul;F_15pOTrEZdF>6rfZ>4Q={OVpIcCrT3no&pJ(NgnwD6aQvwnODlDojD6#U( z&r1dIqdWu5OspJp$`dP#dAXd6QWHy3Qxp%9^(^%a^-OuW057L+4gdfE literal 0 HcmV?d00001