2026-10-01 16:54:03 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using SharpCompress.Archives;
|
|
|
|
|
using SharpCompress.Archives.Tar;
|
|
|
|
|
using SharpCompress.Common;
|
|
|
|
|
using SharpCompress.Detection;
|
|
|
|
|
using SharpCompress.Factories;
|
|
|
|
|
using SharpCompress.Readers;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace SharpCompress.Test;
|
|
|
|
|
|
|
|
|
|
public class ArchiveInspectionReuseTests : TestBase
|
|
|
|
|
{
|
2026-10-02 08:59:29 +01:00
|
|
|
private readonly CountingFactory countingFactory = new();
|
|
|
|
|
private readonly IArchiveInspector inspector;
|
2026-10-01 16:54:03 +01:00
|
|
|
|
|
|
|
|
public ArchiveInspectionReuseTests()
|
|
|
|
|
{
|
2026-10-02 08:59:29 +01:00
|
|
|
inspector = new SharpCompressClient(
|
|
|
|
|
new SharpCompressConfiguration(FormatRegistry.Default.With(countingFactory))
|
|
|
|
|
).Inspector;
|
2026-10-01 16:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[InlineData("stream", false)]
|
|
|
|
|
[InlineData("stream", true)]
|
|
|
|
|
[InlineData("path", false)]
|
|
|
|
|
[InlineData("path", true)]
|
|
|
|
|
[InlineData("singlePath", false)]
|
|
|
|
|
[InlineData("singlePath", true)]
|
|
|
|
|
[InlineData("files", false)]
|
|
|
|
|
[InlineData("files", true)]
|
|
|
|
|
[InlineData("streams", false)]
|
|
|
|
|
[InlineData("streams", true)]
|
|
|
|
|
public async ValueTask InspectArchive_ReusesSelectedFactory(string source, bool useAsync)
|
|
|
|
|
{
|
|
|
|
|
var bytes = new byte[1024];
|
|
|
|
|
bytes[0] = 0x53;
|
|
|
|
|
bytes[1] = 0x43;
|
|
|
|
|
bytes[2] = 0x49;
|
|
|
|
|
bytes[3] = 0x54;
|
|
|
|
|
using var first = new MemoryStream(bytes);
|
|
|
|
|
using var second = new MemoryStream(bytes);
|
|
|
|
|
var options = new ReaderOptions { LeaveStreamOpen = false };
|
|
|
|
|
ArchiveInformation? information;
|
|
|
|
|
if (source == "stream")
|
|
|
|
|
{
|
|
|
|
|
information = useAsync
|
2026-10-02 08:59:29 +01:00
|
|
|
? await inspector.InspectArchiveAsync(first, options)
|
|
|
|
|
: inspector.InspectArchive(first, options);
|
2026-10-01 16:54:03 +01:00
|
|
|
}
|
|
|
|
|
else if (source == "streams")
|
|
|
|
|
{
|
|
|
|
|
Stream[] streams = [first, second];
|
|
|
|
|
information = useAsync
|
2026-10-02 08:59:29 +01:00
|
|
|
? await inspector.InspectArchiveAsync(streams, options)
|
|
|
|
|
: inspector.InspectArchive(streams, options);
|
2026-10-01 16:54:03 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var path = GetScratchPath("inspection.reuse");
|
|
|
|
|
File.WriteAllBytes(path, bytes);
|
|
|
|
|
if (source != "singlePath")
|
|
|
|
|
{
|
|
|
|
|
File.WriteAllBytes(path + ".part2", bytes);
|
|
|
|
|
}
|
|
|
|
|
FileInfo[] files = [new(path), new(path + ".part2")];
|
|
|
|
|
information = source is "path" or "singlePath"
|
|
|
|
|
? useAsync
|
2026-10-02 08:59:29 +01:00
|
|
|
? await inspector.InspectArchiveAsync(path, options)
|
|
|
|
|
: inspector.InspectArchive(path, options)
|
2026-10-01 16:54:03 +01:00
|
|
|
: useAsync
|
2026-10-02 08:59:29 +01:00
|
|
|
? await inspector.InspectArchiveAsync(files, options)
|
|
|
|
|
: inspector.InspectArchive(files, options);
|
2026-10-01 16:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.NotNull(information);
|
|
|
|
|
Assert.Equal("Inspection test", information.Detection.FormatName);
|
|
|
|
|
Assert.Equal(ArchiveInformationStatus.Complete, information.Status);
|
|
|
|
|
Assert.Equal(source is "stream" or "singlePath" ? 1 : 2, information.PhysicalPartCount);
|
|
|
|
|
Assert.Equal(1, countingFactory.ProbeCount);
|
|
|
|
|
Assert.Equal(1, countingFactory.OpenCount);
|
|
|
|
|
Assert.True(first.CanRead);
|
|
|
|
|
Assert.True(second.CanRead);
|
|
|
|
|
Assert.Equal(0, first.Position);
|
|
|
|
|
Assert.Equal(0, second.Position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[InlineData(false)]
|
|
|
|
|
[InlineData(true)]
|
|
|
|
|
public async ValueTask InspectArchive_PartialInformation_ReusesDetection(bool useAsync)
|
|
|
|
|
{
|
|
|
|
|
countingFactory.FailWithEncryptedHeaders = true;
|
|
|
|
|
using var stream = new MemoryStream(new byte[1024]);
|
|
|
|
|
stream.Write(new byte[] { 0x53, 0x43, 0x49, 0x54 }, 0, 4);
|
|
|
|
|
stream.Position = 0;
|
|
|
|
|
|
|
|
|
|
var information = useAsync
|
2026-10-02 08:59:29 +01:00
|
|
|
? await inspector.InspectArchiveAsync(stream)
|
|
|
|
|
: inspector.InspectArchive(stream);
|
2026-10-01 16:54:03 +01:00
|
|
|
|
|
|
|
|
Assert.NotNull(information);
|
|
|
|
|
Assert.Equal(ArchiveInformationLimitations.EncryptedHeaders, information.Limitations);
|
|
|
|
|
Assert.Equal(1, countingFactory.ProbeCount);
|
|
|
|
|
Assert.Equal(1, countingFactory.OpenCount);
|
|
|
|
|
Assert.Equal(0, stream.Position);
|
|
|
|
|
Assert.True(stream.CanRead);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class CountingFactory : Factory, IArchiveFactory, IMultiArchiveFactory
|
|
|
|
|
{
|
|
|
|
|
public int ProbeCount;
|
|
|
|
|
public int OpenCount;
|
|
|
|
|
public bool FailWithEncryptedHeaders;
|
|
|
|
|
|
|
|
|
|
public override string Name => "Inspection test";
|
|
|
|
|
|
|
|
|
|
public override IEnumerable<string> GetSupportedExtensions() => ["reuse"];
|
|
|
|
|
|
|
|
|
|
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
|
|
|
|
{
|
|
|
|
|
if (
|
|
|
|
|
stream.ReadByte() != 0x53
|
|
|
|
|
|| stream.ReadByte() != 0x43
|
|
|
|
|
|| stream.ReadByte() != 0x49
|
|
|
|
|
|| stream.ReadByte() != 0x54
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
ProbeCount++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override ValueTask<bool> IsArchiveAsync(
|
|
|
|
|
Stream stream,
|
|
|
|
|
ReaderOptions readerOptions,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
return new(IsArchive(stream, readerOptions));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override FileInfo? GetFilePart(int index, FileInfo part1) =>
|
|
|
|
|
index == 1 && File.Exists(part1.FullName + ".part2")
|
|
|
|
|
? new FileInfo(part1.FullName + ".part2")
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
private MemoryStream OpenSource()
|
|
|
|
|
{
|
|
|
|
|
OpenCount++;
|
|
|
|
|
if (FailWithEncryptedHeaders)
|
|
|
|
|
{
|
|
|
|
|
throw new CryptographicException("Test encrypted headers");
|
|
|
|
|
}
|
|
|
|
|
// Supply a valid empty TAR after selection; the synthetic signature is only for probing.
|
|
|
|
|
return new MemoryStream(new byte[1024]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) =>
|
|
|
|
|
TarArchive.OpenArchive(OpenSource(), ReaderOptions.ForFilePath);
|
|
|
|
|
|
|
|
|
|
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
|
|
|
|
Stream stream,
|
|
|
|
|
ReaderOptions? readerOptions = null,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
) =>
|
|
|
|
|
await TarArchive
|
|
|
|
|
.OpenAsyncArchive(OpenSource(), ReaderOptions.ForFilePath, cancellationToken)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
|
|
|
|
|
OpenArchive(Stream.Null, readerOptions);
|
|
|
|
|
|
|
|
|
|
public ValueTask<IAsyncArchive> OpenAsyncArchive(
|
|
|
|
|
FileInfo fileInfo,
|
|
|
|
|
ReaderOptions? readerOptions = null,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
) => OpenAsyncArchive(Stream.Null, readerOptions, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public IArchive OpenArchive(
|
|
|
|
|
IReadOnlyList<FileInfo> fileInfos,
|
|
|
|
|
ReaderOptions? readerOptions = null
|
|
|
|
|
) => OpenArchive(Stream.Null, readerOptions);
|
|
|
|
|
|
|
|
|
|
public ValueTask<IAsyncArchive> OpenAsyncArchive(
|
|
|
|
|
IReadOnlyList<FileInfo> fileInfos,
|
|
|
|
|
ReaderOptions? readerOptions = null,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
) => OpenAsyncArchive(Stream.Null, readerOptions, cancellationToken);
|
|
|
|
|
|
|
|
|
|
public IArchive OpenArchive(
|
|
|
|
|
IReadOnlyList<Stream> streams,
|
|
|
|
|
ReaderOptions? readerOptions = null
|
|
|
|
|
) => OpenArchive(Stream.Null, readerOptions);
|
|
|
|
|
|
|
|
|
|
public ValueTask<IAsyncArchive> OpenAsyncArchive(
|
|
|
|
|
IReadOnlyList<Stream> streams,
|
|
|
|
|
ReaderOptions? readerOptions = null,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
) => OpenAsyncArchive(Stream.Null, readerOptions, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
}
|