Skip to content

Commit 4ca1a77

Browse files
authored
Merge pull request #1157 from adamhathcock/adam/1154-release
Merge pull request #1156 from adamhathcock/copilot/fix-sharpcompress-…
2 parents d5a8c37 + 9caf7be commit 4ca1a77

5 files changed

Lines changed: 187 additions & 34 deletions

File tree

src/SharpCompress/Common/EntryStream.cs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,11 @@ protected override void Dispose(bool disposing)
7979
{
8080
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
8181
{
82-
try
83-
{
84-
deflateStream.Flush(); //Deflate over reads. Knock it back
85-
}
86-
catch (NotSupportedException)
87-
{
88-
// Ignore: underlying stream does not support required operations for Flush
89-
}
82+
deflateStream.Flush(); //Deflate over reads. Knock it back
9083
}
9184
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
9285
{
93-
try
94-
{
95-
lzmaStream.Flush(); //Lzma over reads. Knock it back
96-
}
97-
catch (NotSupportedException)
98-
{
99-
// Ignore: underlying stream does not support required operations for Flush
100-
}
86+
lzmaStream.Flush(); //Lzma over reads. Knock it back
10187
}
10288
}
10389
#if DEBUG_STREAMS
@@ -125,25 +111,11 @@ public override async ValueTask DisposeAsync()
125111
{
126112
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
127113
{
128-
try
129-
{
130-
await deflateStream.FlushAsync().ConfigureAwait(false);
131-
}
132-
catch (NotSupportedException)
133-
{
134-
// Ignore: underlying stream does not support required operations for Flush
135-
}
114+
await deflateStream.FlushAsync().ConfigureAwait(false);
136115
}
137116
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
138117
{
139-
try
140-
{
141-
await lzmaStream.FlushAsync().ConfigureAwait(false);
142-
}
143-
catch (NotSupportedException)
144-
{
145-
// Ignore: underlying stream does not support required operations for Flush
146-
}
118+
await lzmaStream.FlushAsync().ConfigureAwait(false);
147119
}
148120
}
149121
#if DEBUG_STREAMS

src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,15 +586,27 @@ public override async ValueTask DisposeAsync()
586586

587587
public override void Flush()
588588
{
589-
_stream.Flush();
589+
// Only flush the underlying stream when in write mode
590+
// Flushing input streams during read operations is not meaningful
591+
// and can cause issues with forward-only/non-seekable streams
592+
if (_streamMode == StreamMode.Writer)
593+
{
594+
_stream.Flush();
595+
}
590596
//rewind the buffer
591597
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
592598
z.AvailableBytesIn = 0;
593599
}
594600

