Merge pull request #1299 from adamhathcock/copilot/add-archive-detection-utility

This commit is contained in:
Adam Hathcock
2026-04-27 18:03:00 +01:00
committed by GitHub
22 changed files with 1003 additions and 168 deletions

View File

@@ -190,6 +190,7 @@ SharpCompress supports multiple archive and compression formats:
### Validation Expectations
- Run targeted tests for the changed area first.
- On non-Windows machines, avoid net48 test runs unless Mono is installed; use framework-specific validation such as `--framework net10.0` instead.
- Run `dotnet csharpier format .` after code edits.
- Run `dotnet csharpier check .` before handing off changes.

View File

@@ -1,11 +1,9 @@
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.Readers;
namespace SharpCompress.Archives;
@@ -109,61 +107,4 @@ public static partial class ArchiveFactory
.OpenAsyncArchive(streamsArray, options, cancellationToken)
.ConfigureAwait(false);
}
internal static ValueTask<T> FindFactoryAsync<T>(
string filePath,
CancellationToken cancellationToken = default
)
where T : IFactory
{
filePath.NotNullOrEmpty(nameof(filePath));
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
FileInfo finfo,
CancellationToken cancellationToken = default
)
where T : IFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken = default
)
where T : IFactory
{
stream.RequireReadable();
stream.RequireSeekable();
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (
await factory
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false)
)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
}

View File

@@ -0,0 +1,264 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
public static partial class ArchiveFactory
{
/// <summary>
/// Returns information about the archive at the given file path asynchronously,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
string filePath,
CancellationToken cancellationToken = default
) =>
await GetArchiveInformationAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
.ConfigureAwait(false);
/// <summary>
/// Returns information about the archive at the given file path asynchronously,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
string filePath,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return await GetArchiveInformationAsync(
stream,
readerOptions ?? ReaderOptions.ForFilePath,
cancellationToken
)
.ConfigureAwait(false);
}
/// <summary>
/// Returns information about the archive in the given stream asynchronously,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await GetArchiveInformationAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
/// <summary>
/// Returns information about the archive in the given stream asynchronously,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
stream.RequireReadable();
stream.RequireSeekable();
var factory = await TryFindFactoryAsync(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
cancellationToken
)
.ConfigureAwait(false);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
}
internal static ValueTask<T> FindFactoryAsync<T>(
string filePath,
CancellationToken cancellationToken = default
)
where T : IFactory
{
filePath.NotNullOrEmpty(nameof(filePath));
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
FileInfo fileInfo,
CancellationToken cancellationToken = default
)
where T : IFactory
{
fileInfo.NotNull(nameof(fileInfo));
using Stream stream = fileInfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
}
internal static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken = default
)
where T : IFactory
{
stream.RequireReadable();
stream.RequireSeekable();
// Use the shared async detection loop over all factories. If the matched factory
// implements T we return it; otherwise (or if nothing matched) we fall through
// to the same "unsupported format" exception that the original code produced,
// listing the T-typed factories as the hint for the caller.
var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false);
if (factory is T typedFactory)
{
return typedFactory;
}
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
/// <summary>
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchiveAsync"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
private static async ValueTask<IFactory?> TryFindFactoryAsync(
Stream stream,
CancellationToken cancellationToken
) =>
await TryFindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
private static async ValueTask<IFactory?> TryFindFactoryAsync(
Stream stream,
ReaderOptions readerOptions,
CancellationToken cancellationToken
)
{
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
var isArchive = await factory
.IsArchiveAsync(stream, readerOptions, cancellationToken)
.ConfigureAwait(false);
if (isArchive)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
return null;
}
/// <summary>
/// Returns information about the archive at the given file path,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
public static ArchiveInformation? GetArchiveInformation(string filePath) =>
GetArchiveInformation(filePath, ReaderOptions.ForFilePath);
/// <summary>
/// Returns information about the archive at the given file path,
/// or <see langword="null"/> if the file is not a recognized archive.
/// </summary>
/// <param name="filePath">Path to the archive file.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
public static ArchiveInformation? GetArchiveInformation(
string filePath,
ReaderOptions? readerOptions
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return GetArchiveInformation(stream, readerOptions ?? ReaderOptions.ForFilePath);
}
/// <summary>
/// Returns information about the archive in the given stream,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
public static ArchiveInformation? GetArchiveInformation(Stream stream) =>
GetArchiveInformation(stream, ReaderOptions.ForExternalStream);
/// <summary>
/// Returns information about the archive in the given stream,
/// or <see langword="null"/> if the stream is not a recognized archive.
/// </summary>
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
public static ArchiveInformation? GetArchiveInformation(
Stream stream,
ReaderOptions? readerOptions
)
{
stream.RequireReadable();
stream.RequireSeekable();
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
}
/// <summary>
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchive"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
/// <remarks>
/// This is the shared, seekable-stream detection core used by
/// <see cref="FindFactory{T}(Stream)"/>, <see cref="IsArchive(Stream, out ArchiveType?)"/>,
/// and <see cref="GetArchiveInformation(Stream)"/>.
/// <para>
/// <see cref="ReaderFactory.OpenReader(Stream, ReaderOptions)"/> uses a separate code path
/// based on <see cref="IO.SharpCompressStream"/> rewindable buffering, which supports
/// non-seekable streams and is therefore not unified with this helper.
/// </para>
/// </remarks>
private static IFactory? TryFindFactory(Stream stream) =>
TryFindFactory(stream, ReaderOptions.ForExternalStream);
private static IFactory? TryFindFactory(Stream stream, ReaderOptions readerOptions)
{
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
var isArchive = factory.IsArchive(stream, readerOptions);
if (isArchive)
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
return null;
}
}

