From 62b8fc92d1829fe71c58d3407c1728fe66816bc3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 1 Feb 2026 08:47:17 +0000 Subject: [PATCH] not sure I like this fix --- src/SharpCompress/Common/EntryStream.cs | 10 ++++- .../Compressors/Deflate/DeflateStream.cs | 5 ++- .../Compressors/Deflate/ZlibBaseStream.cs | 45 ++++++++++++++++--- .../Compressors/LZMA/LzmaStream.cs | 5 ++- src/SharpCompress/IO/IStreamStack.cs | 35 +++++++++++++++ src/SharpCompress/IO/RewindableStream.cs | 42 +++++++++++------ 6 files changed, 119 insertions(+), 23 deletions(-) diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index 6b3be5c4..afaec647 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -48,11 +48,17 @@ public partial class EntryStream : Stream //Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised if (_stream is IStreamStack ss) { - if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream) + if ( + ss.GetStream() + is SharpCompress.Compressors.Deflate.DeflateStream deflateStream + ) { deflateStream.Flush(); //Deflate over reads. Knock it back } - else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream) + else if ( + ss.GetStream() + is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream + ) { lzmaStream.Flush(); //Lzma over reads. Knock it back } diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs index 7fd65184..1d79a3d8 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -29,10 +29,11 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; -public partial class DeflateStream : Stream +public partial class DeflateStream : Stream, IStreamStack { private readonly ZlibBaseStream _baseStream; private bool _disposed; @@ -269,6 +270,8 @@ public partial class DeflateStream : Stream } } + Stream IStreamStack.BaseStream() => _baseStream; + /// /// Flush the stream. /// diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index 3fa7538d..b748f741 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -34,6 +34,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common.Tar.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; @@ -44,8 +45,10 @@ internal enum ZlibStreamFlavor GZIP = 1952, } -internal class ZlibBaseStream : Stream +internal class ZlibBaseStream : Stream, IStreamStack { + Stream IStreamStack.BaseStream() => _stream; + protected internal ZlibCodec _z; // deferred init... new ZlibCodec(); protected internal StreamMode _streamMode = StreamMode.Undefined; @@ -590,8 +593,24 @@ internal class ZlibBaseStream : Stream { _stream.Flush(); } - //rewind the buffer - //unused: ((IStreamStack)this).Rewind(z.AvailableBytesIn); + else if (_streamMode == StreamMode.Reader && z.AvailableBytesIn > 0) + { + // Rewind the underlying stream by the number of unconsumed bytes in the buffer + // This handles the case where the decompressor over-read past the end of the entry + if (_stream is IStreamStack stack) + { + var root = stack.GetRootStream(); + if (root.CanSeek) + { + // Try to seek backward on the root stream; ignore if not supported + try + { + root.Seek(-z.AvailableBytesIn, SeekOrigin.Current); + } + catch (NotSupportedException) { } + } + } + } z.AvailableBytesIn = 0; } @@ -604,8 +623,24 @@ internal class ZlibBaseStream : Stream { await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); } - //rewind the buffer - //unused: ((IStreamStack)this).Rewind(z.AvailableBytesIn); + else if (_streamMode == StreamMode.Reader && z.AvailableBytesIn > 0) + { + // Rewind the underlying stream by the number of unconsumed bytes in the buffer + // This handles the case where the decompressor over-read past the end of the entry + if (_stream is IStreamStack stack) + { + var root = stack.GetRootStream(); + if (root.CanSeek) + { + // Try to seek backward on the root stream; ignore if not supported + try + { + root.Seek(-z.AvailableBytesIn, SeekOrigin.Current); + } + catch (NotSupportedException) { } + } + } + } z.AvailableBytesIn = 0; } diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index 42d548d7..123d8e03 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -4,10 +4,11 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Compressors.LZMA.LZ; +using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA; -public partial class LzmaStream : Stream +public partial class LzmaStream : Stream, IStreamStack { private readonly Stream? _inputStream; private readonly long _inputSize; @@ -190,6 +191,8 @@ public partial class LzmaStream : Stream public override void Flush() { } + Stream IStreamStack.BaseStream() => _inputStream!; + protected override void Dispose(bool disposing) { if (_isDisposed) diff --git a/src/SharpCompress/IO/IStreamStack.cs b/src/SharpCompress/IO/IStreamStack.cs index cfc057fa..48742c22 100644 --- a/src/SharpCompress/IO/IStreamStack.cs +++ b/src/SharpCompress/IO/IStreamStack.cs @@ -15,4 +15,39 @@ namespace SharpCompress.IO /// Stream BaseStream(); } + + public static class StreamStackExtensions + { + public static T? GetStream(this IStreamStack stack) + where T : Stream + { + var baseStream = stack.BaseStream(); + if (baseStream is T tStream) + { + return tStream; + } + else if (baseStream is IStreamStack innerStack) + { + return innerStack.GetStream(); + } + else + { + return null; + } + } + + /// + /// Gets the root underlying stream at the bottom of the stack. + /// This is useful for seeking when the intermediate streams don't support it. + /// + public static Stream GetRootStream(this IStreamStack stack) + { + var current = stack.BaseStream(); + while (current is IStreamStack streamStack) + { + current = streamStack.BaseStream(); + } + return current; + } + } } diff --git a/src/SharpCompress/IO/RewindableStream.cs b/src/SharpCompress/IO/RewindableStream.cs index eeea5769..04be4b2e 100644 --- a/src/SharpCompress/IO/RewindableStream.cs +++ b/src/SharpCompress/IO/RewindableStream.cs @@ -105,20 +105,22 @@ internal partial class RewindableStream : Stream } return streamPosition; } - set - { - long bufferStart = streamPosition - bufferStream.Length; - long bufferEnd = streamPosition; + set => SeekToPosition(value); + } - if (value >= bufferStart && value < bufferEnd) - { - isRewound = true; - bufferStream.Position = value - bufferStart; - } - else - { - throw new NotSupportedException("Cannot seek outside buffered region."); - } + private void SeekToPosition(long targetPosition) + { + long bufferStart = streamPosition - bufferStream.Length; + long bufferEnd = streamPosition; + + if (targetPosition >= bufferStart && targetPosition <= bufferEnd) + { + isRewound = true; + bufferStream.Position = targetPosition - bufferStart; + } + else + { + throw new NotSupportedException("Cannot seek outside buffered region."); } } @@ -159,7 +161,19 @@ internal partial class RewindableStream : Stream return read; } - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) + { + long targetPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => Position + offset, + SeekOrigin.End => throw new NotSupportedException("Seeking from end is not supported."), + _ => throw new ArgumentOutOfRangeException(nameof(origin)), + }; + + SeekToPosition(targetPosition); + return targetPosition; + } public override void SetLength(long value) => throw new NotSupportedException();