From 4df952db1b6be812c4cfcb2ee8f072be234f0ca8 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Mon, 12 Jan 2026 16:32:26 +0000 Subject: [PATCH] split out factories for archive --- .../Archives/ArchiveFactory.Open.cs | 347 +++++++++++++++ src/SharpCompress/Archives/ArchiveFactory.cs | 416 +----------------- .../Archives/GZip/GZipArchive.Factory.cs | 171 +++++++ .../Archives/GZip/GZipArchive.cs | 218 +-------- .../Archives/Rar/RarArchive.Factory.cs | 148 +++++++ src/SharpCompress/Archives/Rar/RarArchive.cs | 194 +------- .../SevenZip/SevenZipArchive.Factory.cs | 151 +++++++ .../Archives/SevenZip/SevenZipArchive.cs | 224 +--------- .../Archives/Tar/TarArchive.Factory.cs | 162 +++++++ src/SharpCompress/Archives/Tar/TarArchive.cs | 203 +-------- .../Archives/Zip/ZipArchive.Factory.cs | 317 +++++++++++++ src/SharpCompress/Archives/Zip/ZipArchive.cs | 373 +--------------- 12 files changed, 1322 insertions(+), 1602 deletions(-) create mode 100644 src/SharpCompress/Archives/ArchiveFactory.Open.cs create mode 100644 src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs create mode 100644 src/SharpCompress/Archives/Rar/RarArchive.Factory.cs create mode 100644 src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs create mode 100644 src/SharpCompress/Archives/Tar/TarArchive.Factory.cs create mode 100644 src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs diff --git a/src/SharpCompress/Archives/ArchiveFactory.Open.cs b/src/SharpCompress/Archives/ArchiveFactory.Open.cs new file mode 100644 index 00000000..25a104ec --- /dev/null +++ b/src/SharpCompress/Archives/ArchiveFactory.Open.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Factories; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Archives; + +public static partial class ArchiveFactory +{ + public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + readerOptions ??= new ReaderOptions(); + stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); + return FindFactory(stream).Open(stream, readerOptions); + } + + public static async ValueTask OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + readerOptions ??= new ReaderOptions(); + stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); + var factory = await FindFactoryAsync(stream, cancellationToken); + return factory.OpenAsync(stream, readerOptions); + } + + public static IWritableArchive Create(ArchiveType type) + { + var factory = Factory + .Factories.OfType() + .FirstOrDefault(item => item.KnownArchiveType == type); + + if (factory != null) + { + return factory.CreateWriteableArchive(); + } + + throw new NotSupportedException("Cannot create Archives of type: " + type); + } + + public static IArchive Open(string filePath, ReaderOptions? options = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), options); + } + + public static ValueTask OpenAsync( + string filePath, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenAsync(new FileInfo(filePath), options, cancellationToken); + } + + public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + return FindFactory(fileInfo).Open(fileInfo, options); + } + + public static async ValueTask OpenAsync( + FileInfo fileInfo, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsync(fileInfo, options, cancellationToken); + } + + public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) + { + fileInfos.NotNull(nameof(fileInfos)); + var filesArray = fileInfos.ToArray(); + if (filesArray.Length == 0) + { + throw new InvalidOperationException("No files to open"); + } + + var fileInfo = filesArray[0]; + if (filesArray.Length == 1) + { + return Open(fileInfo, options); + } + + fileInfo.NotNull(nameof(fileInfo)); + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + return FindFactory(fileInfo).Open(filesArray, options); + } + + public static async ValueTask OpenAsync( + IEnumerable fileInfos, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var filesArray = fileInfos.ToArray(); + if (filesArray.Length == 0) + { + throw new InvalidOperationException("No files to open"); + } + + var fileInfo = filesArray[0]; + if (filesArray.Length == 1) + { + return await OpenAsync(fileInfo, options, cancellationToken); + } + + fileInfo.NotNull(nameof(fileInfo)); + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsync(filesArray, options, cancellationToken); + } + + public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) + { + streams.NotNull(nameof(streams)); + var streamsArray = streams.ToArray(); + if (streamsArray.Length == 0) + { + throw new InvalidOperationException("No streams"); + } + + var firstStream = streamsArray[0]; + if (streamsArray.Length == 1) + { + return Open(firstStream, options); + } + + firstStream.NotNull(nameof(firstStream)); + options ??= new ReaderOptions(); + + return FindFactory(firstStream).Open(streamsArray, options); + } + + public static async ValueTask OpenAsync( + IEnumerable streams, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + streams.NotNull(nameof(streams)); + var streamsArray = streams.ToArray(); + if (streamsArray.Length == 0) + { + throw new InvalidOperationException("No streams"); + } + + var firstStream = streamsArray[0]; + if (streamsArray.Length == 1) + { + return await OpenAsync(firstStream, options, cancellationToken); + } + + firstStream.NotNull(nameof(firstStream)); + options ??= new ReaderOptions(); + + var factory = FindFactory(firstStream); + return factory.OpenAsync(streamsArray, options); + } + + public static void WriteToDirectory( + string sourceArchive, + string destinationDirectory, + ExtractionOptions? options = null + ) + { + using var archive = Open(sourceArchive); + archive.WriteToDirectory(destinationDirectory, options); + } + + private static T FindFactory(FileInfo finfo) + where T : IFactory + { + finfo.NotNull(nameof(finfo)); + using Stream stream = finfo.OpenRead(); + return FindFactory(stream); + } + + private static T FindFactory(Stream stream) + where T : IFactory + { + stream.NotNull(nameof(stream)); + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException("Stream should be readable and seekable"); + } + + var factories = Factory.Factories.OfType(); + + var startPosition = stream.Position; + + foreach (var factory in factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + if (factory.IsArchive(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + return factory; + } + } + + var extensions = string.Join(", ", factories.Select(item => item.Name)); + + throw new InvalidOperationException( + $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" + ); + } + + private static async ValueTask FindFactoryAsync( + FileInfo finfo, + CancellationToken cancellationToken + ) + where T : IFactory + { + finfo.NotNull(nameof(finfo)); + using Stream stream = finfo.OpenRead(); + return await FindFactoryAsync(stream, cancellationToken); + } + + private static async ValueTask FindFactoryAsync( + Stream stream, + CancellationToken cancellationToken + ) + where T : IFactory + { + stream.NotNull(nameof(stream)); + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException("Stream should be readable and seekable"); + } + + var factories = Factory.Factories.OfType(); + + var startPosition = stream.Position; + + foreach (var factory in factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + return factory; + } + } + + var extensions = string.Join(", ", factories.Select(item => item.Name)); + + throw new InvalidOperationException( + $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" + ); + } + + public static bool IsArchive( + string filePath, + out ArchiveType? type, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + using Stream s = File.OpenRead(filePath); + return IsArchive(s, out type, bufferSize); + } + + public static bool IsArchive( + Stream stream, + out ArchiveType? type, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + type = null; + stream.NotNull(nameof(stream)); + + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException("Stream should be readable and seekable"); + } + + var startPosition = stream.Position; + + foreach (var factory in Factory.Factories) + { + var isArchive = factory.IsArchive(stream); + stream.Position = startPosition; + + if (isArchive) + { + type = factory.KnownArchiveType; + return true; + } + } + + return false; + } + + public static IEnumerable GetFileParts(string part1) + { + part1.NotNullOrEmpty(nameof(part1)); + return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); + } + + public static IEnumerable GetFileParts(FileInfo part1) + { + part1.NotNull(nameof(part1)); + yield return part1; + + foreach (var factory in Factory.Factories.OfType()) + { + var i = 1; + var part = factory.GetFilePart(i++, part1); + + if (part != null) + { + yield return part; + while ((part = factory.GetFilePart(i++, part1)) != null) + { + yield return part; + } + + yield break; + } + } + } + + public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory(); +} diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index fb1addc0..a6073240 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -1,417 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using SharpCompress.Common; -using SharpCompress.Factories; -using SharpCompress.IO; -using SharpCompress.Readers; - namespace SharpCompress.Archives; -public static class ArchiveFactory -{ - /// - /// Opens an Archive for random access - /// - /// - /// - /// - public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - readerOptions ??= new ReaderOptions(); - stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); - return FindFactory(stream).Open(stream, readerOptions); - } - - /// - /// Opens an Archive for random access asynchronously - /// - /// - /// - /// - /// - public static async ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - readerOptions ??= new ReaderOptions(); - stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); - var factory = await FindFactoryAsync(stream, cancellationToken); - return factory.OpenAsync(stream, readerOptions); - } - - public static IWritableArchive Create(ArchiveType type) - { - var factory = Factory - .Factories.OfType() - .FirstOrDefault(item => item.KnownArchiveType == type); - - if (factory != null) - { - return factory.CreateWriteableArchive(); - } - - throw new NotSupportedException("Cannot create Archives of type: " + type); - } - - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IArchive Open(string filePath, ReaderOptions? options = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), options); - } - - /// - /// Opens an Archive from a filepath asynchronously. - /// - /// - /// - /// - public static ValueTask OpenAsync( - string filePath, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsync(new FileInfo(filePath), options, cancellationToken); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - return FindFactory(fileInfo).Open(fileInfo, options); - } - - /// - /// Opens an Archive from a FileInfo object asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - var factory = await FindFactoryAsync(fileInfo, cancellationToken); - return factory.OpenAsync(fileInfo, options, cancellationToken); - } - - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) - { - fileInfos.NotNull(nameof(fileInfos)); - var filesArray = fileInfos.ToArray(); - if (filesArray.Length == 0) - { - throw new InvalidOperationException("No files to open"); - } - - var fileInfo = filesArray[0]; - if (filesArray.Length == 1) - { - return Open(fileInfo, options); - } - - fileInfo.NotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - return FindFactory(fileInfo).Open(filesArray, options); - } - - /// - /// Opens a multi-part archive from files asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( - IEnumerable fileInfos, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var filesArray = fileInfos.ToArray(); - if (filesArray.Length == 0) - { - throw new InvalidOperationException("No files to open"); - } - - var fileInfo = filesArray[0]; - if (filesArray.Length == 1) - { - return await OpenAsync(fileInfo, options, cancellationToken); - } - - fileInfo.NotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - var factory = await FindFactoryAsync(fileInfo, cancellationToken); - return factory.OpenAsync(filesArray, options, cancellationToken); - } - - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) - { - streams.NotNull(nameof(streams)); - var streamsArray = streams.ToArray(); - if (streamsArray.Length == 0) - { - throw new InvalidOperationException("No streams"); - } - - var firstStream = streamsArray[0]; - if (streamsArray.Length == 1) - { - return Open(firstStream, options); - } - - firstStream.NotNull(nameof(firstStream)); - options ??= new ReaderOptions(); - - return FindFactory(firstStream).Open(streamsArray, options); - } - - /// - /// Opens a multi-part archive from streams asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( - IEnumerable streams, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - streams.NotNull(nameof(streams)); - var streamsArray = streams.ToArray(); - if (streamsArray.Length == 0) - { - throw new InvalidOperationException("No streams"); - } - - var firstStream = streamsArray[0]; - if (streamsArray.Length == 1) - { - return await OpenAsync(firstStream, options, cancellationToken); - } - - firstStream.NotNull(nameof(firstStream)); - options ??= new ReaderOptions(); - - var factory = FindFactory(firstStream); - return factory.OpenAsync(streamsArray, options); - } - - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteToDirectory( - string sourceArchive, - string destinationDirectory, - ExtractionOptions? options = null - ) - { - using var archive = Open(sourceArchive); - archive.WriteToDirectory(destinationDirectory, options); - } - - private static T FindFactory(FileInfo finfo) - where T : IFactory - { - finfo.NotNull(nameof(finfo)); - using Stream stream = finfo.OpenRead(); - return FindFactory(stream); - } - - private static T FindFactory(Stream stream) - where T : IFactory - { - stream.NotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - - var factories = Factory.Factories.OfType(); - - var startPosition = stream.Position; - - foreach (var factory in factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - if (factory.IsArchive(stream)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - return factory; - } - } - - var extensions = string.Join(", ", factories.Select(item => item.Name)); - - throw new InvalidOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); - } - - private static async ValueTask FindFactoryAsync( - FileInfo finfo, - CancellationToken cancellationToken - ) - where T : IFactory - { - finfo.NotNull(nameof(finfo)); - using Stream stream = finfo.OpenRead(); - return await FindFactoryAsync(stream, cancellationToken); - } - - private static async ValueTask FindFactoryAsync( - Stream stream, - CancellationToken cancellationToken - ) - where T : IFactory - { - stream.NotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - - var factories = Factory.Factories.OfType(); - - var startPosition = stream.Position; - - foreach (var factory in factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - return factory; - } - } - - var extensions = string.Join(", ", factories.Select(item => item.Name)); - - throw new InvalidOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); - } - - public static bool IsArchive( - string filePath, - out ArchiveType? type, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - using Stream s = File.OpenRead(filePath); - return IsArchive(s, out type, bufferSize); - } - - public static bool IsArchive( - Stream stream, - out ArchiveType? type, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - type = null; - stream.NotNull(nameof(stream)); - - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - var isArchive = factory.IsArchive(stream); - stream.Position = startPosition; - - if (isArchive) - { - type = factory.KnownArchiveType; - return true; - } - } - - return false; - } - - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// - public static IEnumerable GetFileParts(string part1) - { - part1.NotNullOrEmpty(nameof(part1)); - return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); - } - - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// - public static IEnumerable GetFileParts(FileInfo part1) - { - part1.NotNull(nameof(part1)); - yield return part1; - - foreach (var factory in Factory.Factories.OfType()) - { - var i = 1; - var part = factory.GetFilePart(i++, part1); - - if (part != null) - { - yield return part; - while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too - { - yield return part; - } - - yield break; - } - } - } - - public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory(); -} +public static partial class ArchiveFactory { } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs new file mode 100644 index 00000000..ca6cd6a3 --- /dev/null +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.GZip; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.GZip; +using SharpCompress.Writers; +using SharpCompress.Writers.GZip; + +namespace SharpCompress.Archives.GZip; + +public partial class GZipArchive +{ + public static IArchive Open(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return new GZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new GZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new GZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new GZipArchive( + new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfos, readerOptions); + } + + public static GZipArchive Create() => new(); + + public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); + + public static bool IsGZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + + using Stream stream = fileInfo.OpenRead(); + return IsGZipFile(stream); + } + + public static bool IsGZipFile(Stream stream) + { + Span header = stackalloc byte[10]; + + if (!stream.ReadFully(header)) + { + return false; + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + return false; + } + + return true; + } + + public static async ValueTask IsGZipFileAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + byte[] header = new byte[10]; + + if (!await stream.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false)) + { + return false; + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + return false; + } + + return true; + } +} diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index b4c1b86f..03503fb8 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -14,186 +14,20 @@ using SharpCompress.Writers.GZip; namespace SharpCompress.Archives.GZip; -public class GZipArchive : AbstractWritableArchive +public partial class GZipArchive : AbstractWritableArchive { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new GZipArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static IArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new GZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new GZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new GZipArchive( - new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a GZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(stream, readerOptions); - } - - /// - /// Opens a GZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfo, readerOptions); - } - - /// - /// Opens a GZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(streams, readerOptions); - } - - /// - /// Opens a GZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfos, readerOptions); - } - - public static GZipArchive Create() => new(); - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private GZipArchive(SourceStream sourceStream) : base(ArchiveType.GZip, sourceStream) { } + internal GZipArchive() + : base(ArchiveType.GZip) { } + protected override IEnumerable LoadVolumes(SourceStream sourceStream) { sourceStream.LoadAllParts(); return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0)); } - public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); - - public static bool IsGZipFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - - using Stream stream = fileInfo.OpenRead(); - return IsGZipFile(stream); - } - public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath)); public void SaveTo(FileInfo fileInfo) @@ -215,50 +49,6 @@ public class GZipArchive : AbstractWritableArchive .ConfigureAwait(false); } - public static bool IsGZipFile(Stream stream) - { - // read the header on the first read - Span header = stackalloc byte[10]; - - // workitem 8501: handle edge case (decompress empty stream) - if (!stream.ReadFully(header)) - { - return false; - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - return false; - } - - return true; - } - - public static async ValueTask IsGZipFileAsync( - Stream stream, - CancellationToken cancellationToken = default - ) - { - // read the header on the first read - byte[] header = new byte[10]; - - // workitem 8501: handle edge case (decompress empty stream) - if (!await stream.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false)) - { - return false; - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - return false; - } - - return true; - } - - internal GZipArchive() - : base(ArchiveType.GZip) { } - protected override GZipArchiveEntry CreateEntryInternal( string filePath, Stream source, diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs new file mode 100644 index 00000000..f5dc8383 --- /dev/null +++ b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using SharpCompress.Common; +using SharpCompress.Common.Rar; +using SharpCompress.Common.Rar.Headers; +using SharpCompress.Compressors.Rar; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Rar; + +namespace SharpCompress.Archives.Rar; + +public partial class RarArchive +{ + public static IRarArchive Open(string filePath, ReaderOptions? options = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + var fileInfo = new FileInfo(filePath); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive Open(FileInfo fileInfo, ReaderOptions? options = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive Open(Stream stream, ReaderOptions? options = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions())); + } + + public static IRarArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new RarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new RarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IRarAsyncArchive OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)Open(stream, readerOptions); + } + + public static IRarAsyncArchive OpenAsync( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)Open(fileInfo, readerOptions); + } + + public static IRarAsyncArchive OpenAsync( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)Open(streams, readerOptions); + } + + public static IRarAsyncArchive OpenAsync( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)Open(fileInfos, readerOptions); + } + + public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); + + public static bool IsRarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsRarFile(stream); + } + + public static bool IsRarFile(Stream stream, ReaderOptions? options = null) + { + try + { + MarkHeader.Read(stream, true, false); + return true; + } + catch + { + return false; + } + } +} diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index 05b41494..d67dd15c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -24,17 +24,13 @@ public interface IRarArchive : IArchive, IRarArchiveCommon { } public interface IRarAsyncArchive : IAsyncArchive, IRarArchiveCommon { } -public class RarArchive : AbstractArchive, IRarArchive +public partial class RarArchive : AbstractArchive, IRarArchive { private bool _disposed; internal Lazy UnpackV2017 { get; } = new(() => new Compressors.Rar.UnpackV2017.Unpack()); internal Lazy UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack()); - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private RarArchive(SourceStream sourceStream) : base(ArchiveType.Rar, sourceStream) { } @@ -57,10 +53,10 @@ public class RarArchive : AbstractArchive, IRarArchi protected override IEnumerable LoadVolumes(SourceStream sourceStream) { - sourceStream.LoadAllParts(); //request all streams + sourceStream.LoadAllParts(); var streams = sourceStream.Streams.ToArray(); var i = 0; - if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split + if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) { sourceStream.IsVolumes = true; streams[1].Position = 0; @@ -73,7 +69,6 @@ public class RarArchive : AbstractArchive, IRarArchi )); } - //split mode or single file return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable(); } @@ -106,187 +101,4 @@ public class RarArchive : AbstractArchive, IRarArchi public virtual int MinVersion => Volumes.First().MinVersion; public virtual int MaxVersion => Volumes.First().MaxVersion; - - #region Creation - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IRarArchive Open(string filePath, ReaderOptions? options = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - var fileInfo = new FileInfo(filePath); - return new RarArchive( - new SourceStream( - fileInfo, - i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), - options ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IRarArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new RarArchive( - new SourceStream( - fileInfo, - i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), - options ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static IRarArchive Open(Stream stream, ReaderOptions? options = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions())); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static IRarArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new RarArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static IRarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new RarArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Opens a RarArchive asynchronously from a stream. - /// - /// - /// - /// - public static IRarAsyncArchive OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IRarAsyncArchive)Open(stream, readerOptions); - } - - /// - /// Opens a RarArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static IRarAsyncArchive OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IRarAsyncArchive)Open(fileInfo, readerOptions); - } - - /// - /// Opens a RarArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static IRarAsyncArchive OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IRarAsyncArchive)Open(streams, readerOptions); - } - - /// - /// Opens a RarArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static IRarAsyncArchive OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IRarAsyncArchive)Open(fileInfos, readerOptions); - } - - public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); - - public static bool IsRarFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsRarFile(stream); - } - - public static bool IsRarFile(Stream stream, ReaderOptions? options = null) - { - try - { - MarkHeader.Read(stream, true, false); - return true; - } - catch - { - return false; - } - } - - #endregion } diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs new file mode 100644 index 00000000..9aaf1ae5 --- /dev/null +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.SevenZip; +using SharpCompress.Compressors.LZMA.Utilites; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Archives.SevenZip; + +public partial class SevenZipArchive +{ + public static IArchive Open(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty("filePath"); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull("fileInfo"); + return new SevenZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new SevenZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new SevenZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull("stream"); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new SevenZipArchive( + new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IAsyncArchive OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)Open(stream, readerOptions); + } + + public static IAsyncArchive OpenAsync( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)Open(fileInfo, readerOptions); + } + + public static IAsyncArchive OpenAsync( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)Open(streams, readerOptions); + } + + public static IAsyncArchive OpenAsync( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)Open(fileInfos, readerOptions); + } + + public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); + + public static bool IsSevenZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsSevenZipFile(stream); + } + + public static bool IsSevenZipFile(Stream stream) + { + try + { + return SignatureMatch(stream); + } + catch + { + return false; + } + } + + private static ReadOnlySpan Signature => + new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; + + private static bool SignatureMatch(Stream stream) + { + var reader = new BinaryReader(stream); + ReadOnlySpan signatureBytes = reader.ReadBytes(6); + return signatureBytes.SequenceEqual(Signature); + } +} diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 8532e837..e6b511d8 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -12,188 +12,22 @@ using SharpCompress.Readers; namespace SharpCompress.Archives.SevenZip; -public class SevenZipArchive : AbstractArchive +public partial class SevenZipArchive : AbstractArchive { private ArchiveDatabase? _database; - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty("filePath"); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull("fileInfo"); - return new SevenZipArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static IArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new SevenZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static IArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new SevenZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull("stream"); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new SevenZipArchive( - new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a SevenZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static IAsyncArchive OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncArchive)Open(stream, readerOptions); - } - - /// - /// Opens a SevenZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static IAsyncArchive OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncArchive)Open(fileInfo, readerOptions); - } - - /// - /// Opens a SevenZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static IAsyncArchive OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncArchive)Open(streams, readerOptions); - } - - /// - /// Opens a SevenZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static IAsyncArchive OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncArchive)Open(fileInfos, readerOptions); - } - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private SevenZipArchive(SourceStream sourceStream) : base(ArchiveType.SevenZip, sourceStream) { } - protected override IEnumerable LoadVolumes(SourceStream sourceStream) - { - sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams - return new SevenZipVolume(sourceStream, ReaderOptions, 0).AsEnumerable(); //simple single volume or split, multivolume not supported - } - - public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); - - public static bool IsSevenZipFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsSevenZipFile(stream); - } - internal SevenZipArchive() : base(ArchiveType.SevenZip) { } + protected override IEnumerable LoadVolumes(SourceStream sourceStream) + { + sourceStream.NotNull("SourceStream is null").LoadAllParts(); + return new SevenZipVolume(sourceStream, ReaderOptions, 0).AsEnumerable(); + } + protected override IEnumerable LoadEntries( IEnumerable volumes ) @@ -219,7 +53,7 @@ public class SevenZipArchive : AbstractArchive Signature => - new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; - - private static bool SignatureMatch(Stream stream) - { - var reader = new BinaryReader(stream); - ReadOnlySpan signatureBytes = reader.ReadBytes(6); - return signatureBytes.SequenceEqual(Signature); - } - protected override IReader CreateReaderForSolidExtraction() => new SevenZipReader(ReaderOptions, this); @@ -295,9 +107,6 @@ public class SevenZipArchive : AbstractArchive !x.IsDirectory)) { _currentEntry = entry; @@ -307,13 +116,6 @@ public class SevenZipArchive : AbstractArchive - /// WORKAROUND: Forces async operations to use synchronous equivalents. - /// This is necessary because the LZMA decoder has bugs in its async implementation - /// that cause state corruption (IndexOutOfRangeException, DataErrorException). - /// - /// The proper fix would be to repair the LZMA decoder's async methods - /// (LzmaStream.ReadAsync, Decoder.CodeAsync, OutWindow async operations), - /// but that requires deep changes to the decoder state machine. - /// private sealed class SyncOnlyStream : Stream { private readonly Stream _baseStream; @@ -361,7 +154,6 @@ public class SevenZipArchive : AbstractArchive _baseStream.Write(buffer, offset, count); - // Force async operations to use sync equivalents to avoid LZMA decoder bugs public override Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs new file mode 100644 index 00000000..528c0954 --- /dev/null +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Tar; +using SharpCompress.Common.Tar.Headers; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Writers; +using SharpCompress.Writers.Tar; + +namespace SharpCompress.Archives.Tar; + +public partial class TarArchive +{ + public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return new TarArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new TarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new TarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new TarArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + string file, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(new FileInfo(file), readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfos, readerOptions); + } + + public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); + + public static bool IsTarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsTarFile(stream); + } + + public static bool IsTarFile(Stream stream) + { + try + { + var tarHeader = new TarHeader(new ArchiveEncoding()); + var readSucceeded = tarHeader.Read(new BinaryReader(stream)); + var isEmptyArchive = + tarHeader.Name?.Length == 0 + && tarHeader.Size == 0 + && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); + return readSucceeded || isEmptyArchive; + } + catch { } + return false; + } + + public static TarArchive Create() => new(); +} diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index f1c0254f..af06e47d 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -15,209 +15,14 @@ using SharpCompress.Writers.Tar; namespace SharpCompress.Archives.Tar; -public class TarArchive : AbstractWritableArchive +public partial class TarArchive : AbstractWritableArchive { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new TarArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static IWritableArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new TarArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static IWritableArchive Open( - IEnumerable streams, - ReaderOptions? readerOptions = null - ) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new TarArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new TarArchive( - new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a TarArchive asynchronously from a stream. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(stream, readerOptions); - } - - public static IWritableAsyncArchive OpenAsync( - string file, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(new FileInfo(file), readerOptions); - } - - /// - /// Opens a TarArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfo, readerOptions); - } - - /// - /// Opens a TarArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(streams, readerOptions); - } - - /// - /// Opens a TarArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfos, readerOptions); - } - - public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); - - public static bool IsTarFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsTarFile(stream); - } - - public static bool IsTarFile(Stream stream) - { - try - { - var tarHeader = new TarHeader(new ArchiveEncoding()); - var readSucceeded = tarHeader.Read(new BinaryReader(stream)); - var isEmptyArchive = - tarHeader.Name?.Length == 0 - && tarHeader.Size == 0 - && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); - return readSucceeded || isEmptyArchive; - } - catch { } - return false; - } - protected override IEnumerable LoadVolumes(SourceStream sourceStream) { - sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams - return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); //simple single volume or split, multivolume not supported + sourceStream.NotNull("SourceStream is null").LoadAllParts(); + return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private TarArchive(SourceStream sourceStream) : base(ArchiveType.Tar, sourceStream) { } @@ -282,8 +87,6 @@ public class TarArchive : AbstractWritableArchive } } - public static TarArchive Create() => new(); - protected override TarArchiveEntry CreateEntryInternal( string filePath, Stream source, diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs new file mode 100644 index 00000000..9fb47187 --- /dev/null +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Zip; +using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Writers; +using SharpCompress.Writers.Zip; + +namespace SharpCompress.Archives.Zip; + +public partial class ZipArchive +{ + public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return new ZipArchive( + new SourceStream( + fileInfo, + i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new ZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new ZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new ZipArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsync( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(path, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsync( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)Open(fileInfos, readerOptions); + } + + public static bool IsZipFile( + string filePath, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => IsZipFile(new FileInfo(filePath), password, bufferSize); + + public static bool IsZipFile( + FileInfo fileInfo, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsZipFile(stream, password, bufferSize); + } + + public static bool IsZipFile( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + return false; + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static bool IsZipMulti( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + if (stream.CanSeek) + { + var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); + var x = z.ReadSeekableHeader(stream, useSync: true).FirstOrDefault(); + return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; + } + else + { + return false; + } + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static async ValueTask IsZipFileAsync( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = await headerFactory + .ReadStreamHeaderAsync(stream) + .WhereAsync(x => x.ZipHeaderType != ZipHeaderType.Split) + .FirstOrDefaultAsync(); + if (header is null) + { + return false; + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static ZipArchive Create() => new(); + + public static async ValueTask IsZipMultiAsync( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + if (stream.CanSeek) + { + var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); + ZipHeader? x = null; + await foreach ( + var h in z.ReadSeekableHeaderAsync(stream) + .WithCancellation(cancellationToken) + ) + { + x = h; + break; + } + return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; + } + else + { + return false; + } + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } +} diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 159cc6f4..5f1c84d8 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -16,21 +16,12 @@ using SharpCompress.Writers.Zip; namespace SharpCompress.Archives.Zip; -public class ZipArchive : AbstractWritableArchive +public partial class ZipArchive : AbstractWritableArchive { private readonly SeekableZipHeaderFactory? headerFactory; - /// - /// Gets or sets the compression level applied to files added to the archive, - /// if the compression method is set to deflate - /// public CompressionLevel DeflateCompressionLevel { get; set; } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// internal ZipArchive(SourceStream sourceStream) : base(ArchiveType.Zip, sourceStream) => headerFactory = new SeekableZipHeaderFactory( @@ -38,384 +29,36 @@ public class ZipArchive : AbstractWritableArchive sourceStream.ReaderOptions.ArchiveEncoding ); - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IWritableArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IWritableArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new ZipArchive( - new SourceStream( - fileInfo, - i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static IWritableArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new ZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static IWritableArchive Open( - IEnumerable streams, - ReaderOptions? readerOptions = null - ) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new ZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static IWritableArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new ZipArchive( - new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) - ); - } - - public static IWritableAsyncArchive OpenAsync( - string path, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(path, readerOptions); - } - - /// - /// Opens a ZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(stream, readerOptions); - } - - /// - /// Opens a ZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfo, readerOptions); - } - - /// - /// Opens a ZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(streams, readerOptions); - } - - /// - /// Opens a ZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static IWritableAsyncArchive OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)Open(fileInfos, readerOptions); - } - - public static bool IsZipFile( - string filePath, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) => IsZipFile(new FileInfo(filePath), password, bufferSize); - - public static bool IsZipFile( - FileInfo fileInfo, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsZipFile(stream, password, bufferSize); - } - - public static bool IsZipFile( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - return false; - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static bool IsZipMulti( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe - { - var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); - var x = z.ReadSeekableHeader(stream, useSync: true).FirstOrDefault(); - return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; - } - else - { - return false; - } - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static async ValueTask IsZipFileAsync( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = await headerFactory - .ReadStreamHeaderAsync(stream) - .WhereAsync(x => x.ZipHeaderType != ZipHeaderType.Split) - .FirstOrDefaultAsync(); - if (header is null) - { - return false; - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static async ValueTask IsZipMultiAsync( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe - { - var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); - ZipHeader? x = null; - await foreach ( - var h in z.ReadSeekableHeaderAsync(stream) - .WithCancellation(cancellationToken) - ) - { - x = h; - break; - } - return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; - } - else - { - return false; - } - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } + internal ZipArchive() + : base(ArchiveType.Zip) { } protected override IEnumerable LoadVolumes(SourceStream stream) { - stream.LoadAllParts(); //request all streams + stream.LoadAllParts(); stream.Position = 0; var streams = stream.Streams.ToList(); var idx = 0; - if (streams.Count() > 1) //test part 2 - true = multipart not split + if (streams.Count() > 1) { - streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception + streams[1].Position += 4; var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize); streams[1].Position -= 4; if (isZip) { stream.IsVolumes = true; - var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end + var tmp = streams[0]; streams.RemoveAt(0); streams.Add(tmp); - //streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++)); } } - //split mode or single file return new ZipVolume(stream, ReaderOptions, idx++).AsEnumerable(); } - internal ZipArchive() - : base(ArchiveType.Zip) { } - protected override IEnumerable LoadEntries(IEnumerable volumes) { var vols = volumes.ToArray(); @@ -597,8 +240,6 @@ public class ZipArchive : AbstractWritableArchive DateTime? modified ) => new ZipWritableArchiveEntry(this, directoryPath, modified); - public static ZipArchive Create() => new(); - protected override IReader CreateReaderForSolidExtraction() { var stream = Volumes.Single().Stream;