mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 08:24:58 +00:00
Tar detection works
This commit is contained in:
@@ -6,12 +6,9 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Tar;
|
||||
|
||||
namespace SharpCompress.Archives.Tar;
|
||||
|
||||
@@ -176,11 +173,11 @@ public partial class TarArchive
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var tarHeader = new TarHeader(new ArchiveEncoding());
|
||||
var readSucceeded = await tarHeader.ReadAsync(stream);
|
||||
var reader = new AsyncBinaryReader(stream, false);
|
||||
var readSucceeded = await tarHeader.ReadAsync(reader);
|
||||
var isEmptyArchive =
|
||||
tarHeader.Name?.Length == 0
|
||||
&& tarHeader.Size == 0
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -496,7 +497,7 @@ internal sealed class TarHeader
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> ReadAsync(Stream stream)
|
||||
internal async Task<bool> ReadAsync(AsyncBinaryReader reader)
|
||||
{
|
||||
string? longName = null;
|
||||
string? longLinkName = null;
|
||||
@@ -506,7 +507,7 @@ internal sealed class TarHeader
|
||||
|
||||
do
|
||||
{
|
||||
buffer = await ReadBlockAsync(stream);
|
||||
buffer = await ReadBlockAsync(reader);
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
@@ -519,12 +520,12 @@ internal sealed class TarHeader
|
||||
// to apply to the header that follows them.
|
||||
if (entryType == EntryType.LongName)
|
||||
{
|
||||
longName = await ReadLongNameAsync(stream, buffer);
|
||||
longName = await ReadLongNameAsync(reader, buffer);
|
||||
continue;
|
||||
}
|
||||
else if (entryType == EntryType.LongLink)
|
||||
{
|
||||
longLinkName = await ReadLongNameAsync(stream, buffer);
|
||||
longLinkName = await ReadLongNameAsync(reader, buffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -616,36 +617,27 @@ internal sealed class TarHeader
|
||||
|
||||
public string? Magic { get; set; }
|
||||
|
||||
private static async Task<byte[]> ReadBlockAsync(Stream stream)
|
||||
private static async ValueTask<byte[]> ReadBlockAsync(AsyncBinaryReader reader)
|
||||
{
|
||||
var buffer = new byte[BLOCK_SIZE];
|
||||
int bytesRead = 0;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < BLOCK_SIZE)
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(BLOCK_SIZE);
|
||||
try
|
||||
{
|
||||
bytesRead = await stream.ReadAsync(buffer, totalBytesRead, BLOCK_SIZE - totalBytesRead);
|
||||
if (bytesRead == 0)
|
||||
await reader.ReadBytesAsync(buffer, 0, BLOCK_SIZE);
|
||||
|
||||
if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE)
|
||||
{
|
||||
break;
|
||||
throw new InvalidFormatException("Buffer is invalid size");
|
||||
}
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
if (totalBytesRead == 0)
|
||||
return buffer;
|
||||
}
|
||||
finally
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
if (totalBytesRead < BLOCK_SIZE)
|
||||
{
|
||||
throw new InvalidFormatException("Buffer is invalid size");
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private async Task<string> ReadLongNameAsync(Stream stream, byte[] buffer)
|
||||
private async Task<string> ReadLongNameAsync(AsyncBinaryReader reader, byte[] buffer)
|
||||
{
|
||||
var size = ReadSize(buffer);
|
||||
|
||||
@@ -658,33 +650,31 @@ internal sealed class TarHeader
|
||||
}
|
||||
|
||||
var nameLength = (int)size;
|
||||
var nameBytes = new byte[nameLength];
|
||||
int bytesRead = 0;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < nameLength)
|
||||
var nameBytes = ArrayPool<byte>.Shared.Rent(nameLength);
|
||||
try
|
||||
{
|
||||
bytesRead = await stream.ReadAsync(
|
||||
nameBytes,
|
||||
totalBytesRead,
|
||||
nameLength - totalBytesRead
|
||||
);
|
||||
if (bytesRead == 0)
|
||||
await reader.ReadBytesAsync(buffer, 0, nameLength);
|
||||
var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE);
|
||||
|
||||
// Read the rest of the block and discard the data
|
||||
if (remainingBytesToRead < BLOCK_SIZE)
|
||||
{
|
||||
break;
|
||||
var remainingBytes = ArrayPool<byte>.Shared.Rent(remainingBytesToRead);
|
||||
try
|
||||
{
|
||||
await reader.ReadBytesAsync(remainingBytes, 0, remainingBytesToRead);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(nameBytes);
|
||||
}
|
||||
}
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
return ArchiveEncoding.Decode(nameBytes, 0, nameLength).TrimNulls();
|
||||
}
|
||||
|
||||
var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE);
|
||||
|
||||
// Read the rest of the block and discard the data
|
||||
if (remainingBytesToRead < BLOCK_SIZE)
|
||||
finally
|
||||
{
|
||||
var paddingBuffer = new byte[remainingBytesToRead];
|
||||
await stream.ReadAsync(paddingBuffer, 0, remainingBytesToRead);
|
||||
ArrayPool<byte>.Shared.Return(nameBytes);
|
||||
}
|
||||
|
||||
return ArchiveEncoding.Decode(nameBytes, 0, nameBytes.Length).TrimNulls();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ internal static class TarHeaderFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static async IAsyncEnumerable<TarHeader?> ReadHeaderAsync(
|
||||
StreamingMode mode,
|
||||
Stream stream,
|
||||
@@ -66,26 +65,26 @@ internal static class TarHeaderFactory
|
||||
TarHeader? header = null;
|
||||
try
|
||||
{
|
||||
var reader = new AsyncBinaryReader(stream, false);
|
||||
header = new TarHeader(archiveEncoding);
|
||||
|
||||
if (!await header.ReadAsync(stream))
|
||||
if (!await header.ReadAsync(reader))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
switch (mode)
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
{
|
||||
header.DataStartPosition = stream.Position;
|
||||
{
|
||||
header.DataStartPosition = stream.Position;
|
||||
|
||||
//skip to nearest 512
|
||||
stream.Position += PadTo512(header.Size);
|
||||
}
|
||||
//skip to nearest 512
|
||||
stream.Position += PadTo512(header.Size);
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
{
|
||||
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
|
||||
}
|
||||
{
|
||||
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
|
||||
@@ -39,14 +39,14 @@ namespace SharpCompress.Factories
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
AceReader.OpenReader(stream, options);
|
||||
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)AceReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)AceReader.OpenReader(stream, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ namespace SharpCompress.Factories
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
ArcReader.OpenReader(stream, options);
|
||||
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)ArcReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)ArcReader.OpenReader(stream, options));
|
||||
}
|
||||
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
|
||||
@@ -39,14 +39,14 @@ namespace SharpCompress.Factories
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
ArjReader.OpenReader(stream, options);
|
||||
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)ArjReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)ArjReader.OpenReader(stream, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,14 +144,14 @@ public class GZipFactory
|
||||
GZipReader.OpenReader(stream, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)GZipReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)GZipReader.OpenReader(stream, options));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -111,14 +111,14 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
|
||||
RarReader.OpenReader(stream, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)RarReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)RarReader.OpenReader(stream, options));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.Lzw;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using SharpCompress.Compressors.ZStandard;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Tar;
|
||||
using GZipArchive = SharpCompress.Archives.GZip.GZipArchive;
|
||||
|
||||
namespace SharpCompress.Factories;
|
||||
|
||||
@@ -45,7 +35,7 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<string> GetSupportedExtensions()
|
||||
{
|
||||
foreach (var testOption in compressionOptions)
|
||||
foreach (var testOption in TarWrapper.Wrappers)
|
||||
{
|
||||
foreach (var ext in testOption.KnownExtensions)
|
||||
{
|
||||
@@ -59,15 +49,55 @@ public class TarFactory
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
int bufferSize = ReaderOptions.DefaultBufferSize
|
||||
) => TarArchive.IsTarFile(stream);
|
||||
)
|
||||
{
|
||||
var rewindableStream = new SharpCompressStream(stream);
|
||||
long pos = rewindableStream.GetPosition();
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
if (wrapper.IsMatch(rewindableStream))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
var decompressedStream = wrapper.CreateStream(rewindableStream);
|
||||
if (TarArchive.IsTarFile(decompressedStream))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
int bufferSize = ReaderOptions.DefaultBufferSize,
|
||||
CancellationToken cancellationToken = default
|
||||
) => TarArchive.IsTarFileAsync(stream, cancellationToken);
|
||||
)
|
||||
{
|
||||
var rewindableStream = new SharpCompressStream(stream);
|
||||
long pos = rewindableStream.GetPosition();
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
var decompressedStream = wrapper.CreateStream(rewindableStream);
|
||||
if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -126,161 +156,54 @@ public class TarFactory
|
||||
|
||||
#region IReaderFactory
|
||||
|
||||
|
||||
protected class TestOption
|
||||
{
|
||||
public readonly CompressionType Type;
|
||||
public readonly Func<Stream, bool> CanHandle;
|
||||
public readonly bool WrapInSharpCompressStream;
|
||||
|
||||
public readonly Func<Stream, Stream> CreateStream;
|
||||
|
||||
public readonly IEnumerable<string> KnownExtensions;
|
||||
|
||||
public TestOption(
|
||||
CompressionType Type,
|
||||
Func<Stream, bool> CanHandle,
|
||||
Func<Stream, Stream> CreateStream,
|
||||
IEnumerable<string> KnownExtensions,
|
||||
bool WrapInSharpCompressStream = true
|
||||
)
|
||||
{
|
||||
this.Type = Type;
|
||||
this.CanHandle = CanHandle;
|
||||
this.WrapInSharpCompressStream = WrapInSharpCompressStream;
|
||||
this.CreateStream = CreateStream;
|
||||
this.KnownExtensions = KnownExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
|
||||
protected TestOption[] compressionOptions =
|
||||
[
|
||||
new(CompressionType.None, (stream) => true, (stream) => stream, ["tar"], false), // We always do a test for IsTarFile later
|
||||
new(
|
||||
CompressionType.BZip2,
|
||||
BZip2Stream.IsBZip2,
|
||||
(stream) => new BZip2Stream(stream, CompressionMode.Decompress, false),
|
||||
["tar.bz2", "tb2", "tbz", "tbz2", "tz2"]
|
||||
),
|
||||
new(
|
||||
CompressionType.GZip,
|
||||
GZipArchive.IsGZipFile,
|
||||
(stream) => new GZipStream(stream, CompressionMode.Decompress),
|
||||
["tar.gz", "taz", "tgz"]
|
||||
),
|
||||
new(
|
||||
CompressionType.ZStandard,
|
||||
ZStandardStream.IsZStandard,
|
||||
(stream) => new ZStandardStream(stream),
|
||||
["tar.zst", "tar.zstd", "tzst", "tzstd"]
|
||||
),
|
||||
new(
|
||||
CompressionType.LZip,
|
||||
LZipStream.IsLZipFile,
|
||||
(stream) => new LZipStream(stream, CompressionMode.Decompress),
|
||||
["tar.lz"]
|
||||
),
|
||||
new(
|
||||
CompressionType.Xz,
|
||||
XZStream.IsXZStream,
|
||||
(stream) => new XZStream(stream),
|
||||
["tar.xz", "txz"],
|
||||
false
|
||||
),
|
||||
new(
|
||||
CompressionType.Lzw,
|
||||
LzwStream.IsLzwStream,
|
||||
(stream) => new LzwStream(stream),
|
||||
["tar.Z", "tZ", "taZ"],
|
||||
false
|
||||
),
|
||||
];
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override bool TryOpenReader(
|
||||
SharpCompressStream rewindableStream,
|
||||
ReaderOptions options,
|
||||
out IReader? reader
|
||||
)
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options)
|
||||
{
|
||||
reader = null;
|
||||
long pos = ((IStreamStack)rewindableStream).GetPosition();
|
||||
TestOption? testedOption = null;
|
||||
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
|
||||
options ??= new ReaderOptions();
|
||||
var rewindableStream = new SharpCompressStream(stream);
|
||||
long pos = rewindableStream.GetPosition();
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
testedOption = compressionOptions.FirstOrDefault(a =>
|
||||
a.KnownExtensions.Contains(
|
||||
options.ExtensionHint,
|
||||
StringComparer.CurrentCultureIgnoreCase
|
||||
)
|
||||
);
|
||||
if (testedOption != null)
|
||||
rewindableStream.StackSeek(pos);
|
||||
if (wrapper.IsMatch(rewindableStream))
|
||||
{
|
||||
reader = TryOption(rewindableStream, options, pos, testedOption);
|
||||
if (reader != null)
|
||||
rewindableStream.StackSeek(pos);
|
||||
var decompressedStream = wrapper.CreateStream(rewindableStream);
|
||||
if (TarArchive.IsTarFile(decompressedStream))
|
||||
{
|
||||
return true;
|
||||
rewindableStream.StackSeek(pos);
|
||||
return new TarReader(rewindableStream, options, wrapper.CompressionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var testOption in compressionOptions)
|
||||
{
|
||||
if (testedOption == testOption)
|
||||
{
|
||||
continue; // Already tested above
|
||||
}
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
reader = TryOption(rewindableStream, options, pos, testOption);
|
||||
if (reader != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IReader? TryOption(
|
||||
SharpCompressStream rewindableStream,
|
||||
ReaderOptions options,
|
||||
long pos,
|
||||
TestOption testOption
|
||||
)
|
||||
{
|
||||
if (testOption.CanHandle(rewindableStream))
|
||||
{
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
var inStream = rewindableStream;
|
||||
if (testOption.WrapInSharpCompressStream)
|
||||
{
|
||||
inStream = SharpCompressStream.Create(rewindableStream, leaveOpen: true);
|
||||
}
|
||||
var testStream = testOption.CreateStream(rewindableStream);
|
||||
|
||||
if (TarArchive.IsTarFile(testStream))
|
||||
{
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
return new TarReader(rewindableStream, options, testOption.Type);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
TarReader.OpenReader(stream, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public async ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
options ??= new ReaderOptions();
|
||||
var rewindableStream = new SharpCompressStream(stream);
|
||||
long pos = rewindableStream.GetPosition();
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
var decompressedStream = wrapper.CreateStream(rewindableStream);
|
||||
if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken))
|
||||
{
|
||||
rewindableStream.StackSeek(pos);
|
||||
return new TarReader(rewindableStream, options, wrapper.CompressionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (IAsyncReader)TarReader.OpenReader(stream, options);
|
||||
}
|
||||
|
||||
|
||||
92
src/SharpCompress/Factories/TarWrapper.cs
Normal file
92
src/SharpCompress/Factories/TarWrapper.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.GZip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.Lzw;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using SharpCompress.Compressors.ZStandard;
|
||||
|
||||
namespace SharpCompress.Factories;
|
||||
|
||||
public class TarWrapper(
|
||||
CompressionType type,
|
||||
Func<Stream, bool> canHandle,
|
||||
Func<Stream, CancellationToken, ValueTask<bool>> canHandleAsync,
|
||||
Func<Stream, Stream> createStream,
|
||||
IEnumerable<string> knownExtensions,
|
||||
bool wrapInSharpCompressStream = true
|
||||
)
|
||||
{
|
||||
public CompressionType CompressionType { get; } = type;
|
||||
public Func<Stream, bool> IsMatch { get; } = canHandle;
|
||||
public Func<Stream, CancellationToken, ValueTask<bool>> IsMatchAsync { get; } = canHandleAsync;
|
||||
public bool WrapInSharpCompressStream { get; } = wrapInSharpCompressStream;
|
||||
|
||||
public Func<Stream, Stream> CreateStream { get; } = createStream;
|
||||
|
||||
public IEnumerable<string> KnownExtensions { get; } = knownExtensions;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
|
||||
public static TarWrapper[] Wrappers { get; } =
|
||||
[
|
||||
new(
|
||||
CompressionType.None,
|
||||
(_) => true,
|
||||
(_, _) => new ValueTask<bool>(true),
|
||||
(stream) => stream,
|
||||
["tar"],
|
||||
false
|
||||
), // We always do a test for IsTarFile later
|
||||
new(
|
||||
CompressionType.BZip2,
|
||||
BZip2Stream.IsBZip2,
|
||||
BZip2Stream.IsBZip2Async,
|
||||
(stream) => new BZip2Stream(stream, CompressionMode.Decompress, false),
|
||||
["tar.bz2", "tb2", "tbz", "tbz2", "tz2"]
|
||||
),
|
||||
new(
|
||||
CompressionType.GZip,
|
||||
GZipArchive.IsGZipFile,
|
||||
GZipArchive.IsGZipFileAsync,
|
||||
(stream) => new GZipStream(stream, CompressionMode.Decompress),
|
||||
["tar.gz", "taz", "tgz"]
|
||||
),
|
||||
new(
|
||||
CompressionType.ZStandard,
|
||||
ZStandardStream.IsZStandard,
|
||||
ZStandardStream.IsZStandardAsync,
|
||||
(stream) => new ZStandardStream(stream),
|
||||
["tar.zst", "tar.zstd", "tzst", "tzstd"]
|
||||
),
|
||||
new(
|
||||
CompressionType.LZip,
|
||||
LZipStream.IsLZipFile,
|
||||
LZipStream.IsLZipFileAsync,
|
||||
(stream) => new LZipStream(stream, CompressionMode.Decompress),
|
||||
["tar.lz"]
|
||||
),
|
||||
new(
|
||||
CompressionType.Xz,
|
||||
XZStream.IsXZStream,
|
||||
XZStream.IsXZStreamAsync,
|
||||
(stream) => new XZStream(stream),
|
||||
["tar.xz", "txz"],
|
||||
false
|
||||
),
|
||||
new(
|
||||
CompressionType.Lzw,
|
||||
LzwStream.IsLzwStream,
|
||||
LzwStream.IsLzwStreamAsync,
|
||||
(stream) => new LzwStream(stream),
|
||||
["tar.Z", "tZ", "taZ"],
|
||||
false
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -190,14 +190,14 @@ public class ZipFactory
|
||||
ZipReader.OpenReader(stream, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncReader OpenAsyncReader(
|
||||
public ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return (IAsyncReader)ZipReader.OpenReader(stream, options);
|
||||
return new((IAsyncReader)ZipReader.OpenReader(stream, options));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Readers;
|
||||
|
||||
@@ -20,7 +21,7 @@ public interface IReaderFactory : Factories.IFactory
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
IAsyncReader OpenAsyncReader(
|
||||
ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken
|
||||
|
||||
@@ -70,7 +70,7 @@ public static class ReaderFactory
|
||||
|
||||
var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
|
||||
|
||||
long pos = ((IStreamStack)bStream).GetPosition();
|
||||
long pos = bStream.GetPosition();
|
||||
|
||||
var factories = Factories.Factory.Factories.OfType<Factories.Factory>();
|
||||
|
||||
@@ -89,7 +89,7 @@ public static class ReaderFactory
|
||||
{
|
||||
return reader;
|
||||
}
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
bStream.StackSeek(pos);
|
||||
}
|
||||
|
||||
foreach (var factory in factories)
|
||||
@@ -98,7 +98,7 @@ public static class ReaderFactory
|
||||
{
|
||||
continue; // Already tested above
|
||||
}
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
bStream.StackSeek(pos);
|
||||
if (factory.TryOpenReader(bStream, options, out var reader) && reader != null)
|
||||
{
|
||||
return reader;
|
||||
@@ -120,13 +120,11 @@ public static class ReaderFactory
|
||||
options ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
|
||||
var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
|
||||
long pos = bStream.GetPosition();
|
||||
|
||||
long pos = ((IStreamStack)bStream).GetPosition();
|
||||
|
||||
var factories = Factories.Factory.Factories.OfType<Factories.Factory>();
|
||||
var factories = Factory.Factories.OfType<Factory>();
|
||||
|
||||
Factory? testedFactory = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
|
||||
{
|
||||
testedFactory = factories.FirstOrDefault(a =>
|
||||
@@ -135,7 +133,7 @@ public static class ReaderFactory
|
||||
);
|
||||
if (testedFactory is IReaderFactory readerFactory)
|
||||
{
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
bStream.StackSeek(pos);
|
||||
if (
|
||||
await testedFactory.IsArchiveAsync(
|
||||
bStream,
|
||||
@@ -143,11 +141,11 @@ public static class ReaderFactory
|
||||
)
|
||||
)
|
||||
{
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
return readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
|
||||
bStream.StackSeek(pos);
|
||||
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
|
||||
}
|
||||
}
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
bStream.StackSeek(pos);
|
||||
}
|
||||
|
||||
foreach (var factory in factories)
|
||||
@@ -156,14 +154,14 @@ public static class ReaderFactory
|
||||
{
|
||||
continue; // Already tested above
|
||||
}
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
bStream.StackSeek(pos);
|
||||
if (
|
||||
factory is IReaderFactory readerFactory
|
||||
&& await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken)
|
||||
)
|
||||
{
|
||||
((IStreamStack)bStream).StackSeek(pos);
|
||||
return readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
|
||||
bStream.StackSeek(pos);
|
||||
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using SharpCompress.Common;
|
||||
@@ -7,7 +6,7 @@ namespace SharpCompress.Readers.Tar;
|
||||
|
||||
public partial class TarReader
|
||||
#if NET8_0_OR_GREATER
|
||||
: IReaderOpenable
|
||||
: IReaderOpenable
|
||||
#endif
|
||||
{
|
||||
public static IAsyncReader OpenAsyncReader(
|
||||
|
||||
@@ -58,9 +58,7 @@ public partial class TarReader : AbstractReader<TarEntry, TarVolume>
|
||||
stream.NotNull(nameof(stream));
|
||||
options = options ?? new ReaderOptions();
|
||||
var rewindableStream = new SharpCompressStream(stream);
|
||||
|
||||
long pos = ((IStreamStack)rewindableStream).GetPosition();
|
||||
|
||||
if (GZipArchive.IsGZipFile(rewindableStream))
|
||||
{
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
@@ -72,7 +70,6 @@ public partial class TarReader : AbstractReader<TarEntry, TarVolume>
|
||||
}
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
if (BZip2Stream.IsBZip2(rewindableStream))
|
||||
{
|
||||
@@ -85,7 +82,6 @@ public partial class TarReader : AbstractReader<TarEntry, TarVolume>
|
||||
}
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
if (ZStandardStream.IsZStandard(rewindableStream))
|
||||
{
|
||||
@@ -110,7 +106,6 @@ public partial class TarReader : AbstractReader<TarEntry, TarVolume>
|
||||
}
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
((IStreamStack)rewindableStream).StackSeek(pos);
|
||||
return new TarReader(rewindableStream, options, CompressionType.None);
|
||||
}
|
||||
|
||||
@@ -121,8 +121,16 @@ public abstract class ReaderTests : TestBase
|
||||
where T : IFactory
|
||||
{
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
var factory = await ArchiveFactory.FindFactoryAsync<T>(testArchive, cancellationToken);
|
||||
(await factory.IsArchiveAsync(new FileInfo(testArchive).OpenRead(), cancellationToken: cancellationToken)).Should().BeTrue();
|
||||
var factory = new TarFactory();
|
||||
factory.IsArchive(new FileInfo(testArchive).OpenRead()).Should().BeTrue();
|
||||
(
|
||||
await factory.IsArchiveAsync(
|
||||
new FileInfo(testArchive).OpenRead(),
|
||||
cancellationToken: cancellationToken
|
||||
)
|
||||
)
|
||||
.Should()
|
||||
.BeTrue();
|
||||
}
|
||||
|
||||
protected async Task ReadAsync(
|
||||
|
||||
@@ -46,10 +46,8 @@ public class TarReaderAsyncTests : ReaderTests
|
||||
public async ValueTask Tar_Z_Reader_Async() =>
|
||||
await ReadAsync("Tar.tar.Z", CompressionType.Lzw);
|
||||
|
||||
|
||||
[Fact]
|
||||
public async ValueTask Tar_BZip2_Reader_Async_Assert() =>
|
||||
await AssertArchiveAsync<TarFactory>("Tar.tar.bz2", default);
|
||||
public async ValueTask Tar_Async_Assert() => await AssertArchiveAsync<TarFactory>("Tar.tar");
|
||||
|
||||
[Fact]
|
||||
public async ValueTask Tar_BZip2_Reader_Async() =>
|
||||
|
||||
Reference in New Issue
Block a user