mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Fix TAR probe cleanup and preserve active recording sessions
This commit is contained in:
@@ -182,10 +182,10 @@ public class TarFactory
|
||||
if (await wrapper.IsMatchAsync(stream, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#endif
|
||||
stream,
|
||||
wrapper.CompressionType,
|
||||
@@ -397,10 +397,10 @@ public class TarFactory
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
buffered.Dispose();
|
||||
#else
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await buffered.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
buffered.Dispose();
|
||||
#endif
|
||||
throw;
|
||||
}
|
||||
@@ -484,10 +484,10 @@ public class TarFactory
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
using var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
#endif
|
||||
sharpCompressStream,
|
||||
wrapper.CompressionType,
|
||||
@@ -522,10 +522,10 @@ public class TarFactory
|
||||
{
|
||||
if (!transferred)
|
||||
{
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
sharpCompressStream.Dispose();
|
||||
#else
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await sharpCompressStream.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
sharpCompressStream.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,59 +108,91 @@ public partial class TarReader
|
||||
TarWrapper.MaximumRewindBufferSize
|
||||
)
|
||||
);
|
||||
if (!sharpCompressStream.IsRecording)
|
||||
var transferred = false;
|
||||
try
|
||||
{
|
||||
sharpCompressStream.StartRecording();
|
||||
}
|
||||
long pos = sharpCompressStream.Position;
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
sharpCompressStream.Position = pos;
|
||||
if (
|
||||
!await wrapper
|
||||
.IsMatchAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
if (!sharpCompressStream.IsRecording)
|
||||
{
|
||||
continue;
|
||||
sharpCompressStream.StartRecording();
|
||||
}
|
||||
|
||||
sharpCompressStream.Position = pos;
|
||||
if (wrapper.CompressionType == CompressionType.LZip)
|
||||
{
|
||||
sharpCompressStream = await TarFactory
|
||||
.BufferLZipHeaderAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
pos = sharpCompressStream.Position;
|
||||
}
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
using var testStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
await using var testStream = await CreateProbeDecompressionStreamAsync(
|
||||
#endif
|
||||
sharpCompressStream,
|
||||
wrapper.CompressionType,
|
||||
readerOptions.Providers,
|
||||
readerOptions,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
if (
|
||||
await TarArchive.IsTarFileAsync(testStream, cancellationToken).ConfigureAwait(false)
|
||||
)
|
||||
long pos = sharpCompressStream.Position;
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
sharpCompressStream.Position = pos;
|
||||
return new TarReader(sharpCompressStream, readerOptions, wrapper.CompressionType);
|
||||
if (
|
||||
!await wrapper
|
||||
.IsMatchAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sharpCompressStream.Position = pos;
|
||||
if (wrapper.CompressionType == CompressionType.LZip)
|
||||
{
|
||||
sharpCompressStream = await TarFactory
|
||||
.BufferLZipHeaderAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
pos = sharpCompressStream.Position;
|
||||
}
|
||||
bool isTar;
|
||||
{
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await using var testStream = await CreateProbeDecompressionStreamAsync(
|
||||
#else
|
||||
using var testStream = await CreateProbeDecompressionStreamAsync(
|
||||
#endif
|
||||
sharpCompressStream,
|
||||
wrapper.CompressionType,
|
||||
readerOptions.Providers,
|
||||
readerOptions,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
isTar = await TarArchive
|
||||
.IsTarFileAsync(testStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
if (isTar)
|
||||
{
|
||||
sharpCompressStream.Position = pos;
|
||||
var reader = new TarReader(
|
||||
sharpCompressStream,
|
||||
readerOptions,
|
||||
wrapper.CompressionType
|
||||
);
|
||||
transferred = true;
|
||||
return reader;
|
||||
}
|
||||
|
||||
if (wrapper.CompressionType != CompressionType.None)
|
||||
{
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (wrapper.CompressionType != CompressionType.None)
|
||||
sharpCompressStream.Position = pos;
|
||||
var fallbackReader = new TarReader(
|
||||
sharpCompressStream,
|
||||
readerOptions,
|
||||
CompressionType.None
|
||||
);
|
||||
transferred = true;
|
||||
return fallbackReader;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The reader owns the probe wrappers only after a successful open.
|
||||
if (!transferred)
|
||||
{
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
#if STREAM_DISPOSEASYNC
|
||||
await sharpCompressStream.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
sharpCompressStream.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
sharpCompressStream.Position = pos;
|
||||
return new TarReader(sharpCompressStream, readerOptions, CompressionType.None);
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -210,7 +242,10 @@ public partial class TarReader
|
||||
TarWrapper.MaximumRewindBufferSize
|
||||
)
|
||||
);
|
||||
sharpCompressStream.StartRecording();
|
||||
if (!sharpCompressStream.IsRecording)
|
||||
{
|
||||
sharpCompressStream.StartRecording();
|
||||
}
|
||||
long pos = sharpCompressStream.Position;
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
|
||||
@@ -68,13 +68,13 @@ public partial class TarReader : AbstractReader<TarEntry, TarVolume>
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if LEGACY_DOTNET && !NETSTANDARD2_1
|
||||
ownedSource?.Dispose();
|
||||
#else
|
||||
#if STREAM_DISPOSEASYNC
|
||||
if (ownedSource is not null)
|
||||
{
|
||||
await ownedSource.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
#else
|
||||
ownedSource?.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Providers;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Test.Mocks;
|
||||
@@ -18,6 +20,117 @@ namespace SharpCompress.Test.Streams;
|
||||
|
||||
public class TarLifetimeTests : TestBase
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task Reader_Preserves_Existing_Recording_Session(bool async)
|
||||
{
|
||||
using var file = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"));
|
||||
using var input = new ForwardOnlyStream(file);
|
||||
using var buffered = SharpCompressStream.Create(input, TarWrapper.MaximumRewindBufferSize);
|
||||
buffered.StartRecording();
|
||||
var options = new ReaderOptions { LeaveStreamOpen = false };
|
||||
if (async)
|
||||
{
|
||||
await using var reader = await TarReader.OpenAsyncReader(
|
||||
buffered,
|
||||
options,
|
||||
CancellationToken.None
|
||||
);
|
||||
Assert.True(await reader.MoveToNextEntryAsync(CancellationToken.None));
|
||||
}
|
||||
else
|
||||
{
|
||||
using var reader = TarReader.OpenReader(buffered, options);
|
||||
Assert.True(reader.MoveToNextEntry());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid", false)]
|
||||
[InlineData("invalid", true)]
|
||||
[InlineData("throw", false)]
|
||||
[InlineData("throw", true)]
|
||||
[InlineData("cancel", false)]
|
||||
[InlineData("cancel", true)]
|
||||
public async Task Failed_LZip_Probe_Releases_Buffer(string failure, bool leaveOpen)
|
||||
{
|
||||
using var source = new MemoryStream();
|
||||
var content = new byte[1024];
|
||||
new Random(42).NextBytes(content);
|
||||
using (
|
||||
var lzip = LZipStream.Create(
|
||||
SharpCompressStream.CreateNonDisposing(source),
|
||||
SharpCompress.Compressors.CompressionMode.Compress
|
||||
)
|
||||
)
|
||||
{
|
||||
lzip.Write(content, 0, content.Length);
|
||||
}
|
||||
source.Position = 0;
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
var rings = new List<RingBuffer>();
|
||||
var provider = new ProbeLZipProvider(stream =>
|
||||
{
|
||||
rings.AddRange(GetRings(stream));
|
||||
if (failure == "throw")
|
||||
{
|
||||
throw new IOException("Probe creation failed.");
|
||||
}
|
||||
if (failure == "cancel")
|
||||
{
|
||||
cancellation.Cancel();
|
||||
cancellation.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
return LZipStream.Create(stream, SharpCompress.Compressors.CompressionMode.Decompress);
|
||||
});
|
||||
var options = new ReaderOptions
|
||||
{
|
||||
LeaveStreamOpen = leaveOpen,
|
||||
Providers = CompressionProviderRegistry.Default.With(provider),
|
||||
};
|
||||
var exception = await Record.ExceptionAsync(async () =>
|
||||
{
|
||||
await using var reader = await TarReader.OpenAsyncReader(
|
||||
source,
|
||||
options,
|
||||
cancellation.Token
|
||||
);
|
||||
});
|
||||
if (failure == "invalid")
|
||||
{
|
||||
Assert.IsType<InvalidFormatException>(exception);
|
||||
}
|
||||
else if (failure == "throw")
|
||||
{
|
||||
Assert.IsType<IOException>(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsAssignableFrom<OperationCanceledException>(exception);
|
||||
}
|
||||
Assert.NotEmpty(rings);
|
||||
var buffer = typeof(RingBuffer).GetField(
|
||||
"_buffer",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic
|
||||
)!;
|
||||
Assert.All(rings, ring => Assert.Null(buffer.GetValue(ring)));
|
||||
Assert.Equal(leaveOpen, source.CanRead);
|
||||
}
|
||||
|
||||
private sealed class ProbeLZipProvider(Func<Stream, Stream> createStream)
|
||||
: CompressionProviderBase
|
||||
{
|
||||
public override CompressionType CompressionType => CompressionType.LZip;
|
||||
public override bool SupportsCompression => false;
|
||||
public override bool SupportsDecompression => true;
|
||||
|
||||
public override Stream CreateCompressStream(Stream destination, int compressionLevel) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override Stream CreateDecompressStream(Stream source) => createStream(source);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReaderCases()
|
||||
{
|
||||
foreach (
|
||||
|
||||
Reference in New Issue
Block a user