From da5cc69a069b4c55570880ac0c165903c824a33e Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 13 Feb 2026 09:51:46 +0000 Subject: [PATCH] fix up gzip encoding --- src/SharpCompress/Archives/Tar/TarArchive.cs | 11 ++++- .../Compressors/Deflate/GZipStream.cs | 21 +++++++- src/SharpCompress/Factories/GZipFactory.cs | 7 ++- src/SharpCompress/Factories/TarFactory.cs | 49 ++++++++++++++++--- .../Default/GZipCompressionProvider.cs | 36 +++++++++++++- src/SharpCompress/Readers/Tar/TarReader.cs | 14 +++++- .../CompressionProviderTests.cs | 39 +++++++++++++++ 7 files changed, 164 insertions(+), 13 deletions(-) diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index 4f8a32bc..7236e7d7 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -8,6 +8,7 @@ using SharpCompress.Common; using SharpCompress.Common.Tar; using SharpCompress.Common.Tar.Headers; using SharpCompress.IO; +using SharpCompress.Providers; using SharpCompress.Readers; using SharpCompress.Readers.Tar; using SharpCompress.Writers.Tar; @@ -44,7 +45,11 @@ public partial class TarArchive ), CompressionType.GZip => ReaderOptions.Providers.CreateDecompressStream( CompressionType.GZip, - stream + stream, + CompressionContext.FromStream(stream) with + { + FormatOptions = ReaderOptions, + } ), CompressionType.ZStandard => ReaderOptions.Providers.CreateDecompressStream( CompressionType.ZStandard, @@ -80,6 +85,10 @@ public partial class TarArchive CompressionType.GZip => ReaderOptions.Providers.CreateDecompressStreamAsync( CompressionType.GZip, stream, + CompressionContext.FromStream(stream) with + { + FormatOptions = ReaderOptions, + }, cancellationToken ), CompressionType.ZStandard => ReaderOptions.Providers.CreateDecompressStreamAsync( diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.cs index 95b7c03d..b0b13216 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -32,6 +32,8 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Options; namespace SharpCompress.Compressors.Deflate; @@ -53,8 +55,23 @@ public partial class GZipStream : Stream public GZipStream(Stream stream, CompressionMode mode) : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) { } - public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) - : this(stream, mode, level, Encoding.UTF8) { } + public GZipStream(Stream stream, CompressionMode mode, IReaderOptions readerOptions) + : this(stream, mode, CompressionLevel.Default, readerOptions) { } + + public GZipStream( + Stream stream, + CompressionMode mode, + CompressionLevel level, + IReaderOptions readerOptions + ) + : this( + stream, + mode, + level, + ( + readerOptions ?? throw new ArgumentNullException(nameof(readerOptions)) + ).ArchiveEncoding.GetEncoding() + ) { } public GZipStream( Stream stream, diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index 4317bb05..37a7b0f0 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -9,6 +9,7 @@ using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Common.Options; using SharpCompress.IO; +using SharpCompress.Providers; using SharpCompress.Readers; using SharpCompress.Readers.GZip; using SharpCompress.Readers.Tar; @@ -129,7 +130,11 @@ public class GZipFactory sharpCompressStream.Rewind(); using var testStream = options.Providers.CreateDecompressStream( CompressionType.GZip, - SharpCompressStream.CreateNonDisposing(sharpCompressStream) + SharpCompressStream.CreateNonDisposing(sharpCompressStream), + CompressionContext.FromStream(sharpCompressStream) with + { + FormatOptions = options, + } ); if (TarArchive.IsTarFile(testStream)) { diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index e648535b..c1696161 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -99,7 +99,7 @@ public class TarFactory sharpCompressStream, wrapper.CompressionType, providers, - cancellationToken + cancellationToken: cancellationToken ) .ConfigureAwait(false); if ( @@ -122,19 +122,36 @@ public class TarFactory private static Stream CreateProbeDecompressionStream( Stream stream, CompressionType compressionType, - CompressionProviderRegistry providers + CompressionProviderRegistry providers, + IReaderOptions? readerOptions = null ) { var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream); - return compressionType == CompressionType.None - ? nonDisposingStream - : providers.CreateDecompressStream(compressionType, nonDisposingStream); + if (compressionType == CompressionType.None) + { + return nonDisposingStream; + } + + if (compressionType == CompressionType.GZip && readerOptions is not null) + { + return providers.CreateDecompressStream( + compressionType, + nonDisposingStream, + CompressionContext.FromStream(nonDisposingStream) with + { + FormatOptions = readerOptions, + } + ); + } + + return providers.CreateDecompressStream(compressionType, nonDisposingStream); } private static async ValueTask CreateProbeDecompressionStreamAsync( Stream stream, CompressionType compressionType, CompressionProviderRegistry providers, + IReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { @@ -143,6 +160,22 @@ public class TarFactory { return nonDisposingStream; } + + if (compressionType == CompressionType.GZip && readerOptions is not null) + { + return await providers + .CreateDecompressStreamAsync( + compressionType, + nonDisposingStream, + CompressionContext.FromStream(nonDisposingStream) with + { + FormatOptions = readerOptions, + }, + cancellationToken + ) + .ConfigureAwait(false); + } + return await providers .CreateDecompressStreamAsync(compressionType, nonDisposingStream, cancellationToken) .ConfigureAwait(false); @@ -193,7 +226,7 @@ public class TarFactory stream, wrapper.CompressionType, providers, - cancellationToken + cancellationToken: cancellationToken ) .ConfigureAwait(false); if ( @@ -286,7 +319,8 @@ public class TarFactory var decompressedStream = CreateProbeDecompressionStream( sharpCompressStream, wrapper.CompressionType, - options.Providers + options.Providers, + options ); if (TarArchive.IsTarFile(decompressedStream)) { @@ -323,6 +357,7 @@ public class TarFactory sharpCompressStream, wrapper.CompressionType, options.Providers, + options, cancellationToken ) .ConfigureAwait(false); diff --git a/src/SharpCompress/Providers/Default/GZipCompressionProvider.cs b/src/SharpCompress/Providers/Default/GZipCompressionProvider.cs index 86d5704b..dff5a427 100644 --- a/src/SharpCompress/Providers/Default/GZipCompressionProvider.cs +++ b/src/SharpCompress/Providers/Default/GZipCompressionProvider.cs @@ -1,5 +1,9 @@ using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; @@ -17,11 +21,41 @@ public sealed class GZipCompressionProvider : CompressionProviderBase public override Stream CreateCompressStream(Stream destination, int compressionLevel) { var level = (CompressionLevel)compressionLevel; - return new GZipStream(destination, CompressionMode.Compress, level); + return new GZipStream(destination, CompressionMode.Compress, level, Encoding.UTF8); } public override Stream CreateDecompressStream(Stream source) { return new GZipStream(source, CompressionMode.Decompress); } + + public override Stream CreateDecompressStream(Stream source, CompressionContext context) + { + return new GZipStream( + source, + CompressionMode.Decompress, + CompressionLevel.Default, + ResolveHeaderEncoding(context) + ); + } + + public override ValueTask CreateDecompressStreamAsync( + Stream source, + CompressionContext context, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(CreateDecompressStream(source, context)); + } + + private static Encoding ResolveHeaderEncoding(CompressionContext context) => + context.FormatOptions switch + { + IReaderOptions readerOptions => readerOptions.ArchiveEncoding.GetEncoding(), + IEncodingOptions encodingOptions => encodingOptions.ArchiveEncoding.GetEncoding(), + IArchiveEncoding archiveEncoding => archiveEncoding.GetEncoding(), + Encoding encoding => encoding, + _ => Encoding.UTF8, + }; } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index 230090bb..2e8a096b 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -13,6 +13,7 @@ using SharpCompress.Compressors.Lzw; using SharpCompress.Compressors.Xz; using SharpCompress.Compressors.ZStandard; using SharpCompress.IO; +using SharpCompress.Providers; namespace SharpCompress.Readers.Tar; @@ -41,7 +42,14 @@ public partial class TarReader : AbstractReader CompressionType.BZip2, stream ), - CompressionType.GZip => providers.CreateDecompressStream(CompressionType.GZip, stream), + CompressionType.GZip => providers.CreateDecompressStream( + CompressionType.GZip, + stream, + CompressionContext.FromStream(stream) with + { + FormatOptions = Options, + } + ), CompressionType.ZStandard => providers.CreateDecompressStream( CompressionType.ZStandard, stream @@ -71,6 +79,10 @@ public partial class TarReader : AbstractReader CompressionType.GZip => providers.CreateDecompressStreamAsync( CompressionType.GZip, stream, + CompressionContext.FromStream(stream) with + { + FormatOptions = Options, + }, cancellationToken ), CompressionType.ZStandard => providers.CreateDecompressStreamAsync( diff --git a/tests/SharpCompress.Test/CompressionProviderTests.cs b/tests/SharpCompress.Test/CompressionProviderTests.cs index ded095e4..7bb274c9 100644 --- a/tests/SharpCompress.Test/CompressionProviderTests.cs +++ b/tests/SharpCompress.Test/CompressionProviderTests.cs @@ -293,6 +293,45 @@ public class CompressionProviderTests result.Should().Equal(original); } + [Fact] + public void GZipProvider_Decompress_WithReaderOptionsContext_UsesArchiveEncoding() + { + var provider = new GZipCompressionProvider(); + var data = Encoding.UTF8.GetBytes("gzip filename encoding"); + var expectedFileName = "café.txt"; + var archiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding("iso-8859-1") }; + + using var compressedStream = new MemoryStream(); + using ( + var compressStream = new SharpCompress.Compressors.Deflate.GZipStream( + SharpCompressStream.CreateNonDisposing(compressedStream), + CompressionMode.Compress, + SharpCompress.Compressors.Deflate.CompressionLevel.Default, + archiveEncoding.Default + ) + ) + { + compressStream.FileName = expectedFileName; + compressStream.Write(data, 0, data.Length); + } + + compressedStream.Position = 0; + var readerOptions = new ReaderOptions { ArchiveEncoding = archiveEncoding }; + var context = CompressionContext.FromStream(compressedStream) with + { + FormatOptions = readerOptions, + }; + + using var decompressStream = provider.CreateDecompressStream(compressedStream, context); + using var resultStream = new MemoryStream(); + decompressStream.CopyTo(resultStream); + + resultStream.ToArray().Should().Equal(data); + decompressStream.Should().BeOfType(); + var gzipStream = (SharpCompress.Compressors.Deflate.GZipStream)decompressStream; + gzipStream.FileName.Should().Be(expectedFileName); + } + [Fact] public void BZip2Provider_SupportsCompressionAndDecompression() {