View File

@@ -123,23 +123,17 @@ public static partial class ArchiveFactory
stream.RequireReadable();
stream.RequireSeekable();
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
// Use the shared detection loop over all factories. If the matched factory
// implements T we return it; otherwise (or if nothing matched) we fall through
// to the same "unsupported format" exception that the original code produced,
// listing the T-typed factories as the hint for the caller.
var factory = TryFindFactory(stream);
if (factory is T typedFactory)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (factory.IsArchive(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
return typedFactory;
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
@@ -147,69 +141,82 @@ public static partial class ArchiveFactory
}
public static bool IsArchive(string filePath, out ArchiveType? type)
{
return IsArchive(filePath, ReaderOptions.ForFilePath, out type);
}
public static bool IsArchive(
string filePath,
ReaderOptions? readerOptions,
out ArchiveType? type
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream s = File.OpenRead(filePath);
return IsArchive(s, out type);
return IsArchive(s, readerOptions ?? ReaderOptions.ForFilePath, out type);
}
public static bool IsArchive(Stream stream, out ArchiveType? type)
{
type = null;
return IsArchive(stream, ReaderOptions.ForExternalStream, out type);
}
public static bool IsArchive(Stream stream, ReaderOptions? readerOptions, out ArchiveType? type)
{
stream.RequireReadable();
stream.RequireSeekable();
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;
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
type = factory?.KnownArchiveType;
return factory is not null;
}
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
string filePath,
CancellationToken cancellationToken = default
) =>
await IsArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
.ConfigureAwait(false);
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
string filePath,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
using Stream stream = File.OpenRead(filePath);
return await IsArchiveAsync(stream, cancellationToken).ConfigureAwait(false);
return await IsArchiveAsync(
stream,
readerOptions ?? ReaderOptions.ForFilePath,
cancellationToken
)
.ConfigureAwait(false);
}
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await IsArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
stream.RequireReadable();
stream.RequireSeekable();
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
var isArchive = await factory
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
stream.Position = startPosition;
if (isArchive)
{
return (true, factory.KnownArchiveType);
}
}
return (false, null);
var factory = await TryFindFactoryAsync(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
cancellationToken
)
.ConfigureAwait(false);
return (factory is not null, factory?.KnownArchiveType);
}
public static IEnumerable<string> GetFileParts(string part1)

View File

@@ -0,0 +1,22 @@
using SharpCompress.Common;
namespace SharpCompress.Archives;
/// <summary>
/// Contains information about a detected archive, including its type and supported capabilities.
/// </summary>
/// <remarks>
/// Use <see cref="ArchiveFactory.GetArchiveInformation(System.IO.Stream)"/> or
/// <see cref="ArchiveFactory.GetArchiveInformationAsync(System.IO.Stream,System.Threading.CancellationToken)"/>
/// to obtain an instance of this record.
/// </remarks>
/// <param name="Type">
/// The type of archive detected, or <see langword="null"/> when the format is not a registered well-known type.
/// </param>
/// <param name="SupportsRandomAccess">
/// <see langword="true"/> when this archive format supports random access via the <see cref="IArchive"/> API,
/// meaning the full file listing can be retrieved without decompressing the entire archive.
/// <see langword="false"/> when only the <see cref="SharpCompress.Readers.IReader"/> API is available,
/// which reads entries sequentially and can only report per-entry progress.
/// </param>
public record ArchiveInformation(ArchiveType? Type, bool SupportsRandomAccess);

View File