595601
public override async Task FlushAsync(CancellationToken cancellationToken)
596602
{
597-
await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
603+
// Only flush the underlying stream when in write mode
604+
// Flushing input streams during read operations is not meaningful
605+
// and can cause issues with forward-only/non-seekable streams
606+
if (_streamMode == StreamMode.Writer)
607+
{
608+
await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
609+
}
598610
//rewind the buffer
599611
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
600612
z.AvailableBytesIn = 0;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace SharpCompress.Test.Mocks;
7+
8+
/// <summary>
9+
/// A stream wrapper that throws NotSupportedException on Flush() calls.
10+
/// This is used to test that archive iteration handles streams that don't support flushing.
11+
/// </summary>
12+
public class ThrowOnFlushStream : Stream
13+
{
14+
private readonly Stream inner;
15+
16+
public ThrowOnFlushStream(Stream inner)
17+
{
18+
this.inner = inner;
19+
}
20+
21+
public override bool CanRead => inner.CanRead;
22+
23+
public override bool CanSeek => false;
24+
25+
public override bool CanWrite => false;
26+
27+
public override long Length => throw new NotSupportedException();
28+
29+
public override long Position
30+
{
31+
get => throw new NotSupportedException();
32+
set => throw new NotSupportedException();
33+
}
34+
35+
public override void Flush() => throw new NotSupportedException("Flush not supported");
36+
37+
public override Task FlushAsync(CancellationToken cancellationToken) =>
38+
throw new NotSupportedException("FlushAsync not supported");
39+
40+
public override int Read(byte[] buffer, int offset, int count) =>
41+
inner.Read(buffer, offset, count);
42+
43+
public override Task<int> ReadAsync(
44+
byte[] buffer,
45+
int offset,
46+
int count,
47+
CancellationToken cancellationToken
48+
) => inner.ReadAsync(buffer, offset, count, cancellationToken);
49+
50+
#if !NETFRAMEWORK && !NETSTANDARD2_0
51+
public override ValueTask<int> ReadAsync(
52+
Memory<byte> buffer,
53+
CancellationToken cancellationToken = default
54+
) => inner.ReadAsync(buffer, cancellationToken);
55+
#endif
56+
57+
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
58+
59+
public override void SetLength(long value) => throw new NotSupportedException();
60+
61+
public override void Write(byte[] buffer, int offset, int count) =>
62+
throw new NotSupportedException();
63+
64+
protected override void Dispose(bool disposing)
65+
{
66+
if (disposing)
67+
{
68+
inner.Dispose();
69+
}
70+
71+
base.Dispose(disposing);
72+
}
73+
}

tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,52 @@ public async ValueTask EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA
305305
}
306306
}
307307
}
308+
309+
[Fact]
310+
public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate_Async()
311+
{
312+
// Regression test: since 0.41.0, archive iteration would silently break
313+
// when the input stream throws NotSupportedException in Flush().
314+
// Only the first entry would be returned, then iteration would stop without exception.
315+
var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip");
316+
using var fileStream = File.OpenRead(path);
317+
using Stream stream = new ThrowOnFlushStream(fileStream);
318+
using var reader = ReaderFactory.Open(stream);
319+
320+
var count = 0;
321+
while (await reader.MoveToNextEntryAsync())
322+
{
323+
if (!reader.Entry.IsDirectory)
324+
{
325+
count++;
326+
}
327+
}
328+
329+
// Should iterate through all entries, not just the first one
330+
Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
331+
}
332+
333+
[Fact]
334+
public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA_Async()
335+
{
336+
// Regression test: since 0.41.0, archive iteration would silently break
337+
// when the input stream throws NotSupportedException in Flush().
338+
// Only the first entry would be returned, then iteration would stop without exception.
339+
var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip");
340+
using var fileStream = File.OpenRead(path);
341+
using Stream stream = new ThrowOnFlushStream(fileStream);
342+
using var reader = ReaderFactory.Open(stream);
343+
344+
var count = 0;
345+
while (await reader.MoveToNextEntryAsync())
346+
{
347+
if (!reader.Entry.IsDirectory)
348+
{
349+
count++;
350+
}
351+
}
352+
353+
// Should iterate through all entries, not just the first one
354+
Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
355+
}
308356
}

tests/SharpCompress.Test/Zip/ZipReaderTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,4 +482,52 @@ public void EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA()
482482
}
483483
}
484484
}
485+
486+
[Fact]
487+
public void Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate()
488+
{
489+
// Regression test: since 0.41.0, archive iteration would silently break
490+
// when the input stream throws NotSupportedException in Flush().
491+
// Only the first entry would be returned, then iteration would stop without exception.
492+
var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip");
493+
using var fileStream = File.OpenRead(path);
494+
using Stream stream = new ThrowOnFlushStream(fileStream);
495+
using var reader = ReaderFactory.Open(stream);
496+
497+
var count = 0;
498+
while (reader.MoveToNextEntry())
499+
{
500+
if (!reader.Entry.IsDirectory)
501+
{
502+
count++;
503+
}
504+
}
505+
506+
// Should iterate through all entries, not just the first one
507+
Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
508+
}
509+
510+
[Fact]
511+
public void Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA()
512+
{
513+
// Regression test: since 0.41.0, archive iteration would silently break
514+
// when the input stream throws NotSupportedException in Flush().
515+
// Only the first entry would be returned, then iteration would stop without exception.
516+
var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip");
517+
using var fileStream = File.OpenRead(path);
518+
using Stream stream = new ThrowOnFlushStream(fileStream);
519+
using var reader = ReaderFactory.Open(stream);
520+
521+
var count = 0;
522+
while (reader.MoveToNextEntry())
523+
{
524+
if (!reader.Entry.IsDirectory)
525+
{
526+
count++;
527+
}
528+
}
529+
530+
// Should iterate through all entries, not just the first one
531+
Assert.True(count > 1, $"Expected more than 1 entry, but got {count}");
532+
}
485533
}

0 commit comments

Comments
 (0)