From 9c555c41cc1b8d6065001029b45de9f4bf74157c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 24 Oct 2026 12:02:27 +0100 Subject: [PATCH] Preserve legacy ZIP descriptor width probe --- .../Zip/StreamingZipHeaderFactory.Async.cs | 7 ++- .../Common/Zip/StreamingZipHeaderFactory.cs | 7 ++- .../Zip/Zip64DescriptorSizeTests.cs | 55 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs index ff8e6817..fdc534e4 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs @@ -160,7 +160,12 @@ internal sealed partial class StreamingZipHeaderFactory .ReadUInt32Async(_cancellationToken) .ConfigureAwait(false); - if (!IsHeader(headerBytes)) + // Keep the legacy probe limited to local/central headers: other + // record signatures can be the low word of a 64-bit size. + if ( + headerBytes != ENTRY_HEADER_BYTES + && headerBytes != DIRECTORY_START_HEADER_BYTES + ) { // Descriptor words are size data, including 0xFFFFFFFF; unlike // local headers, descriptors do not contain size sentinels. diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs index 2501a932..15c4b87e 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -88,7 +88,12 @@ internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory uncompSize = reader.ReadUInt32(); headerBytes = reader.ReadUInt32(); - if (!IsHeader(headerBytes)) + // Keep the legacy probe limited to local/central headers: other + // record signatures can be the low word of a 64-bit size. + if ( + headerBytes != ENTRY_HEADER_BYTES + && headerBytes != DIRECTORY_START_HEADER_BYTES + ) { // Descriptor words are size data, including 0xFFFFFFFF; unlike // local headers, descriptors do not contain size sentinels. diff --git a/tests/SharpCompress.Test/Zip/Zip64DescriptorSizeTests.cs b/tests/SharpCompress.Test/Zip/Zip64DescriptorSizeTests.cs index dfc80e38..f4aeb6ce 100644 --- a/tests/SharpCompress.Test/Zip/Zip64DescriptorSizeTests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64DescriptorSizeTests.cs @@ -72,6 +72,61 @@ public class Zip64DescriptorSizeTests Assert.Equal("second", secondKey); } + public static IEnumerable LegacyDescriptorSizes() + { + foreach (var useAsync in new[] { false, true }) + { + foreach (var signature in new[] { false, true }) + { + foreach ( + var size in new[] + { + 0x08074B50UL, + 0x05054B50UL, + 0x06054B50UL, + 0x30304B50UL, + 0x06064B50UL, + 0x07064B50UL, + } + ) + { + yield return new object[] { useAsync, signature, size }; + yield return new object[] { useAsync, signature, size | (1UL << 32) }; + } + } + } + } + + [Theory] + [MemberData(nameof(LegacyDescriptorSizes))] + public async Task Legacy_Descriptor_Preserves_Signature_Size_And_Next_Header( + bool useAsync, + bool signature, + ulong size + ) + { + var zip = WriteZip64Archive(); + var descriptor = FindDescriptor(zip); + BinaryPrimitives.WriteUInt64LittleEndian(zip.AsSpan(descriptor + 16), size); + // Simulate older writers that used 64-bit descriptors without declaring ZIP64. + BinaryPrimitives.WriteUInt32LittleEndian(zip.AsSpan(18), 0); + BinaryPrimitives.WriteUInt32LittleEndian(zip.AsSpan(22), 0); + var extra = 30 + BinaryPrimitives.ReadUInt16LittleEndian(zip.AsSpan(26)); + BinaryPrimitives.WriteUInt16LittleEndian(zip.AsSpan(extra), 0xFFFF); + if (!signature) + { + zip = zip.Take(descriptor).Concat(zip.Skip(descriptor + 4)).ToArray(); + } + + var (firstSize, secondKey) = await ReadFirstTwoEntries( + zip, + useAsync, + expectedCompressedSize: 3 + ); + Assert.Equal((long)size, firstSize); + Assert.Equal("second", secondKey); + } + [Theory] [InlineData(false, false, 0x04034B50)] [InlineData(false, true, 0x04034B50)]