mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-08 18:16:30 +00:00
Merge pull request #1299 from adamhathcock/copilot/add-archive-detection-utility
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal file
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal file
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user