From d0f44839ffb1bc42ff61b298ff14c9465b865cd8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 23 Jan 2026 15:58:14 +0000
Subject: [PATCH 1/4] Initial plan
From 41e0c151de5701abf176c73868af6c0075c4fa3b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 23 Jan 2026 16:04:09 +0000
Subject: [PATCH 2/4] Fix regression: archive iteration breaking when input
stream throws in Flush()
- Modified ZlibBaseStream.Flush() and FlushAsync() to only flush the underlying stream when in Writer mode
- Added ThrowOnFlushStream mock for testing
- Added regression tests for Deflate and LZMA compressed archives
- All tests pass successfully
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
---
.../Compressors/Deflate/ZlibBaseStream.cs | 16 +++-
src/SharpCompress/packages.lock.json | 12 +--
.../Mocks/ThrowOnFlushStream.cs | 73 +++++++++++++++++++
.../SharpCompress.Test/Zip/ZipReaderTests.cs | 48 ++++++++++++
4 files changed, 141 insertions(+), 8 deletions(-)
create mode 100644 tests/SharpCompress.Test/Mocks/ThrowOnFlushStream.cs
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
index dd6590b7..5099de54 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
@@ -586,7 +586,13 @@ internal class ZlibBaseStream : Stream, IStreamStack
public override void Flush()
{
- _stream.Flush();
+ // Only flush the underlying stream when in write mode
+ // Flushing input streams during read operations is not meaningful
+ // and can cause issues with forward-only/non-seekable streams
+ if (_streamMode == StreamMode.Writer)
+ {
+ _stream.Flush();
+ }
//rewind the buffer
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
z.AvailableBytesIn = 0;
@@ -594,7 +600,13 @@ internal class ZlibBaseStream : Stream, IStreamStack
public override async Task FlushAsync(CancellationToken cancellationToken)
{
- await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ // Only flush the underlying stream when in write mode
+ // Flushing input streams during read operations is not meaningful
+ // and can cause issues with forward-only/non-seekable streams
+ if (_streamMode == StreamMode.Writer)
+ {
+ await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
//rewind the buffer
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
z.AvailableBytesIn = 0;
diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json
index 032c15c4..5e7ece33 100644
--- a/src/SharpCompress/packages.lock.json
+++ b/src/SharpCompress/packages.lock.json
@@ -216,9 +216,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
- "requested": "[10.0.1, )",
- "resolved": "10.0.1",
- "contentHash": "ISahzLHsHY7vrwqr2p1YWZ+gsxoBRtH7gWRDK8fDUst9pp2He0GiesaqEfeX0V8QMCJM3eNEHGGpnIcPjFo2NQ=="
+ "requested": "[10.0.2, )",
+ "resolved": "10.0.2",
+ "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -264,9 +264,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/Mocks/ThrowOnFlushStream.cs b/tests/SharpCompress.Test/Mocks/ThrowOnFlushStream.cs
new file mode 100644
index 00000000..2cf1a84c
--- /dev/null
+++ b/tests/SharpCompress.Test/Mocks/ThrowOnFlushStream.cs
@@ -0,0 +1,73 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Test.Mocks;
+
+///
+/// A stream wrapper that throws NotSupportedException on Flush() calls.
+/// This is used to test that archive iteration handles streams that don't support flushing.
+///
+public class ThrowOnFlushStream : Stream
+{
+ private readonly Stream inner;
+
+ public ThrowOnFlushStream(Stream inner)
+ {
+ this.inner = inner;
+ }
+
+ public override bool CanRead => inner.CanRead;
+
+ public override bool CanSeek => false;
+
+ public override bool CanWrite => false;
+
+ public override long Length => throw new NotSupportedException();
+
+ public override long Position
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+
+ public override void Flush() => throw new NotSupportedException("Flush not supported");
+
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ throw new NotSupportedException("FlushAsync not supported");
+
+ public override int Read(byte[] buffer, int offset, int count) =>
+ inner.Read(buffer, offset, count);
+
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ ) => inner.ReadAsync(buffer, offset, count, cancellationToken);
+
+#if !NETFRAMEWORK && !NETSTANDARD2_0
+ public override ValueTask ReadAsync(
+ Memory buffer,
+ CancellationToken cancellationToken = default
+ ) => inner.ReadAsync(buffer, cancellationToken);
+#endif
+
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException();
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ inner.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs
index 30f1f16f..ab9b3afa 100644
--- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs
+++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs
@@ -490,4 +490,52 @@ public class ZipReaderTests : ReaderTests
}
}
}
+
+ [Fact]
+ public void Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate()
+ {
+ // Regression test: since 0.41.0, archive iteration would silently break
+ // when the input stream throws NotSupportedException in Flush().
+ // Only the first entry would be returned, then iteration would stop without exception.
+ var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip");
+ using var fileStream = File.OpenRead(path);
+ using Stream stream = new ThrowOnFlushStream(fileStream);
+ using var reader = ReaderFactory.OpenReader(stream);
+
+ var count = 0;
+ while (reader.MoveToNextEntry())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ count++;
+ }
+ }
+
+ // 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 Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA()
+ {
+ // Regression test: since 0.41.0, archive iteration would silently break
+ // when the input stream throws NotSupportedException in Flush().
+ // Only the first entry would be returned, then iteration would stop without exception.
+ var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip");
+ using var fileStream = File.OpenRead(path);
+ using Stream stream = new ThrowOnFlushStream(fileStream);
+ using var reader = ReaderFactory.OpenReader(stream);
+
+ var count = 0;
+ while (reader.MoveToNextEntry())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ count++;
+ }
+ }
+
+ // Should iterate through all entries, not just the first one
+ Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
+ }
}
From f2bb81d6113d066d44ac2078320836d076fc9d36 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 23 Jan 2026 16:42:44 +0000
Subject: [PATCH 3/4] Add async versions of archive iteration regression tests
- Added Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate_Async
- Added Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA_Async
- Both async tests mirror the sync versions and pass successfully
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
---
.../Zip/ZipReaderAsyncTests.cs | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs
index c4c5bb3a..aaa345ad 100644
--- a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs
+++ b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs
@@ -337,4 +337,52 @@ public class ZipReaderAsyncTests : ReaderTests
}
}
}
+
+ [Fact]
+ public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate_Async()
+ {
+ // Regression test: since 0.41.0, archive iteration would silently break
+ // when the input stream throws NotSupportedException in Flush().
+ // Only the first entry would be returned, then iteration would stop without exception.
+ var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip");
+ using var fileStream = File.OpenRead(path);
+ using Stream stream = new ThrowOnFlushStream(fileStream);
+ await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));
+
+ var count = 0;
+ while (await reader.MoveToNextEntryAsync())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ count++;
+ }
+ }
+
+ // Should iterate through all entries, not just the first one
+ Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
+ }
+
+ [Fact]
+ public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA_Async()
+ {
+ // Regression test: since 0.41.0, archive iteration would silently break
+ // when the input stream throws NotSupportedException in Flush().
+ // Only the first entry would be returned, then iteration would stop without exception.
+ var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip");
+ using var fileStream = File.OpenRead(path);
+ using Stream stream = new ThrowOnFlushStream(fileStream);
+ await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));
+
+ var count = 0;
+ while (await reader.MoveToNextEntryAsync())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ count++;
+ }
+ }
+
+ // Should iterate through all entries, not just the first one
+ Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
+ }
}
From f1102dc98086b74be9e6ce0a781ab6b7839575c2 Mon Sep 17 00:00:00 2001
From: Adam Hathcock
Date: Sat, 24 Jan 2026 10:01:49 +0000
Subject: [PATCH 4/4] Undoing
https://github.com/adamhathcock/sharpcompress/pull/1151
---
src/SharpCompress/Common/EntryStream.cs | 36 +++----------------------
1 file changed, 4 insertions(+), 32 deletions(-)
diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs
index d265be00..e4de4ca9 100644
--- a/src/SharpCompress/Common/EntryStream.cs
+++ b/src/SharpCompress/Common/EntryStream.cs
@@ -79,25 +79,11 @@ public class EntryStream : Stream, IStreamStack
{
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{
- try
- {
- deflateStream.Flush(); //Deflate over reads. Knock it back
- }
- catch (NotSupportedException)
- {
- // Ignore: underlying stream does not support required operations for Flush
- }
+ deflateStream.Flush(); //Deflate over reads. Knock it back
}
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
{
- try
- {
- lzmaStream.Flush(); //Lzma over reads. Knock it back
- }
- catch (NotSupportedException)
- {
- // Ignore: underlying stream does not support required operations for Flush
- }
+ lzmaStream.Flush(); //Lzma over reads. Knock it back
}
}
#if DEBUG_STREAMS
@@ -125,25 +111,11 @@ public class EntryStream : Stream, IStreamStack
{
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{
- try
- {
- await deflateStream.FlushAsync().ConfigureAwait(false);
- }
- catch (NotSupportedException)
- {
- // Ignore: underlying stream does not support required operations for Flush
- }
+ await deflateStream.FlushAsync().ConfigureAwait(false);
}
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
{
- try
- {
- await lzmaStream.FlushAsync().ConfigureAwait(false);
- }
- catch (NotSupportedException)
- {
- // Ignore: underlying stream does not support required operations for Flush
- }
+ await lzmaStream.FlushAsync().ConfigureAwait(false);
}
}
#if DEBUG_STREAMS