@@ -153,7 +153,7 @@ public partial class RarArchive
{
try
{
MarkHeader.Read(stream, true, false);
MarkHeader.Read(stream, true, options?.LookForHeader ?? false);
return true;
}
catch
@@ -172,7 +172,7 @@ public partial class RarArchive
try
{
await MarkHeader
.ReadAsync(stream, true, false, cancellationToken)
.ReadAsync(stream, true, options?.LookForHeader ?? false, cancellationToken)
.ConfigureAwait(false);
return true;
}

View File

@@ -143,11 +143,14 @@ public partial class SevenZipArchive
return IsSevenZipFile(stream);
}
public static bool IsSevenZipFile(Stream stream)
public static bool IsSevenZipFile(Stream stream) =>
IsSevenZipFile(stream, ReaderOptions.ForExternalStream);
public static bool IsSevenZipFile(Stream stream, ReaderOptions? readerOptions)
{
try
{
return SignatureMatch(stream);
return SignatureMatch(stream, readerOptions?.LookForHeader ?? false);
}
catch
{
@@ -158,12 +161,25 @@ public partial class SevenZipArchive
public static async ValueTask<bool> IsSevenZipFileAsync(
Stream stream,
CancellationToken cancellationToken = default
) =>
await IsSevenZipFileAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
.ConfigureAwait(false);
public static async ValueTask<bool> IsSevenZipFileAsync(
Stream stream,
ReaderOptions? readerOptions,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
return await SignatureMatchAsync(stream, cancellationToken).ConfigureAwait(false);
return await SignatureMatchAsync(
stream,
readerOptions?.LookForHeader ?? false,
cancellationToken
)
.ConfigureAwait(false);
}
catch
{
@@ -173,13 +189,29 @@ public partial class SevenZipArchive
private static ReadOnlySpan<byte> Signature => [(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
private static bool SignatureMatch(Stream stream)
private static bool SignatureMatch(Stream stream, bool lookForHeader)
{
var buffer = ArrayPool<byte>.Shared.Rent(6);
try
{
stream.ReadExact(buffer, 0, 6);
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
for (var offset = 0; offset <= maxScanOffset; offset++)
{
stream.ReadExact(buffer, 0, 6);
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
{
return true;
}
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
{
return false;
}
stream.Position -= 5;
}
return false;
}
finally
{
@@ -189,18 +221,39 @@ public partial class SevenZipArchive
private static async ValueTask<bool> SignatureMatchAsync(
Stream stream,
bool lookForHeader,
CancellationToken cancellationToken
)
{
var buffer = ArrayPool<byte>.Shared.Rent(6);
try
{
if (!await stream.ReadFullyAsync(buffer, 0, 6, cancellationToken).ConfigureAwait(false))
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
for (var offset = 0; offset <= maxScanOffset; offset++)
{
return false;
if (
!await stream
.ReadFullyAsync(buffer, 0, 6, cancellationToken)
.ConfigureAwait(false)
)
{
return false;
}
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
{
return true;
}
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
{
return false;
}
stream.Position -= 5;
}
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
return false;
}
finally
{

View File

@@ -23,12 +23,12 @@ public class AceFactory : Factory, IReaderFactory
yield return "ace";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
AceHeader.IsArchive(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => AceHeader.IsArchiveAsync(stream, cancellationToken);

View File

@@ -25,7 +25,7 @@ public class ArcFactory : Factory, IReaderFactory
yield return "arc";
}
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
//You may have to use some(paranoid) checks to ensure that you actually are
//processing an ARC file, since other archivers also adopted the idea of putting
@@ -63,7 +63,7 @@ public class ArcFactory : Factory, IReaderFactory
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{

View File

@@ -23,12 +23,12 @@ public class ArjFactory : Factory, IReaderFactory
yield return "arj";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
ArjHeader.IsArchive(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => ArjHeader.IsArchiveAsync(stream, cancellationToken);

View File

@@ -54,11 +54,10 @@ public abstract class Factory : IFactory
public abstract IEnumerable<string> GetSupportedExtensions();
/// <inheritdoc/>
public abstract bool IsArchive(Stream stream, string? password = null);
public abstract bool IsArchive(Stream stream, ReaderOptions readerOptions);
public abstract ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
);
@@ -86,7 +85,7 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
stream.Rewind();
if (IsArchive(stream, options.Password))
if (IsArchive(stream, options))
{
stream.Rewind(true);
reader = readerFactory.OpenReader(stream, options);
@@ -106,10 +105,7 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
stream.Rewind();
if (
await IsArchiveAsync(stream, options.Password, cancellationToken)
.ConfigureAwait(false)
)
if (await IsArchiveAsync(stream, options, cancellationToken).ConfigureAwait(false))
{
stream.Rewind(true);
return await readerFactory

View File

@@ -44,13 +44,13 @@ public class GZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
GZipArchive.IsGZipFile(stream);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => GZipArchive.IsGZipFileAsync(stream, cancellationToken);

View File

@@ -37,18 +37,18 @@ public interface IFactory
/// Returns true if the stream represents an archive of the format defined by this type.
/// </summary>
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
/// <param name="password">optional password</param>
bool IsArchive(Stream stream, string? password = null);
/// <param name="readerOptions">Options controlling archive detection.</param>
bool IsArchive(Stream stream, ReaderOptions readerOptions);
/// <summary>
/// Returns true if the stream represents an archive of the format defined by this type asynchronously.
/// </summary>
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
/// <param name="password">optional password</param>
/// <param name="readerOptions">Options controlling archive detection.</param>
/// <param name="cancellationToken">cancellation token</param>
ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
);

View File

@@ -32,13 +32,13 @@ public class LzwFactory : Factory, IReaderFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
LzwStream.IsLzwStream(stream);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => LzwStream.IsLzwStreamAsync(stream, cancellationToken);

View File

@@ -31,15 +31,15 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
RarArchive.IsRarFile(stream);
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
RarArchive.IsRarFile(stream, readerOptions);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => RarArchive.IsRarFileAsync(stream, cancellationToken: cancellationToken);
) => RarArchive.IsRarFileAsync(stream, readerOptions, cancellationToken);
/// <inheritdoc/>
public override FileInfo? GetFilePart(int index, FileInfo part1) =>

View File

@@ -34,15 +34,15 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory, I
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
SevenZipArchive.IsSevenZipFile(stream);
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
SevenZipArchive.IsSevenZipFile(stream, readerOptions);
/// <inheritdoc/>
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => SevenZipArchive.IsSevenZipFileAsync(stream, cancellationToken);
) => SevenZipArchive.IsSevenZipFileAsync(stream, readerOptions, cancellationToken);
#endregion

View File

@@ -48,9 +48,9 @@ public class TarFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
var providers = CompressionProviderRegistry.Default;
var providers = readerOptions.Providers;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)
@@ -78,11 +78,11 @@ public class TarFactory
/// <inheritdoc/>
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{
var providers = CompressionProviderRegistry.Default;
var providers = readerOptions.Providers;
var sharpCompressStream = new SharpCompressStream(stream);
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
foreach (var wrapper in TarWrapper.Wrappers)

View File

@@ -21,12 +21,12 @@ internal class ZStandardFactory : Factory
yield return "zstd";
}
public override bool IsArchive(Stream stream, string? password = null) =>
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
ZStandardStream.IsZStandard(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
) => ZStandardStream.IsZStandardAsync(stream, cancellationToken);
}

View File

@@ -43,10 +43,10 @@ public class ZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
{
var startPosition = stream.CanSeek ? stream.Position : -1;
if (ZipArchive.IsZipFile(stream, password))
if (ZipArchive.IsZipFile(stream, readerOptions.Password))
{
return true;
}
@@ -61,7 +61,7 @@ public class ZipFactory
stream.Position = startPosition;
//test the zip (last) file of a multipart zip
if (ZipArchive.IsZipMulti(stream, password))
if (ZipArchive.IsZipMulti(stream, readerOptions.Password))
{
return true;
}
@@ -74,7 +74,7 @@ public class ZipFactory
/// <inheritdoc/>
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
ReaderOptions readerOptions,
CancellationToken cancellationToken = default
)
{
@@ -84,7 +84,7 @@ public class ZipFactory
// probe for single volume zip
if (
await ZipArchive
.IsZipFileAsync(stream, password, cancellationToken)
.IsZipFileAsync(stream, readerOptions.Password, cancellationToken)
.ConfigureAwait(false)
)
{
@@ -102,7 +102,7 @@ public class ZipFactory
//test the zip (last) file of a multipart zip
if (
await ZipArchive
.IsZipMultiAsync(stream, password, cancellationToken)
.IsZipMultiAsync(stream, readerOptions.Password, cancellationToken)
.ConfigureAwait(false)
)
{

View File

@@ -268,9 +268,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.6, )",
"resolved": "10.0.6",
"contentHash": "QKuvS0LWX4fjFqeDkyM7Kqt8P3wYTiPD4nwU+9y59n0sCiG714fxDgbbN82vDnzq89AF/PiHl92TP2C4aFDUQA=="
"requested": "[10.0.5, )",
"resolved": "10.0.5",
"contentHash": "A+5ZuQ0f449tM+MQrhf6R9ZX7lYpjk/ODEwLYKrnF6111rtARx8fVsm4YznUnQiKnnXfaXNBqgxmil6RW3L3SA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -400,9 +400,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.26, )",
"resolved": "8.0.26",
"contentHash": "o7/yVssM2r9Wyln2s9edBd5ANZXqdSdBI+g7JqXkyJmXrhs2WsJp25K5yPnYrTgdKBCjKB8bg+O2oew4sgzFaA=="
"requested": "[8.0.25, )",
"resolved": "8.0.25",
"contentHash": "sqX4nmBft05ivqKvUT4nxaN8rT3apCLt9SWFkfRrQPwra1zPwFknQAw1lleuMCKOCLvVmOWwrC2iPSm9RiXZUg=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using Xunit;
@@ -111,6 +112,72 @@ public class ArchiveFactoryTests : TestBase
);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
[InlineData("Rar.rar", ArchiveType.Rar)]
[InlineData("7Zip.nonsolid.7z", ArchiveType.SevenZip)]
public void IsArchive_String_ReturnsExpectedType(string archiveName, ArchiveType expectedType)
{
var result = ArchiveFactory.IsArchive(
Path.Combine(TEST_ARCHIVES_PATH, archiveName),
out var type
);
Assert.True(result);
Assert.Equal(expectedType, type);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
public void IsArchive_Stream_PreservesPosition(string archiveName, ArchiveType expectedType)
{
using var stream = CreatePrefixedArchiveStream(archiveName, 11);
var startPosition = stream.Position;
var result = ArchiveFactory.IsArchive(stream, out var type);
Assert.True(result);
Assert.Equal(expectedType, type);
Assert.Equal(startPosition, stream.Position);
}
[Theory]
[InlineData("7Zip.LZMA2.exe", ArchiveType.SevenZip)]
[InlineData("Rar.jpeg.jpg", ArchiveType.Rar)]
public void IsArchive_WithReaderOptions_ReturnsExpectedType(
string archiveName,
ArchiveType expectedType
)
{
var result = ArchiveFactory.IsArchive(
GetTestArchivePath(archiveName),
ReaderOptions.ForFilePath.WithLookForHeader(true),
out var type
);
Assert.True(result);
Assert.Equal(expectedType, type);
}
[Theory]
[InlineData("7Zip.LZMA2.exe", ArchiveType.SevenZip)]
[InlineData("Rar.jpeg.jpg", ArchiveType.Rar)]
public async ValueTask IsArchiveAsync_WithReaderOptions_ReturnsExpectedType(
string archiveName,
ArchiveType expectedType
)
{
var result = await ArchiveFactory.IsArchiveAsync(
GetTestArchivePath(archiveName),
ReaderOptions.ForFilePath.WithLookForHeader(true)
);
Assert.True(result.IsArchive);
Assert.Equal(expectedType, result.Type);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
@@ -159,9 +226,481 @@ public class ArchiveFactoryTests : TestBase
Assert.Equal(0, stream.Position);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip, true)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Rar.rar", ArchiveType.Rar, true)]
[InlineData("7Zip.nonsolid.7z", ArchiveType.SevenZip, true)]
[InlineData("Ace.store.ace", ArchiveType.Ace, false)]
[InlineData("Arc.uncompressed.arc", ArchiveType.Arc, false)]
public void GetArchiveInformation_ReturnsExpectedInfo(
string archiveName,
ArchiveType expectedType,
bool expectedRandomAccess
)
{
var info = ArchiveFactory.GetArchiveInformation(
Path.Combine(TEST_ARCHIVES_PATH, archiveName)
);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedRandomAccess, info.SupportsRandomAccess);
}
[Theory]
[InlineData("7Zip.LZMA2.exe", ArchiveType.SevenZip, true)]
[InlineData("Rar.jpeg.jpg", ArchiveType.Rar, true)]
public void GetArchiveInformation_WithReaderOptions_ReturnsExpectedInfo(
string archiveName,
ArchiveType expectedType,
bool expectedRandomAccess
)
{
var info = ArchiveFactory.GetArchiveInformation(
GetTestArchivePath(archiveName),
ReaderOptions.ForFilePath.WithLookForHeader(true)
);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedRandomAccess, info.SupportsRandomAccess);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip, true)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Rar.rar", ArchiveType.Rar, true)]
[InlineData("7Zip.nonsolid.7z", ArchiveType.SevenZip, true)]
[InlineData("Ace.store.ace", ArchiveType.Ace, false)]
[InlineData("Arc.uncompressed.arc", ArchiveType.Arc, false)]
public async ValueTask GetArchiveInformationAsync_ReturnsExpectedInfo(
string archiveName,
ArchiveType expectedType,
bool expectedRandomAccess
)
{
var info = await ArchiveFactory.GetArchiveInformationAsync(
Path.Combine(TEST_ARCHIVES_PATH, archiveName)
);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedRandomAccess, info.SupportsRandomAccess);
}
[Theory]
[InlineData("7Zip.LZMA2.exe", ArchiveType.SevenZip, true)]
[InlineData("Rar.jpeg.jpg", ArchiveType.Rar, true)]
public async ValueTask GetArchiveInformationAsync_WithReaderOptions_ReturnsExpectedInfo(
string archiveName,
ArchiveType expectedType,
bool expectedRandomAccess
)
{
var info = await ArchiveFactory.GetArchiveInformationAsync(
GetTestArchivePath(archiveName),
ReaderOptions.ForFilePath.WithLookForHeader(true)
);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedRandomAccess, info.SupportsRandomAccess);
}
[Theory]
[InlineData("64bitstream.zip.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARM.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARM64.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARMT.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BCJ.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BCJ2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BZip2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Copy.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.EmptyStream.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Filters.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.IA64.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA.Aes.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA2.Aes.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.PPC.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.PPMd.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.RISCV.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.SPARC.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Tar.tar", ArchiveType.Tar, true)]
[InlineData("7Zip.Tar.tar.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ZSTD.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.delta.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.delta.distance.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.encryptedFiles.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.eos.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.nonsolid.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.solid.1block.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.solid.7z", ArchiveType.SevenZip, true)]
[InlineData("Ace.encrypted.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method1-solid.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method1.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method2-solid.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method2.ace", ArchiveType.Ace, false)]
[InlineData("Ace.store.ace", ArchiveType.Ace, false)]
[InlineData("Ace.store.largefile.ace", ArchiveType.Ace, false)]
[InlineData("Arc.crunched.arc", ArchiveType.Arc, false)]
[InlineData("Arc.crunched.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squashed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squashed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squeezed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squeezed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.uncompressed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.uncompressed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arj.encrypted.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method1.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method1.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method2.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method2.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method3.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method3.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method4.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method4.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.store.arj", ArchiveType.Arj, false)]
[InlineData("Arj.store.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Issue_685.zip", ArchiveType.Zip, true)]
[InlineData("PrePostHeaders.zip", ArchiveType.Zip, true)]
[InlineData("Rar.Audio_program.rar", ArchiveType.Rar, true)]
[InlineData("Rar.Encrypted.rar", ArchiveType.Rar, true)]
[InlineData("Rar.comment.rar", ArchiveType.Rar, true)]
[InlineData("Rar.encrypted_filesAndHeader.rar", ArchiveType.Rar, true)]
[InlineData("Rar.encrypted_filesOnly.rar", ArchiveType.Rar, true)]
[InlineData("Rar.issue1050.rar", ArchiveType.Rar, true)]
[InlineData("Rar.malformed_512byte.rar", ArchiveType.Rar, true)]
[InlineData("Rar.none.rar", ArchiveType.Rar, true)]
[InlineData("Rar.rar", ArchiveType.Rar, true)]
[InlineData("Rar.solid.rar", ArchiveType.Rar, true)]
[InlineData("Rar.test_invalid_exttime.rar", ArchiveType.Rar, true)]
[InlineData("Rar15.rar", ArchiveType.Rar, true)]
[InlineData("Rar2.rar", ArchiveType.Rar, true)]
[InlineData("Rar4.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.comment.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.crc_blake2.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.encrypted_filesAndHeader.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.encrypted_filesOnly.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.none.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.solid.rar", ArchiveType.Rar, true)]
[InlineData("Tar.ContainsRar.tar", ArchiveType.Tar, true)]
[InlineData("Tar.ContainsTarGz.tar", ArchiveType.Tar, true)]
[InlineData("Tar.Empty.tar", ArchiveType.Tar, true)]
[InlineData("Tar.LongPathsWithLongNameExtension.tar", ArchiveType.Tar, true)]
[InlineData("Tar.mod.tar", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar.bz2", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar.lz", ArchiveType.Tar, true)]
[InlineData("Tar.oldgnu.tar.gz", ArchiveType.Tar, true)]
[InlineData("Tar.tar", ArchiveType.Tar, true)]
[InlineData("Tar.tar.Z", ArchiveType.Tar, true)]
[InlineData("Tar.tar.bz2", ArchiveType.Tar, true)]
[InlineData("Tar.tar.gz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.lz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.xz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.zst", ArchiveType.Tar, true)]
[InlineData("TarCorrupted.tar", ArchiveType.Tar, true)]
[InlineData("TarWithSymlink.tar.gz", ArchiveType.Tar, true)]
[InlineData("WinZip26.zip", ArchiveType.Zip, true)]
[InlineData("WinZip26_BZip2.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip26_LZMA.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip27_XZ.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip27_ZSTD.zipx", ArchiveType.Zip, true)]
[InlineData("Zip.644.zip", ArchiveType.Zip, true)]
[InlineData("Zip.EntryComment.zip", ArchiveType.Zip, true)]
[InlineData("Zip.Evil.zip", ArchiveType.Zip, true)]
[InlineData("Zip.LongComment.zip", ArchiveType.Zip, true)]
[InlineData("Zip.UnicodePathExtra.zip", ArchiveType.Zip, true)]
[InlineData("Zip.badlocalextra.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.pkware.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.WinzipAES.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.WinzipAES2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.dd-.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.mod.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.mod2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.pkware.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate64.zip", ArchiveType.Zip, true)]
[InlineData("Zip.implode.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.WinzipAES.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.empty.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.datadescriptors.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.encrypted.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.issue86.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce1.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce3.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce4.zip", ArchiveType.Zip, true)]
[InlineData("Zip.shrink.zip", ArchiveType.Zip, true)]
[InlineData("Zip.uncompressed.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zip64.compressedonly.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zip64.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zipx", ArchiveType.Zip, true)]
[InlineData("Zip.zstd.WinzipAES.mixed.zip", ArchiveType.Zip, true)]
[InlineData("large_test.txt.Z", ArchiveType.Lzw, false)]
[InlineData("test_477.zip", ArchiveType.Zip, true)]
[InlineData("ustar with long names.tar", ArchiveType.Tar, true)]
[InlineData("very long filename.tar", ArchiveType.Tar, true)]
[InlineData("zipcrypto.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.AES.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.Encrypted.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.Encrypted2.zip", ArchiveType.Zip, true)]
public void GetArchiveInformation_DetectsSingleFileTestArchives(
string archiveName,
ArchiveType expectedType,
bool expectedSeekable
)
{
var info = ArchiveFactory.GetArchiveInformation(GetTestArchivePath(archiveName));
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedSeekable, info.SupportsRandomAccess);
}
[Theory]
[InlineData("64bitstream.zip.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARM.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARM64.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ARMT.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BCJ.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BCJ2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.BZip2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Copy.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.EmptyStream.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Filters.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.IA64.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA.Aes.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA2.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.LZMA2.Aes.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.PPC.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.PPMd.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.RISCV.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.SPARC.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.Tar.tar", ArchiveType.Tar, true)]
[InlineData("7Zip.Tar.tar.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.ZSTD.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.delta.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.delta.distance.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.encryptedFiles.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.eos.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.nonsolid.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.solid.1block.7z", ArchiveType.SevenZip, true)]
[InlineData("7Zip.solid.7z", ArchiveType.SevenZip, true)]
[InlineData("Ace.encrypted.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method1-solid.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method1.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method2-solid.ace", ArchiveType.Ace, false)]
[InlineData("Ace.method2.ace", ArchiveType.Ace, false)]
[InlineData("Ace.store.ace", ArchiveType.Ace, false)]
[InlineData("Ace.store.largefile.ace", ArchiveType.Ace, false)]
[InlineData("Arc.crunched.arc", ArchiveType.Arc, false)]
[InlineData("Arc.crunched.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squashed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squashed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squeezed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.squeezed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arc.uncompressed.arc", ArchiveType.Arc, false)]
[InlineData("Arc.uncompressed.largefile.arc", ArchiveType.Arc, false)]
[InlineData("Arj.encrypted.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method1.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method1.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method2.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method2.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method3.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method3.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method4.arj", ArchiveType.Arj, false)]
[InlineData("Arj.method4.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Arj.store.arj", ArchiveType.Arj, false)]
[InlineData("Arj.store.largefile.arj", ArchiveType.Arj, false)]
[InlineData("Issue_685.zip", ArchiveType.Zip, true)]
[InlineData("PrePostHeaders.zip", ArchiveType.Zip, true)]
[InlineData("Rar.Audio_program.rar", ArchiveType.Rar, true)]
[InlineData("Rar.Encrypted.rar", ArchiveType.Rar, true)]
[InlineData("Rar.comment.rar", ArchiveType.Rar, true)]
[InlineData("Rar.encrypted_filesAndHeader.rar", ArchiveType.Rar, true)]
[InlineData("Rar.encrypted_filesOnly.rar", ArchiveType.Rar, true)]
[InlineData("Rar.issue1050.rar", ArchiveType.Rar, true)]
[InlineData("Rar.malformed_512byte.rar", ArchiveType.Rar, true)]
[InlineData("Rar.none.rar", ArchiveType.Rar, true)]
[InlineData("Rar.rar", ArchiveType.Rar, true)]
[InlineData("Rar.solid.rar", ArchiveType.Rar, true)]
[InlineData("Rar.test_invalid_exttime.rar", ArchiveType.Rar, true)]
[InlineData("Rar15.rar", ArchiveType.Rar, true)]
[InlineData("Rar2.rar", ArchiveType.Rar, true)]
[InlineData("Rar4.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.comment.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.crc_blake2.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.encrypted_filesAndHeader.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.encrypted_filesOnly.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.none.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.rar", ArchiveType.Rar, true)]
[InlineData("Rar5.solid.rar", ArchiveType.Rar, true)]
[InlineData("Tar.ContainsRar.tar", ArchiveType.Tar, true)]
[InlineData("Tar.ContainsTarGz.tar", ArchiveType.Tar, true)]
[InlineData("Tar.Empty.tar", ArchiveType.Tar, true)]
[InlineData("Tar.LongPathsWithLongNameExtension.tar", ArchiveType.Tar, true)]
[InlineData("Tar.mod.tar", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar.bz2", ArchiveType.Tar, true)]
[InlineData("Tar.noEmptyDirs.tar.lz", ArchiveType.Tar, true)]
[InlineData("Tar.oldgnu.tar.gz", ArchiveType.Tar, true)]
[InlineData("Tar.tar", ArchiveType.Tar, true)]
[InlineData("Tar.tar.Z", ArchiveType.Tar, true)]
[InlineData("Tar.tar.bz2", ArchiveType.Tar, true)]
[InlineData("Tar.tar.gz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.lz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.xz", ArchiveType.Tar, true)]
[InlineData("Tar.tar.zst", ArchiveType.Tar, true)]
[InlineData("TarCorrupted.tar", ArchiveType.Tar, true)]
[InlineData("TarWithSymlink.tar.gz", ArchiveType.Tar, true)]
[InlineData("WinZip26.zip", ArchiveType.Zip, true)]
[InlineData("WinZip26_BZip2.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip26_LZMA.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip27_XZ.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip27_ZSTD.zipx", ArchiveType.Zip, true)]
[InlineData("Zip.644.zip", ArchiveType.Zip, true)]
[InlineData("Zip.EntryComment.zip", ArchiveType.Zip, true)]
[InlineData("Zip.Evil.zip", ArchiveType.Zip, true)]
[InlineData("Zip.LongComment.zip", ArchiveType.Zip, true)]
[InlineData("Zip.UnicodePathExtra.zip", ArchiveType.Zip, true)]
[InlineData("Zip.badlocalextra.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.pkware.zip", ArchiveType.Zip, true)]
[InlineData("Zip.bzip2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.WinzipAES.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.WinzipAES2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.dd-.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.mod.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.mod2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.pkware.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate.zip", ArchiveType.Zip, true)]
[InlineData("Zip.deflate64.zip", ArchiveType.Zip, true)]
[InlineData("Zip.implode.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.WinzipAES.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.empty.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.lzma.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.datadescriptors.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.encrypted.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.issue86.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.none.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.dd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.noEmptyDirs.zip", ArchiveType.Zip, true)]
[InlineData("Zip.ppmd.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce1.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce2.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce3.zip", ArchiveType.Zip, true)]
[InlineData("Zip.reduce4.zip", ArchiveType.Zip, true)]
[InlineData("Zip.shrink.zip", ArchiveType.Zip, true)]
[InlineData("Zip.uncompressed.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zip64.compressedonly.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zip64.zip", ArchiveType.Zip, true)]
[InlineData("Zip.zipx", ArchiveType.Zip, true)]
[InlineData("Zip.zstd.WinzipAES.mixed.zip", ArchiveType.Zip, true)]
[InlineData("large_test.txt.Z", ArchiveType.Lzw, false)]
[InlineData("test_477.zip", ArchiveType.Zip, true)]
[InlineData("ustar with long names.tar", ArchiveType.Tar, true)]
[InlineData("very long filename.tar", ArchiveType.Tar, true)]
[InlineData("zipcrypto.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.AES.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.Encrypted.zip", ArchiveType.Zip, true)]
[InlineData("SharpCompress.Encrypted2.zip", ArchiveType.Zip, true)]
public async ValueTask GetArchiveInformationAsync_DetectsSingleFileTestArchives(
string archiveName,
ArchiveType expectedType,
bool expectedSeekable
)
{
var info = await ArchiveFactory.GetArchiveInformationAsync(GetTestArchivePath(archiveName));
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(expectedSeekable, info.SupportsRandomAccess);
}
[Fact]
public void GetArchiveInformation_ReturnsNull_ForNonArchive()
{
using var stream = new MemoryStream(Encoding.ASCII.GetBytes("not an archive"));
var info = ArchiveFactory.GetArchiveInformation(stream);
Assert.Null(info);
}
[Fact]
public async ValueTask GetArchiveInformationAsync_ReturnsNull_ForNonArchive()
{
using var stream = new MemoryStream(Encoding.ASCII.GetBytes("not an archive"));
var info = await ArchiveFactory.GetArchiveInformationAsync(stream);
Assert.Null(info);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
public void GetArchiveInformation_Stream_PreservesPosition(
string archiveName,
ArchiveType expectedType
)
{
using var stream = CreatePrefixedArchiveStream(archiveName, 13);
var startPosition = stream.Position;
var info = ArchiveFactory.GetArchiveInformation(stream);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(startPosition, stream.Position);
}
[Theory]
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
public async ValueTask GetArchiveInformationAsync_Stream_PreservesPosition(
string archiveName,
ArchiveType expectedType
)
{
using var stream = CreatePrefixedArchiveStream(archiveName, 13);
var startPosition = stream.Position;
var info = await ArchiveFactory.GetArchiveInformationAsync(stream);
Assert.NotNull(info);
Assert.Equal(expectedType, info.Type);
Assert.Equal(startPosition, stream.Position);
}
private MemoryStream CreatePrefixedArchiveStream(string archiveName, int prefixLength)
{
var archiveBytes = File.ReadAllBytes(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
var archiveBytes = File.ReadAllBytes(GetTestArchivePath(archiveName));
var buffer = new byte[prefixLength + archiveBytes.Length];
archiveBytes.CopyTo(buffer, prefixLength);
@@ -170,4 +709,15 @@ public class ArchiveFactoryTests : TestBase
stream.Position = prefixLength;
return stream;
}
private static string GetTestArchivePath(string archiveName)
{
var archivesPath = Path.Combine(TEST_ARCHIVES_PATH, archiveName);
if (File.Exists(archivesPath))
{
return archivesPath;
}
return Path.GetFullPath(Path.Combine(TEST_ARCHIVES_PATH, "..", archiveName));
}
}

View File

@@ -103,7 +103,7 @@ public abstract class ReaderTests : TestBase
(
await factory.IsArchiveAsync(
new FileInfo(testArchive).OpenRead(),
null,
ReaderOptions.ForExternalStream,
cancellationToken
)
)
@@ -112,6 +112,7 @@ public abstract class ReaderTests : TestBase
(
await factory.IsArchiveAsync(
new FileInfo(testArchive).OpenRead(),
ReaderOptions.ForExternalStream,
cancellationToken: cancellationToken
)
)