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/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 00000000..81c13678 Binary files /dev/null and b/tests/TestArchives/Archives/Zip.lzma.empty.zip differ