diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 3c3caa3f8..8ff13c052 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -58,7 +58,8 @@ public static IWritableArchive OpenArchive( ); var compressionType = TarFactory.GetCompressionType( sourceStream, - sourceStream.ReaderOptions.Providers + sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions ); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); @@ -78,7 +79,8 @@ public static IWritableArchive OpenArchive( ); var compressionType = TarFactory.GetCompressionType( sourceStream, - sourceStream.ReaderOptions.Providers + sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions ); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); @@ -115,6 +117,7 @@ public static async ValueTask> OpenAsync .GetCompressionTypeAsync( sourceStream, sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions, cancellationToken ) .ConfigureAwait(false); @@ -147,6 +150,7 @@ public static async ValueTask> OpenAsync .GetCompressionTypeAsync( sourceStream, sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions, cancellationToken ) .ConfigureAwait(false); @@ -172,6 +176,7 @@ public static async ValueTask> OpenAsync .GetCompressionTypeAsync( sourceStream, sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions, cancellationToken ) .ConfigureAwait(false); @@ -197,6 +202,7 @@ public static async ValueTask> OpenAsync .GetCompressionTypeAsync( sourceStream, sourceStream.ReaderOptions.Providers, + sourceStream.ReaderOptions, cancellationToken ) .ConfigureAwait(false); diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index c92501bac..1af20475f 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -119,6 +119,81 @@ await TarArchive #endregion + internal override bool TryOpenReader( + SharpCompressStream stream, + ReaderOptions options, + out IReader? reader + ) + { + reader = null; + foreach (var wrapper in TarWrapper.Wrappers) + { + stream.Rewind(); + if (wrapper.IsMatch(stream)) + { + stream.Rewind(); + var decompressedStream = CreateProbeDecompressionStream( + stream, + wrapper.CompressionType, + options.Providers, + options + ); + if (TarArchive.IsTarFile(decompressedStream)) + { + stream.Rewind(true); + reader = new TarReader(stream, options, wrapper.CompressionType); + return true; + } + } + } + stream.Rewind(); + return false; + } + + internal override async ValueTask TryOpenReaderAsync( + SharpCompressStream stream, + ReaderOptions options, + CancellationToken cancellationToken = default + ) + { + // Use an intermediate ring-buffer wrapper for the probe phase. + // This ensures decompressor constructors that use synchronous reads + // can replay already-buffered data, even when the underlying stream + // only supports async I/O (e.g. streams wrapping async-only transports). + // After StopRecording(), the ring buffer is still available for replay + // because ReadWithRingBuffer() serves data whenever _logicalPosition < streamPosition, + // regardless of whether recording is active. + var probeStream = new SharpCompressStream(stream); + probeStream.StartRecording(); + foreach (var wrapper in TarWrapper.Wrappers) + { + probeStream.Rewind(); + if (await wrapper.IsMatchAsync(probeStream, cancellationToken).ConfigureAwait(false)) + { + probeStream.Rewind(); + var decompressedStream = await CreateProbeDecompressionStreamAsync( + probeStream, + wrapper.CompressionType, + options.Providers, + options, + cancellationToken + ) + .ConfigureAwait(false); + if ( + await TarArchive + .IsTarFileAsync(decompressedStream, cancellationToken) + .ConfigureAwait(false) + ) + { + probeStream.StopRecording(); + return new TarReader(probeStream, options, wrapper.CompressionType); + } + } + } + stream.Rewind(); + return null; + } + private static Stream CreateProbeDecompressionStream( Stream stream, CompressionType compressionType, @@ -179,10 +254,11 @@ private static async ValueTask CreateProbeDecompressionStreamAsync( public static CompressionType GetCompressionType( Stream stream, - CompressionProviderRegistry? providers = null + CompressionProviderRegistry? providers = null, + IReaderOptions? readerOptions = null ) { - providers ??= CompressionProviderRegistry.Default; + providers ??= readerOptions?.Providers ?? CompressionProviderRegistry.Default; stream.Seek(0, SeekOrigin.Begin); foreach (var wrapper in TarWrapper.Wrappers) { @@ -193,7 +269,8 @@ public static CompressionType GetCompressionType( var decompressedStream = CreateProbeDecompressionStream( stream, wrapper.CompressionType, - providers + providers, + readerOptions ); if (TarArchive.IsTarFile(decompressedStream)) { @@ -207,10 +284,11 @@ public static CompressionType GetCompressionType( public static async ValueTask GetCompressionTypeAsync( Stream stream, CompressionProviderRegistry? providers = null, + IReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { - providers ??= CompressionProviderRegistry.Default; + providers ??= readerOptions?.Providers ?? CompressionProviderRegistry.Default; stream.Seek(0, SeekOrigin.Begin); foreach (var wrapper in TarWrapper.Wrappers) { @@ -222,7 +300,8 @@ public static async ValueTask GetCompressionTypeAsync( stream, wrapper.CompressionType, providers, - cancellationToken: cancellationToken + readerOptions, + cancellationToken ) .ConfigureAwait(false); if ( diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs index d76792cff..013d16aa1 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs @@ -58,6 +58,22 @@ public void Tar_Skip() [Fact] public void Tar_GZip_OldGnu_Reader() => Read("Tar.oldgnu.tar.gz", CompressionType.GZip); + [Fact] + public void Tar_GZip_Reader_With_ExtensionHint() => + Read("Tar.tar.gz", CompressionType.GZip, new ReaderOptions { ExtensionHint = "tar.gz" }); + + [Fact] + public void Tar_BZip2_Reader_With_ExtensionHint() => + Read("Tar.tar.bz2", CompressionType.BZip2, new ReaderOptions { ExtensionHint = "tar.bz2" }); + + [Fact] + public void Tar_Xz_Reader_With_ExtensionHint() => + Read("Tar.tar.xz", CompressionType.Xz, new ReaderOptions { ExtensionHint = "tar.xz" }); + + [Fact] + public void Tar_Plain_Reader_With_ExtensionHint() => + Read("Tar.tar", CompressionType.None, new ReaderOptions { ExtensionHint = "tar" }); + [Fact] public void Tar_BZip2_Entry_Stream() {