diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs index 5c9cc3b9..f874bd65 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -15,6 +15,24 @@ public partial class TarReader : IReaderOpenable #endif { + private static SharpCompressStream CreateReaderProbeStream(Stream stream, ReaderOptions options) + { + if (stream is SharpCompressStream { IsPassthrough: false }) + { + // Keep the caller's recording scope and logical position intact, including + // frozen recordings whose buffered bytes have not been replayed yet. + return new SeekableSharpCompressStream(stream); + } + + return SharpCompressStream.Create( + stream, + bufferSize: Math.Max( + options.RewindableBufferSize ?? 0, + TarWrapper.MaximumRewindBufferSize + ) + ); + } + private static Stream CreateProbeDecompressionStream( Stream stream, CompressionType compressionType, @@ -101,20 +119,11 @@ public partial class TarReader stream = SharpCompressStream.CreateNonDisposing(stream); readerOptions = readerOptions with { LeaveStreamOpen = false }; } - var sharpCompressStream = SharpCompressStream.Create( - stream, - bufferSize: Math.Max( - readerOptions.RewindableBufferSize ?? 0, - TarWrapper.MaximumRewindBufferSize - ) - ); + var sharpCompressStream = CreateReaderProbeStream(stream, readerOptions); var transferred = false; try { - if (!sharpCompressStream.IsRecording) - { - sharpCompressStream.StartRecording(); - } + sharpCompressStream.StartRecording(); long pos = sharpCompressStream.Position; foreach (var wrapper in TarWrapper.Wrappers) { @@ -235,17 +244,8 @@ public partial class TarReader stream = SharpCompressStream.CreateNonDisposing(stream); readerOptions = readerOptions with { LeaveStreamOpen = false }; } - var sharpCompressStream = SharpCompressStream.Create( - stream, - bufferSize: Math.Max( - readerOptions.RewindableBufferSize ?? 0, - TarWrapper.MaximumRewindBufferSize - ) - ); - if (!sharpCompressStream.IsRecording) - { - sharpCompressStream.StartRecording(); - } + var sharpCompressStream = CreateReaderProbeStream(stream, readerOptions); + sharpCompressStream.StartRecording(); long pos = sharpCompressStream.Position; foreach (var wrapper in TarWrapper.Wrappers) { diff --git a/tests/SharpCompress.Test/Streams/TarLifetimeTests.cs b/tests/SharpCompress.Test/Streams/TarLifetimeTests.cs index d99d8555..46b74fcb 100644 --- a/tests/SharpCompress.Test/Streams/TarLifetimeTests.cs +++ b/tests/SharpCompress.Test/Streams/TarLifetimeTests.cs @@ -46,6 +46,72 @@ public class TarLifetimeTests : TestBase } } + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + public async Task Reader_Preserves_Frozen_Recording( + bool async, + bool stopRecording, + bool leaveOpen + ) + { + using var archive = new MemoryStream(); + using ( + var writer = WriterFactory.OpenWriter( + archive, + ArchiveType.Tar, + new WriterOptions(CompressionType.None) { LeaveStreamOpen = true } + ) + ) + { + writer.Write("empty.txt", Stream.Null); + } + archive.Position = 0; + using var input = new ForwardOnlyStream(archive); + using var buffered = SharpCompressStream.Create(input, TarWrapper.MaximumRewindBufferSize); + buffered.StartRecording(); + var header = new byte[512]; + Assert.Equal(header.Length, buffered.Read(header, 0, header.Length)); + if (stopRecording) + { + buffered.StopRecording(); + } + else + { + buffered.Rewind(true); + } + Assert.Equal(0, buffered.Position); + var options = new ReaderOptions { LeaveStreamOpen = leaveOpen }; + if (async) + { + await using var reader = await TarReader.OpenAsyncReader( + buffered, + options, + CancellationToken.None + ); + Assert.True(await reader.MoveToNextEntryAsync(CancellationToken.None)); + Assert.Equal("empty.txt", reader.Entry.Key); + Assert.Equal(0, reader.Entry.Size); + Assert.False(await reader.MoveToNextEntryAsync(CancellationToken.None)); + } + else + { + using var reader = TarReader.OpenReader(buffered, options); + Assert.True(reader.MoveToNextEntry()); + Assert.Equal("empty.txt", reader.Entry.Key); + Assert.Equal(0, reader.Entry.Size); + Assert.False(reader.MoveToNextEntry()); + } + Assert.Equal(leaveOpen, input.CanRead); + Assert.Equal(leaveOpen, buffered.CanRead); + } + [Theory] [InlineData("invalid", false)] [InlineData("invalid", true)]