mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 06:54:40 +00:00
Merge pull request #1360 from adamhathcock/adam/remove-compressed-tar-archive
Remove compressed tar archive
This commit is contained in:
@@ -249,15 +249,15 @@ GNU tar uses base-256 binary fields for out-of-range numeric values. SharpCompre
|
||||
|
||||
Wrapper detection is defined in `TarWrapper.Wrappers`. Detection is content-based: wrapper detection is followed by a tar-header probe of the decompressed payload.
|
||||
|
||||
| Wrapper | Extensions | Read | Write |
|
||||
| ------- | ---------- | ---- | ----- |
|
||||
| Plain tar | `tar` | Yes | Yes |
|
||||
| Tar + GZip | `tar.gz`, `taz`, `tgz` | Yes | Yes |
|
||||
| Tar + BZip2 | `tar.bz2`, `tb2`, `tbz`, `tbz2`, `tz2` | Yes | Yes |
|
||||
| Tar + LZip | `tar.lz` | Yes | Yes |
|
||||
| Tar + XZ | `tar.xz`, `txz` | Yes | No |
|
||||
| Tar + ZStandard | `tar.zst`, `tar.zstd`, `tzst`, `tzstd` | Yes | No |
|
||||
| Tar + LZW compress | `tar.Z`, `tZ`, `taZ` | Yes | No |
|
||||
| Wrapper | Extensions | Archive Read | Reader Read | Write |
|
||||
| ------- | ---------- | ------------ | ----------- | ----- |
|
||||
| Plain tar | `tar` | Yes | Yes | Yes |
|
||||
| Tar + GZip | `tar.gz`, `taz`, `tgz` | No | Yes | Yes |
|
||||
| Tar + BZip2 | `tar.bz2`, `tb2`, `tbz`, `tbz2`, `tz2` | No | Yes | Yes |
|
||||
| Tar + LZip | `tar.lz` | No | Yes | Yes |
|
||||
| Tar + XZ | `tar.xz`, `txz` | No | Yes | No |
|
||||
| Tar + ZStandard | `tar.zst`, `tar.zstd`, `tzst`, `tzstd` | No | Yes | No |
|
||||
| Tar + LZW compress | `tar.Z`, `tZ`, `taZ` | No | Yes | No |
|
||||
|
||||
Writer support currently accepts only these compression types:
|
||||
|
||||
@@ -278,9 +278,9 @@ Reader API:
|
||||
|
||||
Archive API:
|
||||
|
||||
- `TarArchive.OpenArchive(Stream)` and `TarArchive.OpenAsyncArchive(Stream)` require seekable streams.
|
||||
- `TarArchive.OpenArchive(Stream)` and `TarArchive.OpenAsyncArchive(Stream)` require seekable raw tar streams.
|
||||
- File/path overloads own the opened file stream.
|
||||
- Compressed tar archive access follows streaming semantics over the decompressed stream rather than full random-access semantics.
|
||||
- Compressed tar wrappers are supported through `TarReader`, not `TarArchive` or `ArchiveFactory`; archive detection blocks them instead of opening the outer compression wrapper as a standalone archive.
|
||||
|
||||
Parsed metadata surfaced through entries includes:
|
||||
|
||||
@@ -323,7 +323,7 @@ Keep these limitations explicit in code comments, docs, and tests:
|
||||
- No write support for `tar.xz`, `tar.zst`, or `tar.Z`.
|
||||
- PAX read support is limited to `path`, `linkpath`, `size`, `mtime`, `uid`, `gid`, and `mode`.
|
||||
- Unknown PAX keys are ignored.
|
||||
- Stream-based `TarArchive` open requires seekable input.
|
||||
- Stream-based `TarArchive` open requires seekable raw tar input.
|
||||
|
||||
## Test Fixtures
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ using (var archive = ArchiveFactory.OpenArchive(parts))
|
||||
}
|
||||
```
|
||||
|
||||
`ArchiveInformation.SupportsRandomAccess` is `true` when the detected format supports `IArchive` random access. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW, where `ReaderFactory.OpenReader` should be used instead.
|
||||
`ArchiveInformation.SupportsRandomAccess` is `true` when the detected format supports `IArchive` random access. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW, where `ReaderFactory.OpenReader` should be used instead. Compressed tar wrappers such as `.tar.gz` and `.tar.xz` are also reader-only; `ArchiveFactory.GetArchiveInformation` returns `null` for them and `ArchiveFactory.OpenArchive` does not open them as the outer compression wrapper. Use `ReaderFactory.OpenReader` or `TarReader.OpenReader` for those files.
|
||||
|
||||
### Creating Archives
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
| Rar | Rar | Decompress | RarArchive | RarReader | N/A |
|
||||
| Zip (2) | None, Shrink, Reduce, Implode, DEFLATE, Deflate64, BZip2, LZMA, PPMd, ZStandard, XZ | Both | ZipArchive | ZipReader | ZipWriter |
|
||||
| Tar | None | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.GZip | DEFLATE | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.Zstandard | ZStandard | Decompress | TarArchive | TarReader | N/A |
|
||||
| Tar.LZip | LZMA | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | N/A |
|
||||
| Tar.LZW | LZW | Decompress | TarArchive | TarReader | N/A |
|
||||
| Tar.GZip | DEFLATE | Both | N/A | TarReader | TarWriter (3) |
|
||||
| Tar.BZip2 | BZip2 | Both | N/A | TarReader | TarWriter (3) |
|
||||
| Tar.Zstandard | ZStandard | Decompress | N/A | TarReader | N/A |
|
||||
| Tar.LZip | LZMA | Both | N/A | TarReader | TarWriter (3) |
|
||||
| Tar.XZ | LZMA2 | Decompress | N/A | TarReader | N/A |
|
||||
| Tar.LZW | LZW | Decompress | N/A | TarReader | N/A |
|
||||
| GZip (single file) | DEFLATE | Both | GZipArchive | GZipReader | GZipWriter |
|
||||
| 7Zip (4) | LZMA, LZMA2, BZip2, PPMd, BCJ, BCJ2, Deflate | Both | SevenZipArchive | N/A | SevenZipWriter |
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
4. The 7Zip format doesn't allow for reading as a forward-only stream, so 7Zip read support is only through the Archive API. Writing is supported through SevenZipWriter for non-solid archives with LZMA/LZMA2 and requires a seekable output stream. See [7Zip Format Notes](#7zip-format-notes) for details on async extraction behavior.
|
||||
5. LZip has no support for extra data like the file name or timestamp. There is a default filename used when looking at the entry Key on the archive.
|
||||
|
||||
`ArchiveFactory.GetArchiveInformation(...).SupportsRandomAccess` is `true` when the detected format has an Archive API in this table. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW.
|
||||
`ArchiveFactory.GetArchiveInformation(...).SupportsRandomAccess` is `true` when the detected format has an Archive API in this table. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW. Compressed tar wrappers are supported by `ReaderFactory`/`TarReader`, not by `ArchiveFactory`/`TarArchive`; ArchiveFactory detection blocks them instead of opening the outer compression wrapper as a standalone archive.
|
||||
|
||||
### Zip Format Notes
|
||||
|
||||
|
||||
@@ -57,18 +57,18 @@ Tar wrapper detection is defined by `TarWrapper.Wrappers` in `src/SharpCompress/
|
||||
| Wrapper | Detection | `TarArchive` read | `TarReader` read | `TarWriter` write |
|
||||
| ------- | --------- | ----------------- | ---------------- | ----------------- |
|
||||
| Plain tar | Yes | Yes | Yes | Yes |
|
||||
| Tar + GZip | Yes | Yes | Yes | Yes |
|
||||
| Tar + BZip2 | Yes | Yes | Yes | Yes |
|
||||
| Tar + LZip | Yes | Yes | Yes | Yes |
|
||||
| Tar + XZ | Yes | Yes | Yes | No |
|
||||
| Tar + ZStandard | Yes | Yes | Yes | No |
|
||||
| Tar + LZW compress | Yes | Yes | Yes | No |
|
||||
| Tar + GZip | Reader only | No | Yes | Yes |
|
||||
| Tar + BZip2 | Reader only | No | Yes | Yes |
|
||||
| Tar + LZip | Reader only | No | Yes | Yes |
|
||||
| Tar + XZ | Reader only | No | Yes | No |
|
||||
| Tar + ZStandard | Reader only | No | Yes | No |
|
||||
| Tar + LZW compress | Reader only | No | Yes | No |
|
||||
|
||||
Write support is implemented in `src/SharpCompress/Writers/Tar/TarWriter.cs` and currently accepts only `CompressionType.None`, `CompressionType.GZip`, `CompressionType.BZip2`, and `CompressionType.LZip`.
|
||||
|
||||
## Detection Behavior
|
||||
|
||||
Tar detection is implemented in `TarFactory.IsArchive`, `TarFactory.IsArchiveAsync`, `TarFactory.GetCompressionType`, and `TarFactory.GetCompressionTypeAsync`.
|
||||
Tar reader detection is implemented in `TarFactory.TryOpenReader`, `TarFactory.TryOpenReaderAsync`, `TarFactory.GetCompressionType`, and `TarFactory.GetCompressionTypeAsync`. Archive detection through `TarFactory.IsArchive` and `TarFactory.IsArchiveAsync` accepts raw tar only.
|
||||
|
||||
Detection behavior is:
|
||||
|
||||
@@ -77,13 +77,14 @@ Detection behavior is:
|
||||
3. Probe each registered wrapper in order.
|
||||
4. If a wrapper matches, create a decompression stream for that wrapper.
|
||||
5. Call `TarArchive.IsTarFile` or `TarArchive.IsTarFileAsync` on the decompressed stream.
|
||||
6. If the tar probe succeeds, treat the stream as tar with that wrapper compression.
|
||||
6. If the tar probe succeeds, open `TarReader` with that wrapper compression.
|
||||
|
||||
Implications:
|
||||
|
||||
- Tar detection is content-based, not extension-based.
|
||||
- Wrapper detection is not sufficient by itself. The decompressed payload must also parse as tar.
|
||||
- Non-seekable detection is supported through the recording and rewind mechanism.
|
||||
- Non-seekable wrapper detection is supported through the recording and rewind mechanism on the reader path.
|
||||
- `ArchiveFactory`/`TarArchive` do not open compressed tar wrappers and do not fall through to the outer compression wrapper archive; use `ReaderFactory`/`TarReader` for `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.tar.zst`, `.tar.lz`, and `.tar.Z`.
|
||||
- The largest rewind requirement currently comes from BZip2, which declares a larger minimum probe buffer in `TarWrapper`.
|
||||
|
||||
`TarArchive.IsTarFile` and `TarArchive.IsTarFileAsync` attempt to read a single tar header and return `false` on any exception. They also treat an all-zero empty archive block as a valid empty tar archive when the entry type is defined.
|
||||
@@ -151,20 +152,15 @@ Implementation files:
|
||||
|
||||
`TarArchive.OpenArchive(Stream)` and `TarArchive.OpenAsyncArchive(Stream)` require a seekable stream and throw `ArgumentException` when `CanSeek` is `false`.
|
||||
|
||||
`TarArchive.OpenArchive(FileInfo)` and the list-based overloads use `SourceStream` and determine wrapper compression by calling `TarFactory.GetCompressionType`.
|
||||
`TarArchive.OpenArchive(FileInfo)` and the list-based overloads use `SourceStream` and validate the input as raw tar.
|
||||
|
||||
Asynchronous `OpenAsyncArchive` overloads use `TarFactory.GetCompressionTypeAsync` for wrapper detection.
|
||||
Asynchronous `OpenAsyncArchive` overloads perform the same raw-tar validation.
|
||||
|
||||
### Entry Loading
|
||||
|
||||
`TarArchive.LoadEntries` and `LoadEntriesAsync` parse entries differently depending on wrapper compression:
|
||||
`TarArchive.LoadEntries` and `LoadEntriesAsync` parse raw tar streams in `StreamingMode.Seekable`. The header stores `DataStartPosition`, and entries reopen data through `TarFilePart` by seeking back to the data position.
|
||||
|
||||
- Uncompressed tar uses `StreamingMode.Seekable`.
|
||||
- Wrapped tar uses `StreamingMode.Streaming` because the decompressed stream is not treated as random-access.
|
||||
|
||||
When seekable mode is used, the header stores `DataStartPosition`, and entries reopen data through `TarFilePart` by seeking back to the data position.
|
||||
|
||||
When streaming mode is used, the header stores a `PackedStream`, and entry access follows streaming semantics over the decompressed stream.
|
||||
Wrapped tar streams use `TarReader`, whose forward-only entry access follows streaming semantics over the decompressed stream.
|
||||
|
||||
### Archive Rewrite Behavior
|
||||
|
||||
@@ -418,7 +414,7 @@ This section documents current implementation limits, not desired future behavio
|
||||
### Archive behavior limitations
|
||||
|
||||
- Stream-based archive open requires a seekable input stream
|
||||
- Compressed tar archive access is not full random-access in the same sense as uncompressed seekable tar
|
||||
- Compressed tar wrappers are supported through `TarReader`, not `TarArchive`
|
||||
|
||||
## Test Coverage Map
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@ public static partial class ArchiveFactory
|
||||
)
|
||||
{
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken)
|
||||
var factory = await FindFactoryAsync<IArchiveFactory>(
|
||||
stream,
|
||||
readerOptions,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return await factory
|
||||
.OpenAsyncArchive(stream, readerOptions, cancellationToken)
|
||||
@@ -46,7 +50,7 @@ public static partial class ArchiveFactory
|
||||
{
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
|
||||
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken)
|
||||
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return await factory
|
||||
.OpenAsyncArchive(fileInfo, options, cancellationToken)
|
||||
@@ -76,7 +80,11 @@ public static partial class ArchiveFactory
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
|
||||
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken)
|
||||
var factory = await FindFactoryAsync<IMultiArchiveFactory>(
|
||||
fileInfo,
|
||||
options,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return await factory
|
||||
.OpenAsyncArchive(filesArray, options, cancellationToken)
|
||||
@@ -101,7 +109,11 @@ public static partial class ArchiveFactory
|
||||
firstStream.NotNull(nameof(firstStream));
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken)
|
||||
var factory = await FindFactoryAsync<IMultiArchiveFactory>(
|
||||
firstStream,
|
||||
options,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return await factory
|
||||
.OpenAsyncArchive(streamsArray, options, cancellationToken)
|
||||
|
||||
@@ -2,8 +2,11 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Providers;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -107,6 +110,19 @@ public static partial class ArchiveFactory
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -120,7 +136,22 @@ public static partial class ArchiveFactory
|
||||
// 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);
|
||||
return await FindFactoryAsync<T>(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = await TryFindFactoryAsync(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
return typedFactory;
|
||||
@@ -134,18 +165,11 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
|
||||
/// Async counterpart of the synchronous factory detection path.
|
||||
/// 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,
|
||||
@@ -163,6 +187,15 @@ public static partial class ArchiveFactory
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
if (
|
||||
await IsCompressedTarAsync(stream, factory, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
@@ -253,6 +286,12 @@ public static partial class ArchiveFactory
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
if (IsCompressedTar(stream, factory, readerOptions))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
@@ -261,4 +300,115 @@ public static partial class ArchiveFactory
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsCompressedTar(
|
||||
Stream stream,
|
||||
IFactory factory,
|
||||
ReaderOptions readerOptions
|
||||
) =>
|
||||
GetCompressedTarType(factory) is { } compressionType
|
||||
&& IsCompressedTar(stream, readerOptions, compressionType);
|
||||
|
||||
private static bool IsCompressedTar(
|
||||
Stream stream,
|
||||
ReaderOptions readerOptions,
|
||||
CompressionType compressionType
|
||||
)
|
||||
{
|
||||
using var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream);
|
||||
var testStream =
|
||||
compressionType == CompressionType.GZip
|
||||
? readerOptions.Providers.CreateDecompressStream(
|
||||
compressionType,
|
||||
nonDisposingStream,
|
||||
CompressionContext
|
||||
.FromStream(nonDisposingStream)
|
||||
.WithReaderOptions(readerOptions)
|
||||
)
|
||||
: readerOptions.Providers.CreateDecompressStream(
|
||||
compressionType,
|
||||
nonDisposingStream
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
return TarArchive.IsTarFile(testStream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeProbeStream(testStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<bool> IsCompressedTarAsync(
|
||||
Stream stream,
|
||||
IFactory factory,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
GetCompressedTarType(factory) is { } compressionType
|
||||
&& await IsCompressedTarAsync(stream, readerOptions, compressionType, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
private static async ValueTask<bool> IsCompressedTarAsync(
|
||||
Stream stream,
|
||||
ReaderOptions readerOptions,
|
||||
CompressionType compressionType,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream);
|
||||
var testStream =
|
||||
compressionType == CompressionType.GZip
|
||||
? await readerOptions
|
||||
.Providers.CreateDecompressStreamAsync(
|
||||
compressionType,
|
||||
nonDisposingStream,
|
||||
CompressionContext
|
||||
.FromStream(nonDisposingStream)
|
||||
.WithReaderOptions(readerOptions),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false)
|
||||
: await readerOptions
|
||||
.Providers.CreateDecompressStreamAsync(
|
||||
compressionType,
|
||||
nonDisposingStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
return await TarArchive
|
||||
.IsTarFileAsync(testStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeProbeStream(testStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisposeProbeStream(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
#pragma warning disable VSTHRD103 // Probe streams may validate unread trailers during disposal.
|
||||
stream.Dispose();
|
||||
#pragma warning restore VSTHRD103
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Probes intentionally read only enough data to identify tar content.
|
||||
}
|
||||
}
|
||||
|
||||
private static CompressionType? GetCompressedTarType(IFactory factory) =>
|
||||
factory switch
|
||||
{
|
||||
GZipFactory => CompressionType.GZip,
|
||||
LzwFactory => CompressionType.Lzw,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ public static partial class ArchiveFactory
|
||||
public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
return FindFactory<IArchiveFactory>(stream).OpenArchive(stream, readerOptions);
|
||||
return FindFactory<IArchiveFactory>(stream, readerOptions)
|
||||
.OpenArchive(stream, readerOptions);
|
||||
}
|
||||
|
||||
public static IWritableArchive<TOptions> CreateArchive<TOptions>()
|
||||
@@ -44,7 +45,7 @@ public static partial class ArchiveFactory
|
||||
{
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
|
||||
return FindFactory<IArchiveFactory>(fileInfo).OpenArchive(fileInfo, options);
|
||||
return FindFactory<IArchiveFactory>(fileInfo, options).OpenArchive(fileInfo, options);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
@@ -68,7 +69,8 @@ public static partial class ArchiveFactory
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
|
||||
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
|
||||
return FindFactory<IMultiArchiveFactory>(fileInfo, options)
|
||||
.OpenArchive(filesArray, options);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(IReadOnlyList<Stream> streams, ReaderOptions? options = null)
|
||||
@@ -88,7 +90,8 @@ public static partial class ArchiveFactory
|
||||
firstStream.NotNull(nameof(firstStream));
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
return FindFactory<IMultiArchiveFactory>(firstStream).OpenArchive(streamsArray, options);
|
||||
return FindFactory<IMultiArchiveFactory>(firstStream, options)
|
||||
.OpenArchive(streamsArray, options);
|
||||
}
|
||||
|
||||
public static void WriteToDirectory(
|
||||
@@ -118,6 +121,17 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static T FindFactory<T>(Stream stream)
|
||||
where T : IFactory => FindFactory<T>(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
private static T FindFactory<T>(FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return FindFactory<T>(stream, readerOptions);
|
||||
}
|
||||
|
||||
private static T FindFactory<T>(Stream stream, ReaderOptions readerOptions)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.RequireReadable();
|
||||
@@ -127,7 +141,7 @@ public static partial class ArchiveFactory
|
||||
// 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);
|
||||
var factory = TryFindFactory(stream, readerOptions);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
return typedFactory;
|
||||
|
||||
@@ -4,12 +4,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Common.Tar;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Tar;
|
||||
|
||||
namespace SharpCompress.Archives.Tar;
|
||||
@@ -85,7 +83,7 @@ public partial class TarArchive
|
||||
{
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
return new((IAsyncReader)new TarReader(stream, ReaderOptions, _compressionType));
|
||||
return new((IAsyncReader)new TarReader(stream, ReaderOptions, CompressionType.None));
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<TarArchiveEntry> LoadEntriesAsync(
|
||||
@@ -93,20 +91,15 @@ public partial class TarArchive
|
||||
)
|
||||
{
|
||||
var sourceStream = (await volumes.SingleAsync().ConfigureAwait(false)).Stream;
|
||||
var stream = await GetStreamAsync(sourceStream).ConfigureAwait(false);
|
||||
var stream = sourceStream;
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
var streamingMode =
|
||||
_compressionType == CompressionType.None
|
||||
? StreamingMode.Seekable
|
||||
: StreamingMode.Streaming;
|
||||
|
||||
await foreach (
|
||||
var header in TarHeaderFactory.ReadHeaderAsync(
|
||||
streamingMode,
|
||||
StreamingMode.Seekable,
|
||||
stream,
|
||||
ReaderOptions.ArchiveEncoding
|
||||
)
|
||||
@@ -116,10 +109,7 @@ public partial class TarArchive
|
||||
{
|
||||
yield return new TarArchiveEntry(
|
||||
this,
|
||||
new TarFilePart(
|
||||
header,
|
||||
_compressionType == CompressionType.None ? stream : null
|
||||
),
|
||||
new TarFilePart(header, stream),
|
||||
CompressionType.None,
|
||||
ReaderOptions
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers.Tar;
|
||||
@@ -53,12 +52,7 @@ public partial class TarArchive
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
sourceStream.ReaderOptions
|
||||
);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
return OpenValidatedArchive(sourceStream);
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
@@ -72,12 +66,7 @@ public partial class TarArchive
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
sourceStream.ReaderOptions
|
||||
);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
return OpenValidatedArchive(sourceStream);
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
@@ -104,11 +93,8 @@ public partial class TarArchive
|
||||
i => null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
return await OpenValidatedArchiveAsync(sourceStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
@@ -132,11 +118,8 @@ public partial class TarArchive
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
var sourceStream = new SourceStream(fileInfo, i => null, readerOptions);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
return await OpenValidatedArchiveAsync(sourceStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
}
|
||||
|
||||
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
@@ -152,11 +135,8 @@ public partial class TarArchive
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
return await OpenValidatedArchiveAsync(sourceStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
}
|
||||
|
||||
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
@@ -173,11 +153,8 @@ public partial class TarArchive
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
return await OpenValidatedArchiveAsync(sourceStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
sourceStream.Seek(0, SeekOrigin.Begin);
|
||||
return new TarArchive(sourceStream, compressionType);
|
||||
}
|
||||
|
||||
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
|
||||
@@ -226,14 +203,16 @@ public partial class TarArchive
|
||||
#else
|
||||
using var reader = new AsyncBinaryReader(stream, leaveOpen: true);
|
||||
#endif
|
||||
var readSucceeded = await tarHeader.ReadAsync(reader).ConfigureAwait(false);
|
||||
var readSucceeded = await tarHeader
|
||||
.ReadAsync(reader, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var isEmptyArchive =
|
||||
tarHeader.Name?.Length == 0
|
||||
&& tarHeader.Size == 0
|
||||
&& IsDefined(tarHeader.EntryType);
|
||||
return readSucceeded || isEmptyArchive;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
// Catch all exceptions during tar header reading to determine if this is a valid tar file
|
||||
// Invalid tar files or corrupted streams will throw various exceptions
|
||||
@@ -246,6 +225,66 @@ public partial class TarArchive
|
||||
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> CreateAsyncArchive() =>
|
||||
new(new TarArchive());
|
||||
|
||||
private static TarArchive OpenValidatedArchive(SourceStream sourceStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureRawTarFile(sourceStream);
|
||||
return new TarArchive(sourceStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
sourceStream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<TarArchive> OpenValidatedArchiveAsync(
|
||||
SourceStream sourceStream,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureRawTarFileAsync(sourceStream, cancellationToken).ConfigureAwait(false);
|
||||
return new TarArchive(sourceStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER
|
||||
await sourceStream.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
sourceStream.Dispose();
|
||||
#endif
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureRawTarFile(Stream stream)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (!IsTarFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
private static async ValueTask EnsureRawTarFileAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (!await IsTarFileAsync(stream, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
private static bool IsDefined(EntryType value)
|
||||
{
|
||||
#if LEGACY_DOTNET
|
||||
|
||||
@@ -2,12 +2,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.Common.Tar;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Providers;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Writers.Tar;
|
||||
@@ -17,108 +14,28 @@ namespace SharpCompress.Archives.Tar;
|
||||
public partial class TarArchive
|
||||
: AbstractWritableArchive<TarArchiveEntry, TarVolume, TarWriterOptions>
|
||||
{
|
||||
private readonly CompressionType _compressionType;
|
||||
|
||||
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
|
||||
{
|
||||
sourceStream.NotNull("SourceStream is null").LoadAllParts();
|
||||
return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable();
|
||||
}
|
||||
|
||||
internal TarArchive(SourceStream sourceStream, CompressionType compressionType)
|
||||
: base(ArchiveType.Tar, sourceStream)
|
||||
{
|
||||
_compressionType = compressionType;
|
||||
}
|
||||
internal TarArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.Tar, sourceStream) { }
|
||||
|
||||
private TarArchive()
|
||||
: base(ArchiveType.Tar) { }
|
||||
|
||||
private Stream GetStream(Stream stream) =>
|
||||
_compressionType switch
|
||||
{
|
||||
CompressionType.BZip2 => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.BZip2,
|
||||
stream
|
||||
),
|
||||
CompressionType.GZip => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.GZip,
|
||||
stream,
|
||||
CompressionContext.FromStream(stream).WithReaderOptions(ReaderOptions)
|
||||
),
|
||||
CompressionType.ZStandard => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.ZStandard,
|
||||
stream
|
||||
),
|
||||
CompressionType.LZip => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.LZip,
|
||||
stream
|
||||
),
|
||||
CompressionType.Xz => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.Xz,
|
||||
stream
|
||||
),
|
||||
CompressionType.Lzw => ReaderOptions.Providers.CreateDecompressStream(
|
||||
CompressionType.Lzw,
|
||||
stream
|
||||
),
|
||||
CompressionType.None => stream,
|
||||
_ => throw new NotSupportedException("Invalid compression type: " + _compressionType),
|
||||
};
|
||||
|
||||
private ValueTask<Stream> GetStreamAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
_compressionType switch
|
||||
{
|
||||
CompressionType.BZip2 => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.BZip2,
|
||||
stream,
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.GZip => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.GZip,
|
||||
stream,
|
||||
CompressionContext.FromStream(stream).WithReaderOptions(ReaderOptions),
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.ZStandard => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.ZStandard,
|
||||
stream,
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.LZip => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.LZip,
|
||||
stream,
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.Xz => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.Xz,
|
||||
stream,
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.Lzw => ReaderOptions.Providers.CreateDecompressStreamAsync(
|
||||
CompressionType.Lzw,
|
||||
stream,
|
||||
cancellationToken
|
||||
),
|
||||
CompressionType.None => new ValueTask<Stream>(stream),
|
||||
_ => throw new NotSupportedException("Invalid compression type: " + _compressionType),
|
||||
};
|
||||
|
||||
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
|
||||
{
|
||||
var stream = GetStream(volumes.Single().Stream);
|
||||
var stream = volumes.Single().Stream;
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
foreach (
|
||||
var header in TarHeaderFactory.ReadHeader(
|
||||
_compressionType == CompressionType.None
|
||||
? StreamingMode.Seekable
|
||||
: StreamingMode.Streaming,
|
||||
StreamingMode.Seekable,
|
||||
stream,
|
||||
ReaderOptions.ArchiveEncoding
|
||||
)
|
||||
@@ -128,10 +45,7 @@ public partial class TarArchive
|
||||
{
|
||||
yield return new TarArchiveEntry(
|
||||
this,
|
||||
new TarFilePart(
|
||||
header,
|
||||
_compressionType == CompressionType.None ? stream : null
|
||||
),
|
||||
new TarFilePart(header, stream),
|
||||
CompressionType.None,
|
||||
ReaderOptions
|
||||
);
|
||||
@@ -197,6 +111,6 @@ public partial class TarArchive
|
||||
{
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
return new TarReader(stream, ReaderOptions, _compressionType);
|
||||
return new TarReader(stream, ReaderOptions, CompressionType.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,8 @@ internal sealed partial class TarHeader
|
||||
|
||||
internal async ValueTask<bool> ReadAsync(
|
||||
AsyncBinaryReader reader,
|
||||
PaxMetadata? globalPaxMetadata = null
|
||||
PaxMetadata? globalPaxMetadata = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
globalPaxMetadata ??= new PaxMetadata();
|
||||
@@ -213,35 +214,45 @@ internal sealed partial class TarHeader
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await reader.ReadBytesAsync(buffer, 0, BLOCK_SIZE).ConfigureAwait(false);
|
||||
await reader
|
||||
.ReadBytesAsync(buffer, 0, BLOCK_SIZE, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
entryType = ReadEntryType(buffer);
|
||||
|
||||
// LongName and LongLink headers can follow each other and need
|
||||
// to apply to the header that follows them.
|
||||
if (entryType == EntryType.LongName)
|
||||
{
|
||||
pendingMetadata.Name = await ReadLongNameAsync(reader, buffer)
|
||||
pendingMetadata.Name = await ReadLongNameAsync(
|
||||
reader,
|
||||
buffer,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entryType == EntryType.LongLink)
|
||||
{
|
||||
pendingMetadata.LinkName = await ReadLongNameAsync(reader, buffer)
|
||||
pendingMetadata.LinkName = await ReadLongNameAsync(
|
||||
reader,
|
||||
buffer,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entryType == EntryType.LocalExtendedHeader)
|
||||
{
|
||||
await ReadPaxMetadataAsync(reader, buffer, pendingMetadata)
|
||||
await ReadPaxMetadataAsync(reader, buffer, pendingMetadata, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entryType == EntryType.GlobalExtendedHeader)
|
||||
{
|
||||
await ReadPaxMetadataAsync(reader, buffer, globalPaxMetadata)
|
||||
await ReadPaxMetadataAsync(reader, buffer, globalPaxMetadata, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
pendingMetadata = globalPaxMetadata.Clone();
|
||||
continue;
|
||||
@@ -306,12 +317,16 @@ internal sealed partial class TarHeader
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async ValueTask ReadLengthAsync(AsyncBinaryReader reader, int length)
|
||||
private static async ValueTask ReadLengthAsync(
|
||||
AsyncBinaryReader reader,
|
||||
int length,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
await reader.ReadBytesAsync(buffer, 0, length).ConfigureAwait(false);
|
||||
await reader.ReadBytesAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -319,13 +334,18 @@ internal sealed partial class TarHeader
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<string> ReadLongNameAsync(AsyncBinaryReader reader, byte[] buffer)
|
||||
private async ValueTask<string> ReadLongNameAsync(
|
||||
AsyncBinaryReader reader,
|
||||
byte[] buffer,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var nameBytes = await ReadMetadataPayloadAsync(
|
||||
reader,
|
||||
buffer,
|
||||
MAX_LONG_NAME_SIZE,
|
||||
"Long name"
|
||||
"Long name",
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -335,14 +355,16 @@ internal sealed partial class TarHeader
|
||||
private async ValueTask ReadPaxMetadataAsync(
|
||||
AsyncBinaryReader reader,
|
||||
byte[] buffer,
|
||||
PaxMetadata pendingMetadata
|
||||
PaxMetadata pendingMetadata,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var payload = await ReadMetadataPayloadAsync(
|
||||
reader,
|
||||
buffer,
|
||||
MAX_PAX_HEADER_SIZE,
|
||||
"PAX header"
|
||||
"PAX header",
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -353,7 +375,8 @@ internal sealed partial class TarHeader
|
||||
AsyncBinaryReader reader,
|
||||
byte[] buffer,
|
||||
int maxSize,
|
||||
string payloadName
|
||||
string payloadName,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var size = ReadSize(buffer);
|
||||
@@ -368,12 +391,14 @@ internal sealed partial class TarHeader
|
||||
|
||||
var payloadLength = (int)size;
|
||||
var payload = new byte[payloadLength];
|
||||
await reader.ReadBytesAsync(payload, 0, payloadLength).ConfigureAwait(false);
|
||||
await reader
|
||||
.ReadBytesAsync(payload, 0, payloadLength, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var paddingLength = GetPaddingLength(payloadLength);
|
||||
if (paddingLength > 0)
|
||||
{
|
||||
await ReadLengthAsync(reader, paddingLength).ConfigureAwait(false);
|
||||
await ReadLengthAsync(reader, paddingLength, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return payload;
|
||||
|
||||
@@ -161,6 +161,36 @@ public class GZipFactory
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override async ValueTask<IAsyncReader?> TryOpenReaderAsync(
|
||||
SharpCompressStream sharpCompressStream,
|
||||
ReaderOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (
|
||||
!await GZipArchive
|
||||
.IsGZipFileAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
return null;
|
||||
}
|
||||
|
||||
sharpCompressStream.Rewind();
|
||||
var tarReader = await new TarFactory()
|
||||
.TryOpenReaderAsync(sharpCompressStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (tarReader is not null)
|
||||
{
|
||||
return tarReader;
|
||||
}
|
||||
|
||||
sharpCompressStream.StopRecording();
|
||||
return await OpenAsyncReader(sharpCompressStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
GZipReader.OpenReader(stream, options);
|
||||
|
||||
@@ -80,6 +80,36 @@ public class LzwFactory : Factory, IReaderFactory
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override async ValueTask<IAsyncReader?> TryOpenReaderAsync(
|
||||
SharpCompressStream sharpCompressStream,
|
||||
ReaderOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (
|
||||
!await LzwStream
|
||||
.IsLzwStreamAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
return null;
|
||||
}
|
||||
|
||||
sharpCompressStream.Rewind();
|
||||
var tarReader = await new TarFactory()
|
||||
.TryOpenReaderAsync(sharpCompressStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (tarReader is not null)
|
||||
{
|
||||
return tarReader;
|
||||
}
|
||||
|
||||
sharpCompressStream.StopRecording();
|
||||
return await OpenAsyncReader(sharpCompressStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
LzwReader.OpenReader(stream, options);
|
||||
|
||||
@@ -50,28 +50,7 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
if (wrapper.IsMatch(sharpCompressStream))
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
var decompressedStream = CreateProbeDecompressionStream(
|
||||
sharpCompressStream,
|
||||
wrapper.CompressionType
|
||||
);
|
||||
if (TarArchive.IsTarFile(decompressedStream))
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return TarArchive.IsTarFile(stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -81,38 +60,7 @@ public class TarFactory
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
if (
|
||||
await wrapper
|
||||
.IsMatchAsync(sharpCompressStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
var decompressedStream = await CreateProbeDecompressionStreamAsync(
|
||||
sharpCompressStream,
|
||||
wrapper.CompressionType,
|
||||
cancellationToken: cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
if (
|
||||
await TarArchive
|
||||
.IsTarFileAsync(decompressedStream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
sharpCompressStream.Rewind();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return await TarArchive.IsTarFileAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -234,6 +182,44 @@ public class TarFactory
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
internal override bool TryOpenReader(
|
||||
SharpCompressStream stream,
|
||||
ReaderOptions options,
|
||||
out IReader? reader
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.Rewind();
|
||||
reader = OpenReader(stream, options);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidFormatException)
|
||||
{
|
||||
stream.Rewind();
|
||||
reader = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal override async ValueTask<IAsyncReader?> TryOpenReaderAsync(
|
||||
SharpCompressStream stream,
|
||||
ReaderOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.Rewind();
|
||||
return await OpenAsyncReader(stream, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (InvalidFormatException)
|
||||
{
|
||||
stream.Rewind();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region IArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -378,8 +364,7 @@ public class TarFactory
|
||||
}
|
||||
}
|
||||
|
||||
sharpCompressStream.Rewind();
|
||||
return (IAsyncReader)TarReader.OpenReader(sharpCompressStream, options);
|
||||
throw new InvalidFormatException("Not a tar file.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -407,18 +407,8 @@ public class ArchiveFactoryTests : TestBase
|
||||
[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)]
|
||||
@@ -574,18 +564,8 @@ public class ArchiveFactoryTests : TestBase
|
||||
[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)]
|
||||
@@ -676,6 +656,74 @@ public class ArchiveFactoryTests : TestBase
|
||||
Assert.Null(info);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public void IsArchive_ReturnsFalse_ForCompressedTar(string archiveName)
|
||||
{
|
||||
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
|
||||
|
||||
var isArchive = ArchiveFactory.IsArchive(stream, out var archiveType);
|
||||
|
||||
Assert.False(isArchive);
|
||||
Assert.Null(archiveType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public async ValueTask IsArchiveAsync_ReturnsFalse_ForCompressedTar(string archiveName)
|
||||
{
|
||||
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
|
||||
|
||||
var (isArchive, archiveType) = await ArchiveFactory.IsArchiveAsync(stream);
|
||||
|
||||
Assert.False(isArchive);
|
||||
Assert.Null(archiveType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public void GetArchiveInformation_ReturnsNull_ForCompressedTar(string archiveName)
|
||||
{
|
||||
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
|
||||
|
||||
var info = ArchiveFactory.GetArchiveInformation(stream);
|
||||
|
||||
Assert.Null(info);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public async ValueTask GetArchiveInformationAsync_ReturnsNull_ForCompressedTar(
|
||||
string archiveName
|
||||
)
|
||||
{
|
||||
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
|
||||
|
||||
var info = await ArchiveFactory.GetArchiveInformationAsync(stream);
|
||||
|
||||
Assert.Null(info);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Zip.deflate.zip", ArchiveType.Zip)]
|
||||
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar)]
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Test.Mocks;
|
||||
@@ -19,7 +20,6 @@ public class AsyncParityAndCancellationTests : TestBase
|
||||
[Theory]
|
||||
[InlineData("Zip.deflate.zip")]
|
||||
[InlineData("Tar.tar")]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Rar.rar")]
|
||||
[InlineData("7Zip.nonsolid.7z")]
|
||||
public async Task ArchiveAsyncEntries_ShouldMatchSyncEntries(string archiveName)
|
||||
@@ -74,6 +74,34 @@ public class AsyncParityAndCancellationTests : TestBase
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TarArchiveOpenAsyncArchive_ShouldRespectCancellationBeforeValidationAsync()
|
||||
{
|
||||
await using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"));
|
||||
using var cts = new CancellationTokenSource();
|
||||
await cts.CancelAsync();
|
||||
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
|
||||
await TarArchive.OpenAsyncArchive(stream, cancellationToken: cts.Token)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TarArchiveOpenAsyncArchive_ShouldRespectCancellationDuringValidationAsync()
|
||||
{
|
||||
var archiveBytes = CreateLargeTarArchive();
|
||||
using var cts = new CancellationTokenSource();
|
||||
await using var stream = new CancelAfterBytesReadStream(
|
||||
new MemoryStream(archiveBytes),
|
||||
cts,
|
||||
cancelAfterBytes: 128
|
||||
);
|
||||
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
|
||||
await TarArchive.OpenAsyncArchive(stream, cancellationToken: cts.Token)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncReaderExtraction_ShouldRespectCancellationDuringRead()
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AwesomeAssertions;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Providers;
|
||||
@@ -524,7 +522,7 @@ public class CompressionProviderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchive_OpenArchive_UsesCustomGZipProvider()
|
||||
public void TarReader_OpenReader_UsesCustomGZipProvider()
|
||||
{
|
||||
using var archiveStream = new MemoryStream();
|
||||
using (
|
||||
@@ -543,9 +541,9 @@ public class CompressionProviderTests
|
||||
var readOptions = ReaderOptions.ForExternalStream.WithProviders(registry);
|
||||
|
||||
archiveStream.Position = 0;
|
||||
using var archive = TarArchive.OpenArchive(archiveStream, readOptions);
|
||||
var entry = archive.Entries.First(x => !x.IsDirectory);
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
using var reader = TarReader.OpenReader(archiveStream, readOptions);
|
||||
reader.MoveToNextEntry().Should().BeTrue();
|
||||
using var entryStream = reader.OpenEntryStream();
|
||||
using var resultStream = new MemoryStream();
|
||||
entryStream.CopyTo(resultStream);
|
||||
|
||||
@@ -553,7 +551,7 @@ public class CompressionProviderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TarArchive_OpenAsyncArchive_UsesCustomGZipProvider()
|
||||
public async Task TarReader_OpenAsyncReader_UsesCustomGZipProvider()
|
||||
{
|
||||
using var archiveStream = new MemoryStream();
|
||||
using (
|
||||
@@ -572,23 +570,71 @@ public class CompressionProviderTests
|
||||
var readOptions = ReaderOptions.ForExternalStream.WithProviders(registry);
|
||||
|
||||
archiveStream.Position = 0;
|
||||
await using var archive = await TarArchive.OpenAsyncArchive(archiveStream, readOptions);
|
||||
await foreach (var entry in archive.EntriesAsync)
|
||||
{
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using var entryStream = await entry.OpenEntryStreamAsync();
|
||||
using var resultStream = new MemoryStream();
|
||||
await entryStream.CopyToAsync(resultStream);
|
||||
break;
|
||||
}
|
||||
await using var reader = await TarReader.OpenAsyncReader(archiveStream, readOptions);
|
||||
(await reader.MoveToNextEntryAsync()).Should().BeTrue();
|
||||
using var entryStream = await reader.OpenEntryStreamAsync();
|
||||
using var resultStream = new MemoryStream();
|
||||
await entryStream.CopyToAsync(resultStream);
|
||||
|
||||
trackingProvider.AsyncDecompressionCalls.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArchiveFactory_OpenArchive_UsesCustomProviderWhenRejectingCompressedTar()
|
||||
{
|
||||
using var archiveStream = CreateGZipTarStream();
|
||||
var trackingProvider = new TrackingCompressionProvider(new GZipCompressionProvider());
|
||||
var options = ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Default.With(trackingProvider)
|
||||
);
|
||||
|
||||
Action open = () => ArchiveFactory.OpenArchive(archiveStream, options);
|
||||
|
||||
open.Should().Throw<ArchiveOperationException>();
|
||||
trackingProvider.DecompressionCalls.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveFactory_OpenAsyncArchive_UsesCustomProviderWhenRejectingCompressedTarAsync()
|
||||
{
|
||||
using var archiveStream = CreateGZipTarStream();
|
||||
var trackingProvider = new TrackingCompressionProvider(new GZipCompressionProvider());
|
||||
var options = ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Default.With(trackingProvider)
|
||||
);
|
||||
|
||||
Func<Task> open = async () =>
|
||||
await ArchiveFactory.OpenAsyncArchive(
|
||||
archiveStream,
|
||||
options,
|
||||
TestContext.Current.CancellationToken
|
||||
);
|
||||
|
||||
await open.Should().ThrowAsync<ArchiveOperationException>();
|
||||
trackingProvider.AsyncDecompressionCalls.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
private static MemoryStream CreateGZipTarStream()
|
||||
{
|
||||
var archiveStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
archiveStream,
|
||||
new TarWriterOptions(CompressionType.GZip, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.Write(
|
||||
"test.txt",
|
||||
new MemoryStream(Encoding.UTF8.GetBytes("tar archive provider usage")),
|
||||
DateTime.Now
|
||||
);
|
||||
}
|
||||
|
||||
archiveStream.Position = 0;
|
||||
return archiveStream;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ZipReader_OpenEntryStreamAsync_UsesCustomDeflateProvider()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Test.Mocks;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Tar;
|
||||
@@ -35,6 +34,38 @@ public class TarArchiveAsyncTests : ArchiveTests
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public async ValueTask TarArchiveOpenAsyncArchive_RejectsCompressedTar(string archiveName)
|
||||
{
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
|
||||
|
||||
await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
await TarArchive.OpenAsyncArchive(stream)
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public async ValueTask ArchiveFactoryOpenAsyncArchive_RejectsCompressedTar(string archiveName)
|
||||
{
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
|
||||
|
||||
await Assert.ThrowsAsync<ArchiveOperationException>(async () =>
|
||||
await ArchiveFactory.OpenAsyncArchive(stream)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask TarArchiveOpenAsyncStream_Throws_On_Unreadable_Stream()
|
||||
{
|
||||
|
||||
@@ -486,14 +486,53 @@ public class TarArchiveTests : ArchiveTests
|
||||
Assert.False(isTar);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchiveStreamRead_Autodetect_CompressedTar()
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public void ArchiveFactoryStreamRead_Autodetect_RejectsCompressedTar(string archiveName)
|
||||
{
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
using var archive = ArchiveFactory.OpenArchive(stream);
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
|
||||
|
||||
Assert.Equal(ArchiveType.Tar, archive.Type);
|
||||
Assert.NotEmpty(archive.Entries);
|
||||
Assert.Throws<ArchiveOperationException>(() => ArchiveFactory.OpenArchive(stream));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz")]
|
||||
[InlineData("Tar.tar.bz2")]
|
||||
[InlineData("Tar.tar.lz")]
|
||||
[InlineData("Tar.tar.xz")]
|
||||
[InlineData("Tar.tar.zst")]
|
||||
[InlineData("Tar.tar.Z")]
|
||||
public void TarArchiveOpenArchive_RejectsCompressedTar(string archiveName)
|
||||
{
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
|
||||
|
||||
Assert.Throws<InvalidFormatException>(() => TarArchive.OpenArchive(stream));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, true)]
|
||||
public void TarArchiveOpenArchive_RejectionHonorsLeaveStreamOpen(
|
||||
bool leaveStreamOpen,
|
||||
bool expectedDisposed
|
||||
)
|
||||
{
|
||||
using var file = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
var stream = new TestStream(file);
|
||||
var options = ReaderOptions.ForExternalStream.WithLeaveStreamOpen(leaveStreamOpen);
|
||||
|
||||
Assert.Throws<InvalidFormatException>(() => TarArchive.OpenArchive(stream, options));
|
||||
|
||||
Assert.Equal(expectedDisposed, stream.IsDisposed);
|
||||
if (!stream.IsDisposed)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -46,6 +46,25 @@ public class TarReaderAsyncTests : ReaderTests
|
||||
public async ValueTask Tar_Z_Reader_Async() =>
|
||||
await ReadAsync("Tar.tar.Z", CompressionType.Lzw);
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tar.tar.gz", "gz", CompressionType.GZip)]
|
||||
[InlineData("Tar.tar.Z", "z", CompressionType.Lzw)]
|
||||
public async ValueTask ReaderFactory_ExtensionHint_PreservesCompressedTarDetection_Async(
|
||||
string archiveName,
|
||||
string extensionHint,
|
||||
CompressionType compressionType
|
||||
)
|
||||
{
|
||||
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
|
||||
var options = ReaderOptions.ForExternalStream.WithExtensionHint(extensionHint);
|
||||
|
||||
await using var reader = await ReaderFactory.OpenAsyncReader(stream, options);
|
||||
|
||||
Assert.Equal(ArchiveType.Tar, reader.Type);
|
||||
Assert.True(await reader.MoveToNextEntryAsync());
|
||||
Assert.Equal(compressionType, reader.Entry.CompressionType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async ValueTask Tar_Async_Assert() => await AssertArchiveAsync("Tar.tar");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user