From 55c2e75b1d13e3af3b6a81a91406cd4edf51487e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:49:40 +0000 Subject: [PATCH 1/3] Initial plan From 75d2b70f206744b853431aa22bb696e337ded72a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:06:18 +0000 Subject: [PATCH 2/3] Fix DataErrorException when extracting 0-size LZMA ZIP entries Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .../Common/Zip/ZipFilePart.Async.cs | 14 +++++++++++ src/SharpCompress/Common/Zip/ZipFilePart.cs | 15 ++++++++++++ src/SharpCompress/packages.lock.json | 12 ++++----- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 14 +++++++++++ .../SharpCompress.Test/Zip/ZipReaderTests.cs | 23 ++++++++++++++++++ .../TestArchives/Archives/Zip.lzma.empty.zip | Bin 0 -> 145 bytes 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 tests/TestArchives/Archives/Zip.lzma.empty.zip diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs index fb2caf11..90f9ae2b 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -158,6 +158,20 @@ internal abstract partial class ZipFilePart .ReadFullyAsync(props, 0, propsSize, cancellationToken) .ConfigureAwait(false); + // When the uncompressed size is known to be zero, skip remaining compressed + // bytes (required for streaming reads) and return an empty stream. + // Bit1 (EOS marker flag) means the output size is not stored in the header + // (the LZMA stream itself contains an end-of-stream marker instead), so we + // only short-circuit when the size is explicitly known to be zero. + if ( + !FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) + && Header.UncompressedSize == 0 + ) + { + await stream.SkipAsync(cancellationToken).ConfigureAwait(false); + return Stream.Null; + } + context = context with { Properties = props, diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 3f11df6d..e4ac519c 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -149,6 +149,21 @@ internal abstract partial class ZipFilePart : FilePart reader.ReadUInt16(); // LZMA version var propsLength = reader.ReadUInt16(); var props = reader.ReadBytes(propsLength); + + // When the uncompressed size is known to be zero, skip remaining compressed + // bytes (required for streaming reads) and return an empty stream. + // Bit1 (EOS marker flag) means the output size is not stored in the header + // (the LZMA stream itself contains an end-of-stream marker instead), so we + // only short-circuit when the size is explicitly known to be zero. + if ( + !FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) + && Header.UncompressedSize == 0 + ) + { + stream.Skip(); + return Stream.Null; + } + context = context with { Properties = props, diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 5059aafe..b44de28b 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -268,9 +268,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -442,9 +442,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.22, )", - "resolved": "8.0.22", - "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" + "requested": "[8.0.23, )", + "resolved": "8.0.23", + "contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index f166187e..914688c2 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -896,4 +896,18 @@ public class ZipArchiveTests : ArchiveTests const int expected = (S_IFREG | 0b110_100_100) << 16; // 0644 mode regular file Assert.Equal(expected, firstEntry.Attrib); } + + [Fact] + public void Zip_LZMA_ZeroSizeEntry_CanExtract() + { + using var archive = ArchiveFactory.OpenArchive( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.empty.zip") + ); + var entries = archive.Entries.Where(x => !x.IsDirectory).ToList(); + Assert.Single(entries); + Assert.Equal(0, entries[0].Size); + var outStream = new MemoryStream(); + entries[0].WriteTo(outStream); + Assert.Equal(0, outStream.Length); + } } diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 5ca55b7b..af4bd3ad 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -543,4 +543,27 @@ public class ZipReaderTests : ReaderTests // Should iterate through all entries, not just the first one Assert.True(count > 1, $"Expected more than 1 entry, but got {count}"); } + + [Fact] + public void Zip_LZMA_ZeroSizeEntry_CanExtract_Streaming() + { + var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.empty.zip"); + using var fileStream = File.OpenRead(path); + using Stream stream = new ForwardOnlyStream(fileStream); + using var reader = ReaderFactory.OpenReader(stream); + + var count = 0; + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + count++; + Assert.Equal(0, reader.Entry.Size); + var outStream = new MemoryStream(); + reader.WriteEntryTo(outStream); + Assert.Equal(0, outStream.Length); + } + } + Assert.Equal(1, count); + } } diff --git a/tests/TestArchives/Archives/Zip.lzma.empty.zip b/tests/TestArchives/Archives/Zip.lzma.empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..81c1367870e37388630f1b4eb02c5ef647d72453 GIT binary patch literal 145 zcmWIWW@Zs#U|`^5fB|7B10<4~TToINpHr2as8>=^BErDR5X->Oz`)S_|M&m@2N)O_ k0=yZSL>O=z05uCrp&A$9&B_LnU}Ruquwh_eum^D%04JUl&j0`b literal 0 HcmV?d00001 From 30dfa72622860a3c9c62ca3abc070d68ac52bb48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:10:58 +0000 Subject: [PATCH 3/3] Fix DataErrorException when extracting LZMA-compressed zero-byte ZIP entries Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/packages.lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index b44de28b..5059aafe 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -268,9 +268,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -442,9 +442,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.23, )", - "resolved": "8.0.23", - "contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct",