reintroduce RewindableStream stream. SharpCompressStream does too much

This commit is contained in:
Adam Hathcock 2026-01-28 16:33:19 +00:00
parent 24b4ef8780
commit c770bc4788
8 changed files with 208 additions and 39 deletions

View file

@ -35,8 +35,14 @@ internal partial class ZStandardStream : DecompressionStream, IStreamStack
internal static bool IsZStandard(Stream stream)
{
var br = new BinaryReader(stream);
var magic = br.ReadUInt32();
var buffer = new byte[4];
var bytesRead = stream.Read(buffer, 0, 4);
if (bytesRead < 4)
{
return false;
}
var magic = BitConverter.ToUInt32(buffer, 0);
if (ZstandardConstants.MAGIC != magic)
{
return false;

View file

@ -36,8 +36,11 @@ namespace SharpCompress.Factories
var buffer = ArrayPool<byte>.Shared.Rent(2);
try
{
stream.ReadExact(buffer, 0, 2);
return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have
if (stream.ReadFully(buffer.AsSpan(0, 2)))
{
return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have
}
return false;
}
finally
{

View file

@ -75,7 +75,7 @@ public abstract class Factory : IFactory
/// <param name="reader"></param>
/// <returns></returns>
internal virtual bool TryOpenReader(
SharpCompressStream stream,
RewindableStream stream,
ReaderOptions options,
out IReader? reader
)
@ -84,16 +84,15 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
long pos = ((IStreamStack)stream).GetPosition();
stream.Rewind();
if (IsArchive(stream, options.Password))
{
((IStreamStack)stream).StackSeek(pos);
stream.Rewind();
reader = readerFactory.OpenReader(stream, options);
return true;
}
}
stream.Rewind();
return false;
}
}

View file

@ -107,31 +107,28 @@ public class GZipFactory
/// <inheritdoc/>
internal override bool TryOpenReader(
SharpCompressStream rewindableStream,
RewindableStream rewindableStream,
ReaderOptions options,
out IReader? reader
)
{
reader = null;
long pos = ((IStreamStack)rewindableStream).GetPosition();
if (GZipArchive.IsGZipFile(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
rewindableStream.Rewind();
var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
rewindableStream.Rewind();
reader = new TarReader(rewindableStream, options, CompressionType.GZip);
return true;
}
((IStreamStack)rewindableStream).StackSeek(pos);
rewindableStream.Rewind();
reader = OpenReader(rewindableStream, options);
return true;
}
rewindableStream.Rewind();
return false;
}

View file

@ -94,7 +94,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
#region reader
internal override bool TryOpenReader(
SharpCompressStream rewindableStream,
RewindableStream rewindableStream,
ReaderOptions options,
out IReader? reader
)

View file

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -47,18 +49,18 @@ public class TarFactory
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
{
var rewindableStream = new SharpCompressStream(stream);
long pos = rewindableStream.GetPosition();
var rewindableStream = new RewindableStream(stream);
rewindableStream.StartRecording();
foreach (var wrapper in TarWrapper.Wrappers)
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
if (wrapper.IsMatch(rewindableStream))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
var decompressedStream = wrapper.CreateStream(rewindableStream);
if (TarArchive.IsTarFile(decompressedStream))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
return true;
}
}
@ -74,18 +76,18 @@ public class TarFactory
CancellationToken cancellationToken = default
)
{
var rewindableStream = new SharpCompressStream(stream);
long pos = rewindableStream.GetPosition();
var rewindableStream = new RewindableStream(stream);
rewindableStream.StartRecording();
foreach (var wrapper in TarWrapper.Wrappers)
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
var decompressedStream = wrapper.CreateStream(rewindableStream);
if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
return true;
}
}
@ -155,18 +157,18 @@ public class TarFactory
public IReader OpenReader(Stream stream, ReaderOptions? options)
{
options ??= new ReaderOptions();
var rewindableStream = new SharpCompressStream(stream);
long pos = rewindableStream.GetPosition();
var rewindableStream = new RewindableStream(stream);
rewindableStream.StartRecording();
foreach (var wrapper in TarWrapper.Wrappers)
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
if (wrapper.IsMatch(rewindableStream))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
var decompressedStream = wrapper.CreateStream(rewindableStream);
if (TarArchive.IsTarFile(decompressedStream))
{
rewindableStream.StackSeek(pos);
rewindableStream.Rewind();
return new TarReader(rewindableStream, options, wrapper.CompressionType);
}
}
@ -184,7 +186,7 @@ public class TarFactory
cancellationToken.ThrowIfCancellationRequested();
options ??= new ReaderOptions();
var rewindableStream = new SharpCompressStream(stream);
long pos = rewindableStream.GetPosition();
var pos = rewindableStream.GetPosition();
foreach (var wrapper in TarWrapper.Wrappers)
{
rewindableStream.StackSeek(pos);

View file

@ -0,0 +1,163 @@
using System;
using System.IO;
namespace SharpCompress.IO
{
internal partial class RewindableStream : Stream
{
private readonly Stream stream;
private MemoryStream bufferStream = new MemoryStream();
private bool isRewound;
private bool isDisposed;
public RewindableStream(Stream stream)
{
this.stream = stream;
}
internal bool IsRecording { get; private set; }
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
isDisposed = true;
base.Dispose(disposing);
if (disposing)
{
stream.Dispose();
}
}
public void Rewind(bool stopRecording = false)
{
isRewound = true;
IsRecording = !stopRecording;
bufferStream.Position = 0;
}
public void Rewind(MemoryStream buffer)
{
if (bufferStream.Position >= buffer.Length)
{
bufferStream.Position -= buffer.Length;
}
else
{
bufferStream.TransferTo(buffer, buffer.Length - bufferStream.Position);
//create new memorystream to allow proper resizing as memorystream could be a user provided buffer
//https://github.com/adamhathcock/sharpcompress/issues/306
bufferStream = new MemoryStream();
buffer.Position = 0;
buffer.TransferTo(bufferStream, buffer.Length);
bufferStream.Position = 0;
}
isRewound = true;
}
public void StartRecording()
{
//if (isRewound && bufferStream.Position != 0)
// throw new System.NotImplementedException();
if (bufferStream.Position != 0)
{
byte[] data = bufferStream.ToArray();
long position = bufferStream.Position;
bufferStream.SetLength(0);
bufferStream.Write(data, (int)position, data.Length - (int)position);
bufferStream.Position = 0;
}
IsRecording = true;
}
public override bool CanRead => true;
public override bool CanSeek => stream.CanSeek;
public override bool CanWrite => false;
public override void Flush()
{
throw new NotSupportedException();
}
public override long Length => throw new NotSupportedException();
public override long Position
{
get { return stream.Position + bufferStream.Position - bufferStream.Length; }
set
{
if (!isRewound)
{
stream.Position = value;
}
else if (value < stream.Position - bufferStream.Length || value >= stream.Position)
{
stream.Position = value;
isRewound = false;
bufferStream.SetLength(0);
}
else
{
bufferStream.Position = value - stream.Position + bufferStream.Length;
}
}
}
public override int Read(byte[] buffer, int offset, int count)
{
//don't actually read if we don't really want to read anything
//currently a network stream bug on Windows for .NET Core
if (count == 0)
{
return 0;
}
int read;
if (isRewound && bufferStream.Position != bufferStream.Length)
{
read = bufferStream.Read(buffer, offset, count);
if (read < count)
{
int tempRead = stream.Read(buffer, offset + read, count - read);
if (IsRecording)
{
bufferStream.Write(buffer, offset + read, tempRead);
}
read += tempRead;
}
if (bufferStream.Position == bufferStream.Length && !IsRecording)
{
isRewound = false;
bufferStream.SetLength(0);
}
return read;
}
read = stream.Read(buffer, offset, count);
if (IsRecording)
{
bufferStream.Write(buffer, offset, read);
}
return read;
}
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();
}
}
}

View file

@ -34,9 +34,8 @@ public static partial class ReaderFactory
stream.NotNull(nameof(stream));
options ??= new ReaderOptions() { LeaveStreamOpen = false };
var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
long pos = bStream.GetPosition();
var bStream = new RewindableStream(stream);
bStream.StartRecording();
var factories = Factories.Factory.Factories.OfType<Factories.Factory>();
@ -53,9 +52,9 @@ public static partial class ReaderFactory
&& reader != null
)
{
bStream.Rewind();
return reader;
}
bStream.StackSeek(pos);
}
foreach (var factory in factories)
@ -64,7 +63,7 @@ public static partial class ReaderFactory
{
continue; // Already tested above
}
bStream.StackSeek(pos);
bStream.Rewind();
if (factory.TryOpenReader(bStream, options, out var reader) && reader != null)
{
return reader;