diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs index 80b306ae..f9ea36ae 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs @@ -84,7 +84,11 @@ public partial class GZipArchive var stream = (await volumes.SingleAsync()).Stream; yield return new GZipArchiveEntry( this, - await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding) + await GZipFilePart.CreateAsync( + stream, + ReaderOptions.ArchiveEncoding, + ReaderOptions.CompressionProviders + ) ); } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 3aca1752..d6257bfd 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -88,7 +88,11 @@ public partial class GZipArchive : AbstractWritableArchive GetEntries(Stream stream, ReaderOptions options) { - yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding)); + yield return new GZipEntry( + GZipFilePart.Create(stream, options.ArchiveEncoding, options.CompressionProviders) + ); } // Async methods moved to GZipEntry.Async.cs diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs b/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs index 34c3c697..2c5ab7b8 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common.Tar.Headers; +using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; namespace SharpCompress.Common.GZip; @@ -17,7 +18,17 @@ internal sealed partial class GZipFilePart CancellationToken cancellationToken = default ) { - var part = new GZipFilePart(stream, archiveEncoding); + return await CreateAsync(stream, archiveEncoding, null, cancellationToken); + } + + internal static async ValueTask CreateAsync( + Stream stream, + IArchiveEncoding archiveEncoding, + CompressionProviderRegistry? compressionProviders, + CancellationToken cancellationToken = default + ) + { + var part = new GZipFilePart(stream, archiveEncoding, compressionProviders); await part.ReadAndValidateGzipHeaderAsync(cancellationToken); if (stream.CanSeek) diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index 1134fa89..610607e5 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -12,10 +12,37 @@ internal sealed partial class GZipFilePart : FilePart { private string? _name; private readonly Stream _stream; + private readonly CompressionProviderRegistry? _compressionProviders; internal static GZipFilePart Create(Stream stream, IArchiveEncoding archiveEncoding) { - var part = new GZipFilePart(stream, archiveEncoding); + var part = new GZipFilePart(stream, archiveEncoding, null); + + part.ReadAndValidateGzipHeader(); + if (stream.CanSeek) + { + var position = stream.Position; + stream.Position = stream.Length - 8; + part.ReadTrailer(); + stream.Position = position; + part.EntryStartPosition = position; + } + else + { + // For non-seekable streams, we can't read the trailer or track position. + // Set to 0 since the stream will be read sequentially from its current position. + part.EntryStartPosition = 0; + } + return part; + } + + internal static GZipFilePart Create( + Stream stream, + IArchiveEncoding archiveEncoding, + CompressionProviderRegistry? compressionProviders + ) + { + var part = new GZipFilePart(stream, archiveEncoding, compressionProviders); part.ReadAndValidateGzipHeader(); if (stream.CanSeek) @@ -38,6 +65,17 @@ internal sealed partial class GZipFilePart : FilePart private GZipFilePart(Stream stream, IArchiveEncoding archiveEncoding) : base(archiveEncoding) => _stream = stream; + private GZipFilePart( + Stream stream, + IArchiveEncoding archiveEncoding, + CompressionProviderRegistry? compressionProviders + ) + : base(archiveEncoding) + { + _stream = stream; + _compressionProviders = compressionProviders; + } + internal long EntryStartPosition { get; private set; } internal DateTime? DateModified { get; private set; } @@ -46,13 +84,11 @@ internal sealed partial class GZipFilePart : FilePart internal override string? FilePartName => _name; - internal override Stream GetCompressedStream() => - new DeflateStream( - _stream, - CompressionMode.Decompress, - CompressionLevel.Default, - leaveOpen: true - ); + internal override Stream GetCompressedStream() + { + var providers = _compressionProviders ?? CompressionProviderRegistry.Default; + return providers.CreateDecompressStream(CompressionType.Deflate, _stream); + } internal override Stream GetRawStream() => _stream; diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index 9e2e06b0..565b85d1 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs @@ -1,5 +1,6 @@ using System.IO; using SharpCompress.Common.Zip.Headers; +using SharpCompress.Compressors; namespace SharpCompress.Common.Zip; @@ -15,6 +16,14 @@ internal partial class SeekableZipFilePart : ZipFilePart ) : base(header, stream) => _headerFactory = headerFactory; + internal SeekableZipFilePart( + SeekableZipHeaderFactory headerFactory, + DirectoryEntryHeader header, + Stream stream, + CompressionProviderRegistry? compressionProviders + ) + : base(header, stream, compressionProviders) => _headerFactory = headerFactory; + internal override Stream GetCompressedStream() { if (!_isLocalHeaderLoaded) diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs index 5368469f..08a52973 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -1,6 +1,6 @@ using System.IO; using SharpCompress.Common.Zip.Headers; -using SharpCompress.Compressors.Deflate; +using SharpCompress.Compressors; using SharpCompress.IO; namespace SharpCompress.Common.Zip; @@ -12,6 +12,13 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart internal StreamingZipFilePart(ZipFileEntry header, Stream stream) : base(header, stream) { } + internal StreamingZipFilePart( + ZipFileEntry header, + Stream stream, + CompressionProviderRegistry? compressionProviders + ) + : base(header, stream, compressionProviders) { } + protected override Stream CreateBaseStream() => Header.PackedStream.NotNull(); internal override Stream GetCompressedStream() @@ -47,11 +54,6 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart // If we had TotalIn / TotalOut we could have used them Header.CompressedSize = _decompressionStream.Position; - if (_decompressionStream is DeflateStream deflateStream) - { - stream.Position = 0; - } - Skipped = true; } var reader = new BinaryReader(stream, System.Text.Encoding.Default, leaveOpen: true); diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 7dd860e7..0be4fd49 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -20,6 +20,8 @@ namespace SharpCompress.Common.Zip; internal abstract partial class ZipFilePart : FilePart { + private CompressionProviderRegistry? _compressionProviders; + internal ZipFilePart(ZipFileEntry header, Stream stream) : base(header.ArchiveEncoding) { @@ -28,9 +30,29 @@ internal abstract partial class ZipFilePart : FilePart BaseStream = stream; } + internal ZipFilePart( + ZipFileEntry header, + Stream stream, + CompressionProviderRegistry? compressionProviders + ) + : this(header, stream) + { + _compressionProviders = compressionProviders; + } + internal Stream BaseStream { get; } internal ZipFileEntry Header { get; set; } + /// + /// Gets or sets the compression provider registry. + /// If null, uses the default registry. + /// + internal CompressionProviderRegistry? CompressionProviders + { + get => _compressionProviders; + set => _compressionProviders = value; + } + internal override string? FilePartName => Header.Name; internal override Stream GetCompressedStream() @@ -64,8 +86,38 @@ internal abstract partial class ZipFilePart : FilePart protected bool LeaveStreamOpen => FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64; + /// + /// Gets the compression provider registry, falling back to default if not set. + /// + protected CompressionProviderRegistry GetProviders() => + _compressionProviders ?? CompressionProviderRegistry.Default; + + /// + /// Converts ZipCompressionMethod to CompressionType. + /// + protected static CompressionType ToCompressionType(ZipCompressionMethod method) => + method switch + { + ZipCompressionMethod.None => CompressionType.None, + ZipCompressionMethod.Deflate => CompressionType.Deflate, + ZipCompressionMethod.Deflate64 => CompressionType.Deflate64, + ZipCompressionMethod.BZip2 => CompressionType.BZip2, + ZipCompressionMethod.LZMA => CompressionType.LZMA, + ZipCompressionMethod.PPMd => CompressionType.PPMd, + ZipCompressionMethod.ZStandard => CompressionType.ZStandard, + ZipCompressionMethod.Xz => CompressionType.Xz, + ZipCompressionMethod.Shrink => CompressionType.Shrink, + ZipCompressionMethod.Reduce1 => CompressionType.Reduce1, + ZipCompressionMethod.Reduce2 => CompressionType.Reduce2, + ZipCompressionMethod.Reduce3 => CompressionType.Reduce3, + ZipCompressionMethod.Reduce4 => CompressionType.Reduce4, + ZipCompressionMethod.Explode => CompressionType.Explode, + _ => throw new NotSupportedException($"Unsupported compression method: {method}"), + }; + protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method) { + // Handle special cases first switch (method) { case ZipCompressionMethod.None: @@ -74,76 +126,29 @@ internal abstract partial class ZipFilePart : FilePart { return new DataDescriptorStream(stream); } - return stream; } - case ZipCompressionMethod.Shrink: + case ZipCompressionMethod.WinzipAes: { - return new ShrinkStream( - stream, - CompressionMode.Decompress, - Header.CompressedSize, - Header.UncompressedSize - ); - } - case ZipCompressionMethod.Reduce1: - { - return ReduceStream.Create( - stream, - Header.CompressedSize, - Header.UncompressedSize, - 1 - ); - } - case ZipCompressionMethod.Reduce2: - { - return ReduceStream.Create( - stream, - Header.CompressedSize, - Header.UncompressedSize, - 2 - ); - } - case ZipCompressionMethod.Reduce3: - { - return ReduceStream.Create( - stream, - Header.CompressedSize, - Header.UncompressedSize, - 3 - ); - } - case ZipCompressionMethod.Reduce4: - { - return ReduceStream.Create( - stream, - Header.CompressedSize, - Header.UncompressedSize, - 4 - ); - } - case ZipCompressionMethod.Explode: - { - return ExplodeStream.Create( - stream, - Header.CompressedSize, - Header.UncompressedSize, - Header.Flags - ); + return CreateWinzipAesDecompressionStream(stream); } + } - case ZipCompressionMethod.Deflate: - { - return new DeflateStream(stream, CompressionMode.Decompress); - } - case ZipCompressionMethod.Deflate64: - { - return new Deflate64Stream(stream, CompressionMode.Decompress); - } - case ZipCompressionMethod.BZip2: - { - return BZip2Stream.Create(stream, CompressionMode.Decompress, false); - } + // Get the compression type and providers + var compressionType = ToCompressionType(method); + var providers = GetProviders(); + + // Build context with header information + var context = new CompressionContext + { + InputSize = Header.CompressedSize, + OutputSize = Header.UncompressedSize, + CanSeek = stream.CanSeek, + }; + + // Handle methods that need special context + switch (method) + { case ZipCompressionMethod.LZMA: { if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted)) @@ -158,71 +163,71 @@ internal abstract partial class ZipFilePart : FilePart ) ) { - reader.ReadUInt16(); //LZMA version - var props = new byte[reader.ReadUInt16()]; + reader.ReadUInt16(); // LZMA version + var propsLength = reader.ReadUInt16(); + var props = new byte[propsLength]; reader.Read(props, 0, props.Length); - return LzmaStream.Create( - props, - stream, - Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1, - FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) + context = context with + { + Properties = props, + InputSize = + Header.CompressedSize > 0 + ? Header.CompressedSize - 4 - props.Length + : -1, + OutputSize = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) ? -1 - : Header.UncompressedSize - ); + : Header.UncompressedSize, + }; + return providers.CreateDecompressStream(compressionType, stream, context); } } - case ZipCompressionMethod.Xz: - { - return new XZStream(stream); - } - case ZipCompressionMethod.ZStandard: - { - return new DecompressionStream(stream); - } case ZipCompressionMethod.PPMd: { Span props = stackalloc byte[2]; stream.ReadFully(props); - return PpmdStream.Create(new PpmdProperties(props), stream, false); + context = context with { Properties = props.ToArray() }; + return providers.CreateDecompressStream(compressionType, stream, context); } - case ZipCompressionMethod.WinzipAes: + case ZipCompressionMethod.Explode: { - var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); - if (data is null) - { - throw new InvalidFormatException("No Winzip AES extra data found."); - } - if (data.Length != 7) - { - throw new InvalidFormatException("Winzip data length is not 7."); - } - var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes); - - if (compressedMethod != 0x01 && compressedMethod != 0x02) - { - throw new InvalidFormatException( - "Unexpected vendor version number for WinZip AES metadata" - ); - } - - var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2)); - if (vendorId != 0x4541) - { - throw new InvalidFormatException( - "Unexpected vendor ID for WinZip AES metadata" - ); - } - return CreateDecompressionStream( - stream, - (ZipCompressionMethod) - BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)) - ); - } - default: - { - throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod); + context = context with { FormatOptions = Header.Flags }; + return providers.CreateDecompressStream(compressionType, stream, context); } } + + // For simple methods, use the basic decompress + return providers.CreateDecompressStream(compressionType, stream, context); + } + + private Stream CreateWinzipAesDecompressionStream(Stream stream) + { + var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); + if (data is null) + { + throw new InvalidFormatException("No Winzip AES extra data found."); + } + if (data.Length != 7) + { + throw new InvalidFormatException("Winzip data length is not 7."); + } + var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes); + + if (compressedMethod != 0x01 && compressedMethod != 0x02) + { + throw new InvalidFormatException( + "Unexpected vendor version number for WinZip AES metadata" + ); + } + + var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2)); + if (vendorId != 0x4541) + { + throw new InvalidFormatException("Unexpected vendor ID for WinZip AES metadata"); + } + return CreateDecompressionStream( + stream, + (ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)) + ); } protected Stream GetCryptoStream(Stream plainStream) diff --git a/src/SharpCompress/Compressors/CompressionContext.cs b/src/SharpCompress/Compressors/CompressionContext.cs new file mode 100644 index 00000000..34a2379a --- /dev/null +++ b/src/SharpCompress/Compressors/CompressionContext.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; + +namespace SharpCompress.Compressors; + +/// +/// Provides context information for compression operations. +/// Carries format-specific parameters that some compression types require. +/// +public sealed record CompressionContext +{ + /// + /// The size of the input data, or -1 if unknown. + /// + public long InputSize { get; init; } = -1; + + /// + /// The expected output size, or -1 if unknown. + /// + public long OutputSize { get; init; } = -1; + + /// + /// Properties bytes for the compression format (e.g., LZMA properties). + /// + public byte[]? Properties { get; init; } + + /// + /// Whether the underlying stream supports seeking. + /// + public bool CanSeek { get; init; } + + /// + /// Additional format-specific options. + /// + public object? FormatOptions { get; init; } + + /// + /// Creates a CompressionContext from a stream. + /// + /// The stream to extract context from. + /// A CompressionContext populated from the stream. + public static CompressionContext FromStream(Stream stream) => + new() { CanSeek = stream.CanSeek, InputSize = stream.CanSeek ? stream.Length : -1 }; +} diff --git a/src/SharpCompress/Compressors/CompressionProviderRegistry.cs b/src/SharpCompress/Compressors/CompressionProviderRegistry.cs index 563d45f7..8ab3827c 100644 --- a/src/SharpCompress/Compressors/CompressionProviderRegistry.cs +++ b/src/SharpCompress/Compressors/CompressionProviderRegistry.cs @@ -90,6 +90,69 @@ public sealed class CompressionProviderRegistry return provider.CreateDecompressStream(source); } + /// + /// Creates a compression stream for the specified type with context. + /// + /// The compression type. + /// The destination stream. + /// The compression level. + /// Context information for the compression. + /// A compression stream. + /// If no provider is registered for the type. + /// If the provider does not support compression. + public Stream CreateCompressStream( + CompressionType type, + Stream destination, + int level, + CompressionContext context + ) + { + var provider = GetProvider(type); + if (provider is null) + { + throw new InvalidOperationException( + $"No compression provider registered for type: {type}" + ); + } + return provider.CreateCompressStream(destination, level, context); + } + + /// + /// Creates a decompression stream for the specified type with context. + /// + /// The compression type. + /// The source stream. + /// Context information for the decompression. + /// A decompression stream. + /// If no provider is registered for the type. + /// If the provider does not support decompression. + public Stream CreateDecompressStream( + CompressionType type, + Stream source, + CompressionContext context + ) + { + var provider = GetProvider(type); + if (provider is null) + { + throw new InvalidOperationException( + $"No compression provider registered for type: {type}" + ); + } + return provider.CreateDecompressStream(source, context); + } + + /// + /// Gets the provider as an ICompressingProvider if it supports complex initialization. + /// + /// The compression type. + /// The compressing provider, or null if the provider doesn't support complex initialization. + public ICompressingProvider? GetCompressingProvider(CompressionType type) + { + var provider = GetProvider(type); + return provider as ICompressingProvider; + } + /// /// Creates a new registry with the specified provider added or replaced. /// @@ -122,6 +185,15 @@ public sealed class CompressionProviderRegistry [CompressionType.LZip] = new LZipCompressionProvider(), [CompressionType.Xz] = new XzCompressionProvider(), [CompressionType.Lzw] = new LzwCompressionProvider(), + [CompressionType.Deflate64] = new Deflate64CompressionProvider(), + [CompressionType.Shrink] = new ShrinkCompressionProvider(), + [CompressionType.Reduce1] = new Reduce1CompressionProvider(), + [CompressionType.Reduce2] = new Reduce2CompressionProvider(), + [CompressionType.Reduce3] = new Reduce3CompressionProvider(), + [CompressionType.Reduce4] = new Reduce4CompressionProvider(), + [CompressionType.Explode] = new ExplodeCompressionProvider(), + [CompressionType.LZMA] = new LzmaCompressingProvider(), + [CompressionType.PPMd] = new PpmdCompressingProvider(), }; return new CompressionProviderRegistry(providers); diff --git a/src/SharpCompress/Compressors/ICompressingProvider.cs b/src/SharpCompress/Compressors/ICompressingProvider.cs new file mode 100644 index 00000000..9cb85339 --- /dev/null +++ b/src/SharpCompress/Compressors/ICompressingProvider.cs @@ -0,0 +1,43 @@ +using System.IO; +using SharpCompress.Common; + +namespace SharpCompress.Compressors; + +/// +/// Extended compression provider interface for formats that require initialization/finalization data. +/// +/// +/// Some compression formats (like LZMA and PPMd in Zip) require special handling: +/// - Data written before compression starts (magic bytes, properties headers) +/// - Data written after compression completes (properties, footers) +/// This interface extends ICompressionProvider to support these complex initialization patterns +/// while keeping the simple ICompressionProvider interface for formats that don't need it. +/// +public interface ICompressingProvider : ICompressionProvider +{ + /// + /// Gets initialization data to write before compression starts. + /// Returns null if no pre-compression data is needed. + /// + /// Context information. + /// Bytes to write before compression, or null. + byte[]? GetPreCompressionData(CompressionContext context); + + /// + /// Gets properties/data to write after creating the compression stream but before writing data. + /// Returns null if no properties are needed. + /// + /// The compression stream that was created. + /// Context information. + /// Bytes to write after stream creation, or null. + byte[]? GetCompressionProperties(Stream stream, CompressionContext context); + + /// + /// Gets data to write after compression is complete. + /// Returns null if no post-compression data is needed. + /// + /// The compression stream. + /// Context information. + /// Bytes to write after compression, or null. + byte[]? GetPostCompressionData(Stream stream, CompressionContext context); +} diff --git a/src/SharpCompress/Compressors/ICompressionProvider.cs b/src/SharpCompress/Compressors/ICompressionProvider.cs index a1a662c3..14cc12db 100644 --- a/src/SharpCompress/Compressors/ICompressionProvider.cs +++ b/src/SharpCompress/Compressors/ICompressionProvider.cs @@ -46,6 +46,20 @@ public interface ICompressionProvider /// Thrown if SupportsCompression is false. Stream CreateCompressStream(Stream destination, int compressionLevel); + /// + /// Creates a compression stream with context information. + /// + /// The destination stream. + /// The compression level. + /// Context information about the compression. + /// A compression stream. + /// Thrown if SupportsCompression is false. + Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ); + /// /// Creates a decompression stream that decompresses data read from it. /// @@ -53,4 +67,13 @@ public interface ICompressionProvider /// A stream that decompresses data read from it. /// Thrown if SupportsDecompression is false. Stream CreateDecompressStream(Stream source); + + /// + /// Creates a decompression stream with context information. + /// + /// The source stream. + /// Context information about the decompression. + /// A decompression stream. + /// Thrown if SupportsDecompression is false. + Stream CreateDecompressStream(Stream source, CompressionContext context); } diff --git a/src/SharpCompress/Compressors/Providers/BZip2CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/BZip2CompressionProvider.cs index ca21acbb..654bbb08 100644 --- a/src/SharpCompress/Compressors/Providers/BZip2CompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/BZip2CompressionProvider.cs @@ -19,8 +19,24 @@ public sealed class BZip2CompressionProvider : ICompressionProvider return BZip2Stream.Create(destination, CompressionMode.Compress, false); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for BZip2 compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return BZip2Stream.Create(source, CompressionMode.Decompress, false); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for BZip2 decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/Deflate64CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/Deflate64CompressionProvider.cs new file mode 100644 index 00000000..80541591 --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/Deflate64CompressionProvider.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Deflate64; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Deflate64 decompression using SharpCompress's internal implementation. +/// Note: Deflate64 compression is not supported; this provider is decompression-only. +/// +public sealed class Deflate64CompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Deflate64; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Deflate64 compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Deflate64 compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + return new Deflate64Stream(source, CompressionMode.Decompress); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for Deflate64 decompression + return CreateDecompressStream(source); + } +} diff --git a/src/SharpCompress/Compressors/Providers/DeflateCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/DeflateCompressionProvider.cs index 447cb363..09ff8630 100644 --- a/src/SharpCompress/Compressors/Providers/DeflateCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/DeflateCompressionProvider.cs @@ -19,8 +19,24 @@ public sealed class DeflateCompressionProvider : ICompressionProvider return new DeflateStream(destination, CompressionMode.Compress, level); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for simple Deflate compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new DeflateStream(source, CompressionMode.Decompress); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for simple Deflate decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/ExplodeCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/ExplodeCompressionProvider.cs new file mode 100644 index 00000000..5d62c00f --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/ExplodeCompressionProvider.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Common.Zip.Headers; +using SharpCompress.Compressors.Explode; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Explode decompression using SharpCompress's internal implementation. +/// Note: Explode compression is not supported; this provider is decompression-only. +/// +/// +/// Explode requires compressed size, uncompressed size, and flags which must be provided via CompressionContext. +/// +public sealed class ExplodeCompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Explode; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Explode compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Explode compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Explode decompression requires compressed size, uncompressed size, and flags. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload with FormatOptions." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Explode decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + if (context.FormatOptions is not HeaderFlags flags) + { + throw new ArgumentException( + "Explode decompression requires HeaderFlags in CompressionContext.FormatOptions.", + nameof(context) + ); + } + + return ExplodeStream.Create(source, context.InputSize, context.OutputSize, flags); + } +} diff --git a/src/SharpCompress/Compressors/Providers/GZipCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/GZipCompressionProvider.cs index 0c9da42b..40bedbd0 100644 --- a/src/SharpCompress/Compressors/Providers/GZipCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/GZipCompressionProvider.cs @@ -19,8 +19,24 @@ public sealed class GZipCompressionProvider : ICompressionProvider return new GZipStream(destination, CompressionMode.Compress, level); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for simple GZip compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new GZipStream(source, CompressionMode.Decompress); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for simple GZip decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/LZipCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/LZipCompressionProvider.cs index e19da0c1..2775b049 100644 --- a/src/SharpCompress/Compressors/Providers/LZipCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/LZipCompressionProvider.cs @@ -18,8 +18,24 @@ public sealed class LZipCompressionProvider : ICompressionProvider return new LZipStream(destination, CompressionMode.Compress); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for LZip compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new LZipStream(source, CompressionMode.Decompress); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for LZip decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/LzmaCompressingProvider.cs b/src/SharpCompress/Compressors/Providers/LzmaCompressingProvider.cs new file mode 100644 index 00000000..38d42c67 --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/LzmaCompressingProvider.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.LZMA; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides LZMA compression and decompression using SharpCompress's internal implementation. +/// This is a complex provider that requires initialization data for compression. +/// +public sealed class LzmaCompressingProvider : ICompressingProvider +{ + public CompressionType CompressionType => CompressionType.LZMA; + public bool SupportsCompression => true; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new InvalidOperationException( + "LZMA compression requires context with CanSeek information. " + + "Use CreateCompressStream(Stream, int, CompressionContext) overload." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // LZMA stream creation returns the encoder stream + // Note: Pre-compression data and properties are handled via ICompressingProvider methods + var props = new LzmaEncoderProperties(!context.CanSeek); + return LzmaStream.Create(props, false, destination); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "LZMA decompression requires properties. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload with Properties." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.Properties is null || context.Properties.Length < 5) + { + throw new ArgumentException( + "LZMA decompression requires Properties (at least 5 bytes) in CompressionContext.", + nameof(context) + ); + } + + return LzmaStream.Create(context.Properties, source, context.InputSize, context.OutputSize); + } + + public byte[]? GetPreCompressionData(CompressionContext context) + { + // Zip format writes these magic bytes before the LZMA stream + return new byte[] { 9, 20, 5, 0 }; + } + + public byte[]? GetCompressionProperties(Stream stream, CompressionContext context) + { + // The LZMA stream exposes its properties after creation + if (stream is LzmaStream lzmaStream) + { + return lzmaStream.Properties; + } + return null; + } + + public byte[]? GetPostCompressionData(Stream stream, CompressionContext context) + { + // No post-compression data needed for LZMA in Zip + return null; + } +} diff --git a/src/SharpCompress/Compressors/Providers/LzwCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/LzwCompressionProvider.cs index 4b1646d3..8740cdee 100644 --- a/src/SharpCompress/Compressors/Providers/LzwCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/LzwCompressionProvider.cs @@ -22,8 +22,25 @@ public sealed class LzwCompressionProvider : ICompressionProvider ); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "LZW compression is not supported by SharpCompress's internal implementation." + ); + } + public Stream CreateDecompressStream(Stream source) { return new LzwStream(source); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for LZW decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/PpmdCompressingProvider.cs b/src/SharpCompress/Compressors/Providers/PpmdCompressingProvider.cs new file mode 100644 index 00000000..fe3c6f56 --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/PpmdCompressingProvider.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.PPMd; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides PPMd compression and decompression using SharpCompress's internal implementation. +/// This is a complex provider that requires initialization data for compression. +/// +public sealed class PpmdCompressingProvider : ICompressingProvider +{ + public CompressionType CompressionType => CompressionType.PPMd; + public bool SupportsCompression => true; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + // Ppmd doesn't use compressionLevel, uses PpmdProperties instead + var props = new PpmdProperties(); + return PpmdStream.Create(props, destination, true); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for Ppmd compression, but we could use FormatOptions for custom properties + if (context.FormatOptions is PpmdProperties customProps) + { + return PpmdStream.Create(customProps, destination, true); + } + + return CreateCompressStream(destination, compressionLevel); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "PPMd decompression requires properties. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload with Properties." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.Properties is null || context.Properties.Length < 2) + { + throw new ArgumentException( + "PPMd decompression requires Properties (at least 2 bytes) in CompressionContext.", + nameof(context) + ); + } + + var props = new PpmdProperties(context.Properties); + return PpmdStream.Create(props, source, false); + } + + public byte[]? GetPreCompressionData(CompressionContext context) + { + // Ppmd writes its properties before the compressed data + if (context.FormatOptions is PpmdProperties customProps) + { + return customProps.Properties; + } + + var defaultProps = new PpmdProperties(); + return defaultProps.Properties; + } + + public byte[]? GetCompressionProperties(Stream stream, CompressionContext context) + { + // Properties are already written in GetPreCompressionData + return null; + } + + public byte[]? GetPostCompressionData(Stream stream, CompressionContext context) + { + // No post-compression data needed for Ppmd + return null; + } +} diff --git a/src/SharpCompress/Compressors/Providers/Reduce1CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/Reduce1CompressionProvider.cs new file mode 100644 index 00000000..4ea46396 --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/Reduce1CompressionProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Reduce; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Reduce1 decompression using SharpCompress's internal implementation. +/// Note: Reduce compression is not supported; this provider is decompression-only. +/// +public sealed class Reduce1CompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Reduce1; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Reduce decompression requires compressed and uncompressed sizes. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Reduce decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + return ReduceStream.Create(source, context.InputSize, context.OutputSize, 1); + } +} diff --git a/src/SharpCompress/Compressors/Providers/Reduce2CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/Reduce2CompressionProvider.cs new file mode 100644 index 00000000..f863288a --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/Reduce2CompressionProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Reduce; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Reduce2 decompression using SharpCompress's internal implementation. +/// Note: Reduce compression is not supported; this provider is decompression-only. +/// +public sealed class Reduce2CompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Reduce2; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Reduce decompression requires compressed and uncompressed sizes. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Reduce decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + return ReduceStream.Create(source, context.InputSize, context.OutputSize, 2); + } +} diff --git a/src/SharpCompress/Compressors/Providers/Reduce3CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/Reduce3CompressionProvider.cs new file mode 100644 index 00000000..d9676a3d --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/Reduce3CompressionProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Reduce; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Reduce3 decompression using SharpCompress's internal implementation. +/// Note: Reduce compression is not supported; this provider is decompression-only. +/// +public sealed class Reduce3CompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Reduce3; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Reduce decompression requires compressed and uncompressed sizes. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Reduce decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + return ReduceStream.Create(source, context.InputSize, context.OutputSize, 3); + } +} diff --git a/src/SharpCompress/Compressors/Providers/Reduce4CompressionProvider.cs b/src/SharpCompress/Compressors/Providers/Reduce4CompressionProvider.cs new file mode 100644 index 00000000..d57def7d --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/Reduce4CompressionProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Reduce; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Reduce4 decompression using SharpCompress's internal implementation. +/// Note: Reduce compression is not supported; this provider is decompression-only. +/// +public sealed class Reduce4CompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Reduce4; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Reduce compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Reduce decompression requires compressed and uncompressed sizes. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Reduce decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + return ReduceStream.Create(source, context.InputSize, context.OutputSize, 4); + } +} diff --git a/src/SharpCompress/Compressors/Providers/ShrinkCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/ShrinkCompressionProvider.cs new file mode 100644 index 00000000..827a2d34 --- /dev/null +++ b/src/SharpCompress/Compressors/Providers/ShrinkCompressionProvider.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using SharpCompress.Common; +using SharpCompress.Compressors.Shrink; + +namespace SharpCompress.Compressors.Providers; + +/// +/// Provides Shrink decompression using SharpCompress's internal implementation. +/// Note: Shrink compression is not supported; this provider is decompression-only. +/// +/// +/// Shrink requires compressed and uncompressed sizes which must be provided via CompressionContext. +/// +public sealed class ShrinkCompressionProvider : ICompressionProvider +{ + public CompressionType CompressionType => CompressionType.Shrink; + public bool SupportsCompression => false; + public bool SupportsDecompression => true; + + public Stream CreateCompressStream(Stream destination, int compressionLevel) + { + throw new NotSupportedException( + "Shrink compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "Shrink compression is not supported by SharpCompress's internal implementation." + ); + } + + public Stream CreateDecompressStream(Stream source) + { + throw new InvalidOperationException( + "Shrink decompression requires compressed and uncompressed sizes. " + + "Use CreateDecompressStream(Stream, CompressionContext) overload." + ); + } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + if (context.InputSize < 0 || context.OutputSize < 0) + { + throw new ArgumentException( + "Shrink decompression requires InputSize and OutputSize in CompressionContext.", + nameof(context) + ); + } + + return new ShrinkStream( + source, + CompressionMode.Decompress, + context.InputSize, + context.OutputSize + ); + } +} diff --git a/src/SharpCompress/Compressors/Providers/SystemDeflateCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/SystemDeflateCompressionProvider.cs index 2249d7d3..83bdbd86 100644 --- a/src/SharpCompress/Compressors/Providers/SystemDeflateCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/SystemDeflateCompressionProvider.cs @@ -24,6 +24,16 @@ public sealed class SystemDeflateCompressionProvider : ICompressionProvider return new DeflateStream(destination, bclLevel, leaveOpen: true); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for simple Deflate compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new DeflateStream( @@ -33,6 +43,12 @@ public sealed class SystemDeflateCompressionProvider : ICompressionProvider ); } + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for simple Deflate decompression + return CreateDecompressStream(source); + } + /// /// Maps SharpCompress compression level (0-9) to BCL CompressionLevel. /// diff --git a/src/SharpCompress/Compressors/Providers/SystemGZipCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/SystemGZipCompressionProvider.cs index 32c2b375..65246964 100644 --- a/src/SharpCompress/Compressors/Providers/SystemGZipCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/SystemGZipCompressionProvider.cs @@ -24,6 +24,16 @@ public sealed class SystemGZipCompressionProvider : ICompressionProvider return new GZipStream(destination, bclLevel, leaveOpen: true); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for simple GZip compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new GZipStream( @@ -33,6 +43,12 @@ public sealed class SystemGZipCompressionProvider : ICompressionProvider ); } + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for simple GZip decompression + return CreateDecompressStream(source); + } + /// /// Maps SharpCompress compression level (0-9) to BCL CompressionLevel. /// diff --git a/src/SharpCompress/Compressors/Providers/XzCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/XzCompressionProvider.cs index 13913d7a..43ececf8 100644 --- a/src/SharpCompress/Compressors/Providers/XzCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/XzCompressionProvider.cs @@ -22,8 +22,25 @@ public sealed class XzCompressionProvider : ICompressionProvider ); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + throw new NotSupportedException( + "XZ compression is not supported by SharpCompress's internal implementation." + ); + } + public Stream CreateDecompressStream(Stream source) { return new XZStream(source); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for XZ decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Compressors/Providers/ZStandardCompressionProvider.cs b/src/SharpCompress/Compressors/Providers/ZStandardCompressionProvider.cs index 55a81fbc..ed902806 100644 --- a/src/SharpCompress/Compressors/Providers/ZStandardCompressionProvider.cs +++ b/src/SharpCompress/Compressors/Providers/ZStandardCompressionProvider.cs @@ -18,8 +18,24 @@ public sealed class ZStandardCompressionProvider : ICompressionProvider return new ZStd.CompressionStream(destination, compressionLevel); } + public Stream CreateCompressStream( + Stream destination, + int compressionLevel, + CompressionContext context + ) + { + // Context not used for ZStandard compression + return CreateCompressStream(destination, compressionLevel); + } + public Stream CreateDecompressStream(Stream source) { return new ZStd.DecompressionStream(source); } + + public Stream CreateDecompressStream(Stream source, CompressionContext context) + { + // Context not used for ZStandard decompression + return CreateDecompressStream(source); + } } diff --git a/src/SharpCompress/Readers/Zip/ZipReader.Async.cs b/src/SharpCompress/Readers/Zip/ZipReader.Async.cs index b8c71eaa..ed38eac0 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.Async.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.Async.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; +using SharpCompress.Compressors; namespace SharpCompress.Readers.Zip; @@ -18,16 +19,28 @@ public partial class ZipReader { private readonly StreamingZipHeaderFactory _headerFactory; private readonly Stream _stream; + private readonly CompressionProviderRegistry? _compressionProviders; - public ZipEntryAsyncEnumerable(StreamingZipHeaderFactory headerFactory, Stream stream) + public ZipEntryAsyncEnumerable( + StreamingZipHeaderFactory headerFactory, + Stream stream, + CompressionProviderRegistry? compressionProviders + ) { _headerFactory = headerFactory; _stream = stream; + _compressionProviders = compressionProviders; } public IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default - ) => new ZipEntryAsyncEnumerator(_headerFactory, _stream, cancellationToken); + ) => + new ZipEntryAsyncEnumerator( + _headerFactory, + _stream, + _compressionProviders, + cancellationToken + ); } /// @@ -37,15 +50,18 @@ public partial class ZipReader { private readonly Stream _stream; private readonly IAsyncEnumerator _headerEnumerator; + private readonly CompressionProviderRegistry? _compressionProviders; private ZipEntry? _current; public ZipEntryAsyncEnumerator( StreamingZipHeaderFactory headerFactory, Stream stream, + CompressionProviderRegistry? compressionProviders, CancellationToken cancellationToken ) { _stream = stream; + _compressionProviders = compressionProviders; _headerEnumerator = headerFactory .ReadStreamHeaderAsync(stream) .GetAsyncEnumerator(cancellationToken); @@ -67,7 +83,11 @@ public partial class ZipReader { case ZipHeaderType.LocalEntry: _current = new ZipEntry( - new StreamingZipFilePart((LocalEntryHeader)header, _stream) + new StreamingZipFilePart( + (LocalEntryHeader)header, + _stream, + _compressionProviders + ) ); return true; case ZipHeaderType.DirectoryEntry: diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index 83d0aa8c..89227a34 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -74,7 +74,11 @@ public partial class ZipReader : AbstractReader case ZipHeaderType.LocalEntry: { yield return new ZipEntry( - new StreamingZipFilePart((LocalEntryHeader)h, stream) + new StreamingZipFilePart( + (LocalEntryHeader)h, + stream, + Options.CompressionProviders + ) ); } break; @@ -99,7 +103,7 @@ public partial class ZipReader : AbstractReader /// Returns entries asynchronously for streams that only support async reads. /// protected override IAsyncEnumerable GetEntriesAsync(Stream stream) => - new ZipEntryAsyncEnumerable(_headerFactory, stream); + new ZipEntryAsyncEnumerable(_headerFactory, stream, Options.CompressionProviders); // Async nested classes moved to ZipReader.Async.cs } diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 1769557c..a34a78a7 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -448,25 +448,70 @@ public partial class ZipWriter : AbstractWriter } case ZipCompressionMethod.LZMA: { - // LZMA has complex initialization that doesn't fit the provider pattern - counting.WriteByte(9); - counting.WriteByte(20); - counting.WriteByte(5); - counting.WriteByte(0); - - var lzmaStream = LzmaStream.Create( - new LzmaEncoderProperties(!originalStream.CanSeek), - false, - counting + // Use ICompressingProvider for complex initialization + var compressingProvider = providers.GetCompressingProvider( + CompressionType.LZMA ); - counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length); + if (compressingProvider is null) + { + throw new InvalidOperationException("LZMA compression provider not found."); + } + + var context = new CompressionContext { CanSeek = originalStream.CanSeek }; + + // Write pre-compression data (magic bytes) + var preData = compressingProvider.GetPreCompressionData(context); + if (preData != null) + { + counting.Write(preData, 0, preData.Length); + } + + // Create compression stream + var lzmaStream = compressingProvider.CreateCompressStream( + counting, + compressionLevel, + context + ); + + // Write compression properties + var props = compressingProvider.GetCompressionProperties(lzmaStream, context); + if (props != null) + { + counting.Write(props, 0, props.Length); + } + return lzmaStream; } case ZipCompressionMethod.PPMd: { - // PPMd has complex initialization that doesn't fit the provider pattern - counting.Write(writer.PpmdProperties.Properties, 0, 2); - return PpmdStream.Create(writer.PpmdProperties, counting, true); + // Use ICompressingProvider for complex initialization + var compressingProvider = providers.GetCompressingProvider( + CompressionType.PPMd + ); + if (compressingProvider is null) + { + throw new InvalidOperationException("PPMd compression provider not found."); + } + + var context = new CompressionContext + { + CanSeek = originalStream.CanSeek, + FormatOptions = writer.PpmdProperties, + }; + + // Write pre-compression data (properties) + var preData = compressingProvider.GetPreCompressionData(context); + if (preData != null) + { + counting.Write(preData, 0, preData.Length); + } + + // Create compression stream + return compressingProvider.CreateCompressStream( + counting, + compressionLevel, + context + ); } case ZipCompressionMethod.ZStandard: {