fix deflate rewinding?

This commit is contained in:
Adam Hathcock
2026-02-03 17:07:39 +00:00
parent 08d64ee8a1
commit 1b0ec2410d
7 changed files with 37 additions and 81 deletions

View File

@@ -2,15 +2,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory
internal partial class StreamingZipHeaderFactory : ZipHeaderFactory
{
private IEnumerable<ZipEntry>? _entries;
@@ -134,13 +131,8 @@ internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory
{
_lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize;
// For SeekableRewindableStream, seek back to just after the local header signature.
// Plain RewindableStream cannot seek to arbitrary positions, so we skip this.
// 4 = First 4 bytes of the entry header (i.e. 50 4B 03 04)
if (rewindableStream is SeekableRewindableStream)
{
rewindableStream.Position = pos.Value + 4;
}
rewindableStream.Position = pos.Value + 4;
}
}
else
@@ -181,38 +173,14 @@ internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory
} // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor )
else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor))
{
// Peek ahead to check if next data is a header or file data.
// For SeekableRewindableStream, use direct position save/restore to avoid
// interfering with any recording state set by the caller (e.g., ReaderFactory).
// Plain RewindableStream can use StartRecording/Rewind safely since it was
// created fresh by EnsureSeekable and isn't shared with the caller.
if (rewindableStream is SeekableRewindableStream)
var nextHeaderBytes = reader.ReadUInt32();
if (rewindableStream is IStreamStack ss)
{
var savedPosition = rewindableStream.Position;
var nextHeaderBytes = reader.ReadUInt32();
rewindableStream.Position = savedPosition;
header.HasData = !IsHeader(nextHeaderBytes);
}
else
{
// Only start recording if not already recording.
// The stream may already be recording if it was created by ReaderFactory.
if (!rewindableStream.IsRecording)
{
rewindableStream.StartRecording();
var nextHeaderBytes = reader.ReadUInt32();
rewindableStream.Rewind(true);
header.HasData = !IsHeader(nextHeaderBytes);
}
else
{
// If already recording, save position and restore after peek
var savedPosition = rewindableStream.Position;
var nextHeaderBytes = reader.ReadUInt32();
rewindableStream.Position = savedPosition;
header.HasData = !IsHeader(nextHeaderBytes);
}
ss.Rewind(sizeof(uint));
}
// Check if next data is PostDataDescriptor, streamed file with 0 length
header.HasData = !IsHeader(nextHeaderBytes);
}
else // We are not streaming and compressed size is 0, we have no data
{

View File

@@ -64,10 +64,6 @@ public partial class DeflateStream : Stream, IStreamStack
leaveOpen,
forceEncoding
);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(DeflateStream));
#endif
}
#region Zlib properties

View File

@@ -593,25 +593,16 @@ internal class ZlibBaseStream : Stream, IStreamStack
{
_stream.Flush();
}
else if (_streamMode == StreamMode.Reader && z.AvailableBytesIn > 0)
else if (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) { }
}
stack.Rewind(z.AvailableBytesIn);
}
z.AvailableBytesIn = 0;
}
z.AvailableBytesIn = 0;
}
public override async Task FlushAsync(CancellationToken cancellationToken)
@@ -623,25 +614,16 @@ internal class ZlibBaseStream : Stream, IStreamStack
{
await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
else if (_streamMode == StreamMode.Reader && z.AvailableBytesIn > 0)
else if (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) { }
}
stack.Rewind(z.AvailableBytesIn);
}
z.AvailableBytesIn = 0;
}
z.AvailableBytesIn = 0;
}
public override Int64 Seek(Int64 offset, SeekOrigin origin) =>
@@ -1016,7 +998,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress)
{
//rewind the buffer
//unused: ((IStreamStack)this).Rewind(z.AvailableBytesIn);
this.Rewind(z.AvailableBytesIn);
z.AvailableBytesIn = 0;
}
@@ -1208,7 +1190,7 @@ internal class ZlibBaseStream : Stream, IStreamStack
if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress)
{
//rewind the buffer
//unused: ((IStreamStack)this).Rewind(z.AvailableBytesIn);
this.Rewind(z.AvailableBytesIn);
z.AvailableBytesIn = 0;
}

View File

@@ -8,10 +8,6 @@ namespace SharpCompress.IO;
internal partial class BufferedSubStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => _stream;
private readonly Stream _stream;
@@ -19,18 +15,12 @@ internal partial class BufferedSubStream : Stream, IStreamStack
public BufferedSubStream(Stream stream, long origin, long bytesToRead)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
#if DEBUG_STREAMS
this.DebugConstruct(typeof(BufferedSubStream));
#endif
this.origin = origin;
this.BytesLeftToRead = bytesToRead;
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(BufferedSubStream));
#endif
if (_isDisposed)
{
return;

View File

@@ -49,5 +49,22 @@ namespace SharpCompress.IO
}
return current;
}
internal static void Rewind(this IStreamStack stream, int count)
{
IStreamStack? buffStream = null;
IStreamStack? current = stream;
while (buffStream == null && current != null)
{
if (current is RewindableStream rewindableStream)
{
buffStream = current;
rewindableStream.Position -= Math.Min(rewindableStream.Position, count);
}
current = current.BaseStream() as IStreamStack;
}
}
}
}

View File

@@ -3,8 +3,10 @@ using System.IO;
namespace SharpCompress.IO;
internal partial class RewindableStream : Stream
internal partial class RewindableStream : Stream, IStreamStack
{
public virtual Stream BaseStream() => stream;
private readonly Stream stream;
private MemoryStream bufferStream = new MemoryStream();
private bool isRewound;

View File

@@ -5,6 +5,7 @@ namespace SharpCompress.IO;
internal sealed partial class SeekableRewindableStream : RewindableStream
{
public override Stream BaseStream() => _underlyingStream;
private readonly Stream _underlyingStream;
private long? _recordedPosition;