diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index deb2f278..08aaa2b2 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -70,64 +70,50 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry public Stream OpenEntryStream() { - IRarUnpack unpack; - bool ownsUnpack; + var readStream = new MultiVolumeReadOnlyStream(Parts.Cast(), archive); // For solid archives, use shared Unpack instance (must be processed sequentially) - // For non-solid archives, create new instance per stream to support multi-threading + // For non-solid archives, use factory to create owned instance (supports multi-threading) if (archive.IsSolid) { - unpack = IsRarV3 ? archive.UnpackV1.Value : archive.UnpackV2017.Value; - ownsUnpack = false; + var unpack = IsRarV3 ? archive.UnpackV1.Value : archive.UnpackV2017.Value; + var stream = new RarStream(unpack, FileHeader, readStream, ownsUnpack: false); + stream.Initialize(); + return stream; } else { - unpack = IsRarV3 - ? new Compressors.Rar.UnpackV1.Unpack() - : new Compressors.Rar.UnpackV2017.Unpack(); - ownsUnpack = true; + var factory = IsRarV3 + ? (IRarUnpackFactory)UnpackV1Factory.Instance + : UnpackV2017Factory.Instance; + var stream = new RarStream(factory, FileHeader, readStream); + stream.Initialize(); + return stream; } - - var stream = new RarStream( - unpack, - FileHeader, - new MultiVolumeReadOnlyStream(Parts.Cast(), archive), - ownsUnpack - ); - - stream.Initialize(); - return stream; } public async Task OpenEntryStreamAsync(CancellationToken cancellationToken = default) { - IRarUnpack unpack; - bool ownsUnpack; + var readStream = new MultiVolumeReadOnlyStream(Parts.Cast(), archive); // For solid archives, use shared Unpack instance (must be processed sequentially) - // For non-solid archives, create new instance per stream to support multi-threading + // For non-solid archives, use factory to create owned instance (supports multi-threading) if (archive.IsSolid) { - unpack = IsRarV3 ? archive.UnpackV1.Value : archive.UnpackV2017.Value; - ownsUnpack = false; + var unpack = IsRarV3 ? archive.UnpackV1.Value : archive.UnpackV2017.Value; + var stream = new RarStream(unpack, FileHeader, readStream, ownsUnpack: false); + await stream.InitializeAsync(cancellationToken); + return stream; } else { - unpack = IsRarV3 - ? new Compressors.Rar.UnpackV1.Unpack() - : new Compressors.Rar.UnpackV2017.Unpack(); - ownsUnpack = true; + var factory = IsRarV3 + ? (IRarUnpackFactory)UnpackV1Factory.Instance + : UnpackV2017Factory.Instance; + var stream = new RarStream(factory, FileHeader, readStream); + await stream.InitializeAsync(cancellationToken); + return stream; } - - var stream = new RarStream( - unpack, - FileHeader, - new MultiVolumeReadOnlyStream(Parts.Cast(), archive), - ownsUnpack - ); - - await stream.InitializeAsync(cancellationToken); - return stream; } public bool IsComplete diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpackFactory.cs b/src/SharpCompress/Compressors/Rar/IRarUnpackFactory.cs new file mode 100644 index 00000000..b538a0c9 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/IRarUnpackFactory.cs @@ -0,0 +1,34 @@ +namespace SharpCompress.Compressors.Rar; + +/// +/// Factory interface for creating IRarUnpack instances. +/// Each created instance is owned by the caller and should be disposed when done. +/// +internal interface IRarUnpackFactory +{ + /// + /// Creates a new IRarUnpack instance. + /// The caller is responsible for disposing the returned instance. + /// + IRarUnpack Create(); +} + +/// +/// Factory for creating UnpackV1 instances (RAR v3 and earlier). +/// +internal sealed class UnpackV1Factory : IRarUnpackFactory +{ + public static readonly UnpackV1Factory Instance = new(); + + public IRarUnpack Create() => new UnpackV1.Unpack(); +} + +/// +/// Factory for creating UnpackV2017 instances (RAR v5+). +/// +internal sealed class UnpackV2017Factory : IRarUnpackFactory +{ + public static readonly UnpackV2017Factory Instance = new(); + + public IRarUnpack Create() => new UnpackV2017.Unpack(); +} diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs index 693d284b..4fa4580b 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -106,11 +106,30 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack byte[] _hash = { }; private RarBLAKE2spStream( - IRarUnpack unpack, + IRarUnpackFactory unpackFactory, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream ) - : base(unpack, fileHeader, readStream) + : base(unpackFactory, fileHeader, readStream) + { + this.readStream = readStream; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RarBLAKE2spStream)); +#endif + disableCRCCheck = fileHeader.IsEncrypted; + _hash = fileHeader.FileCrc.NotNull(); + _blake2sp = new BLAKE2SP(); + ResetCrc(); + } + + private RarBLAKE2spStream( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + bool ownsUnpack + ) + : base(unpack, fileHeader, readStream, ownsUnpack) { this.readStream = readStream; @@ -124,24 +143,49 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack } public static RarBLAKE2spStream Create( - IRarUnpack unpack, + IRarUnpackFactory unpackFactory, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream ) { - var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream); + var stream = new RarBLAKE2spStream(unpackFactory, fileHeader, readStream); stream.Initialize(); return stream; } + public static RarBLAKE2spStream Create( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + bool ownsUnpack + ) + { + var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream, ownsUnpack); + stream.Initialize(); + return stream; + } + + public static async Task CreateAsync( + IRarUnpackFactory unpackFactory, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + CancellationToken cancellationToken = default + ) + { + var stream = new RarBLAKE2spStream(unpackFactory, fileHeader, readStream); + await stream.InitializeAsync(cancellationToken); + return stream; + } + public static async Task CreateAsync( IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream, + bool ownsUnpack, CancellationToken cancellationToken = default ) { - var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream); + var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream, ownsUnpack); await stream.InitializeAsync(cancellationToken); return stream; } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 0ec0527d..ba0a4391 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -34,11 +34,27 @@ internal class RarCrcStream : RarStream, IStreamStack private readonly bool disableCRC; private RarCrcStream( - IRarUnpack unpack, + IRarUnpackFactory unpackFactory, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream ) - : base(unpack, fileHeader, readStream) + : base(unpackFactory, fileHeader, readStream) + { + this.readStream = readStream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RarCrcStream)); +#endif + disableCRC = fileHeader.IsEncrypted; + ResetCrc(); + } + + private RarCrcStream( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + bool ownsUnpack + ) + : base(unpack, fileHeader, readStream, ownsUnpack) { this.readStream = readStream; #if DEBUG_STREAMS @@ -49,24 +65,49 @@ internal class RarCrcStream : RarStream, IStreamStack } public static RarCrcStream Create( - IRarUnpack unpack, + IRarUnpackFactory unpackFactory, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream ) { - var stream = new RarCrcStream(unpack, fileHeader, readStream); + var stream = new RarCrcStream(unpackFactory, fileHeader, readStream); stream.Initialize(); return stream; } + public static RarCrcStream Create( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + bool ownsUnpack + ) + { + var stream = new RarCrcStream(unpack, fileHeader, readStream, ownsUnpack); + stream.Initialize(); + return stream; + } + + public static async Task CreateAsync( + IRarUnpackFactory unpackFactory, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream, + CancellationToken cancellationToken = default + ) + { + var stream = new RarCrcStream(unpackFactory, fileHeader, readStream); + await stream.InitializeAsync(cancellationToken); + return stream; + } + public static async Task CreateAsync( IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream, + bool ownsUnpack, CancellationToken cancellationToken = default ) { - var stream = new RarCrcStream(unpack, fileHeader, readStream); + var stream = new RarCrcStream(unpack, fileHeader, readStream, ownsUnpack); await stream.InitializeAsync(cancellationToken); return stream; } diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index e3bb056e..91f3c0fa 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -50,12 +50,23 @@ internal class RarStream : Stream, IStreamStack private bool isDisposed; private long _position; - public RarStream( - IRarUnpack unpack, - FileHeader fileHeader, - Stream readStream, - bool ownsUnpack = false - ) + /// + /// Creates a new RarStream that owns and will dispose its IRarUnpack instance. + /// + /// Factory to create the IRarUnpack instance + /// File header for the entry + /// Stream to read compressed data from + public RarStream(IRarUnpackFactory unpackFactory, FileHeader fileHeader, Stream readStream) + : this(unpackFactory.Create(), fileHeader, readStream, ownsUnpack: true) { } + + /// + /// Creates a new RarStream with the specified unpack instance. + /// + /// The IRarUnpack instance to use + /// File header for the entry + /// Stream to read compressed data from + /// Whether this stream should dispose the unpack instance + internal RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream, bool ownsUnpack) { this.unpack = unpack; this.fileHeader = fileHeader; @@ -92,7 +103,7 @@ internal class RarStream : Stream, IStreamStack ArrayPool.Shared.Return(this.tmpBuffer); this.tmpBuffer = null; - // Only dispose the unpack instance if we own it + // Dispose the unpack instance if we own it if (ownsUnpack && unpack is IDisposable disposableUnpack) { disposableUnpack.Dispose(); diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 67af2684..0650717d 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -15,6 +15,8 @@ public abstract class RarReader : AbstractReader { private bool _disposed; private RarVolume? volume; + + // Shared Unpack instances for solid archives (must be used sequentially) private Lazy UnpackV2017 { get; } = new(() => new Compressors.Rar.UnpackV2017.Unpack()); private Lazy UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack()); @@ -111,19 +113,50 @@ public abstract class RarReader : AbstractReader CreateFilePartEnumerableForCurrentEntry().Cast(), this ); - if (Entry.IsRarV3) - { - return CreateEntryStream(RarCrcStream.Create(UnpackV1.Value, Entry.FileHeader, stream)); - } - if (Entry.FileHeader.FileCrc?.Length > 5) + // For solid archives, use shared Unpack instance (must be processed sequentially) + // For non-solid archives, use factory to create owned instance + if (Entry.IsSolid || Entry.FileHeader.IsSolid) { + var unpack = Entry.IsRarV3 ? UnpackV1.Value : UnpackV2017.Value; + if (Entry.IsRarV3) + { + return CreateEntryStream( + RarCrcStream.Create(unpack, Entry.FileHeader, stream, ownsUnpack: false) + ); + } + + if (Entry.FileHeader.FileCrc?.Length > 5) + { + return CreateEntryStream( + RarBLAKE2spStream.Create(unpack, Entry.FileHeader, stream, ownsUnpack: false) + ); + } + return CreateEntryStream( - RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream) + RarCrcStream.Create(unpack, Entry.FileHeader, stream, ownsUnpack: false) ); } + else + { + var factory = Entry.IsRarV3 + ? (IRarUnpackFactory)UnpackV1Factory.Instance + : UnpackV2017Factory.Instance; - return CreateEntryStream(RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream)); + if (Entry.IsRarV3) + { + return CreateEntryStream(RarCrcStream.Create(factory, Entry.FileHeader, stream)); + } + + if (Entry.FileHeader.FileCrc?.Length > 5) + { + return CreateEntryStream( + RarBLAKE2spStream.Create(factory, Entry.FileHeader, stream) + ); + } + + return CreateEntryStream(RarCrcStream.Create(factory, Entry.FileHeader, stream)); + } } protected override async System.Threading.Tasks.Task GetEntryStreamAsync( @@ -139,28 +172,83 @@ public abstract class RarReader : AbstractReader CreateFilePartEnumerableForCurrentEntry().Cast(), this ); - if (Entry.IsRarV3) + + // For solid archives, use shared Unpack instance (must be processed sequentially) + // For non-solid archives, use factory to create owned instance + if (Entry.IsSolid || Entry.FileHeader.IsSolid) { + var unpack = Entry.IsRarV3 ? UnpackV1.Value : UnpackV2017.Value; + if (Entry.IsRarV3) + { + return CreateEntryStream( + await RarCrcStream + .CreateAsync( + unpack, + Entry.FileHeader, + stream, + ownsUnpack: false, + cancellationToken + ) + .ConfigureAwait(false) + ); + } + + if (Entry.FileHeader.FileCrc?.Length > 5) + { + return CreateEntryStream( + await RarBLAKE2spStream + .CreateAsync( + unpack, + Entry.FileHeader, + stream, + ownsUnpack: false, + cancellationToken + ) + .ConfigureAwait(false) + ); + } + return CreateEntryStream( await RarCrcStream - .CreateAsync(UnpackV1.Value, Entry.FileHeader, stream, cancellationToken) + .CreateAsync( + unpack, + Entry.FileHeader, + stream, + ownsUnpack: false, + cancellationToken + ) .ConfigureAwait(false) ); } - - if (Entry.FileHeader.FileCrc?.Length > 5) + else { + var factory = Entry.IsRarV3 + ? (IRarUnpackFactory)UnpackV1Factory.Instance + : UnpackV2017Factory.Instance; + + if (Entry.IsRarV3) + { + return CreateEntryStream( + await RarCrcStream + .CreateAsync(factory, Entry.FileHeader, stream, cancellationToken) + .ConfigureAwait(false) + ); + } + + if (Entry.FileHeader.FileCrc?.Length > 5) + { + return CreateEntryStream( + await RarBLAKE2spStream + .CreateAsync(factory, Entry.FileHeader, stream, cancellationToken) + .ConfigureAwait(false) + ); + } + return CreateEntryStream( - await RarBLAKE2spStream - .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) + await RarCrcStream + .CreateAsync(factory, Entry.FileHeader, stream, cancellationToken) .ConfigureAwait(false) ); } - - return CreateEntryStream( - await RarCrcStream - .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false) - ); } }