First pass of removing Compressed Tar Archive support. Compressed Tars should be done with TarReader only

This commit is contained in:
Adam Hathcock
2026-06-23 15:42:04 +01:00
parent 0e20a10961
commit 760f7685f8
12 changed files with 151 additions and 266 deletions

View File

@@ -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`.
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

View File

@@ -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; use `ReaderFactory.OpenReader` or `TarReader.OpenReader` for those files.
### Creating Archives

View File

@@ -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`.
### Zip Format Notes

View File

@@ -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; 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,9 +152,9 @@ 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
@@ -418,7 +419,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

View File

@@ -85,7 +85,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 +93,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 +111,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
);

View File

@@ -53,12 +53,8 @@ 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);
EnsureRawTarFile(sourceStream);
return new TarArchive(sourceStream);
}
public static IWritableArchive<TarWriterOptions> OpenArchive(
@@ -72,12 +68,8 @@ 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);
EnsureRawTarFile(sourceStream);
return new TarArchive(sourceStream);
}
public static IWritableArchive<TarWriterOptions> OpenArchive(
@@ -104,11 +96,8 @@ public partial class TarArchive
i => null,
readerOptions ?? ReaderOptions.ForExternalStream
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
await EnsureRawTarFileAsync(sourceStream, cancellationToken).ConfigureAwait(false);
return new TarArchive(sourceStream);
}
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
@@ -132,11 +121,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)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
await EnsureRawTarFileAsync(sourceStream, cancellationToken).ConfigureAwait(false);
return new TarArchive(sourceStream);
}
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
@@ -152,11 +138,8 @@ public partial class TarArchive
i => i < strms.Count ? strms[i] : null,
readerOptions ?? ReaderOptions.ForExternalStream
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
await EnsureRawTarFileAsync(sourceStream, cancellationToken).ConfigureAwait(false);
return new TarArchive(sourceStream);
}
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
@@ -173,11 +156,8 @@ public partial class TarArchive
i => i < files.Count ? files[i] : null,
readerOptions ?? ReaderOptions.ForFilePath
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
await EnsureRawTarFileAsync(sourceStream, cancellationToken).ConfigureAwait(false);
return new TarArchive(sourceStream);
}
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
@@ -246,6 +226,29 @@ public partial class TarArchive
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> CreateAsyncArchive() =>
new(new TarArchive());
private static void EnsureRawTarFile(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
if (!IsTarFile(stream))
{
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))
{
throw new InvalidFormatException("Not a tar file.");
}
stream.Seek(0, SeekOrigin.Begin);
}
private static bool IsDefined(EntryType value)
{
#if LEGACY_DOTNET

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -407,18 +407,9 @@ 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("TarWithSymlink.tar.gz", ArchiveType.GZip, true)]
[InlineData("WinZip26.zip", ArchiveType.Zip, true)]
[InlineData("WinZip26_BZip2.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip26_LZMA.zipx", ArchiveType.Zip, true)]
@@ -574,18 +565,9 @@ 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("TarWithSymlink.tar.gz", ArchiveType.GZip, true)]
[InlineData("WinZip26.zip", ArchiveType.Zip, true)]
[InlineData("WinZip26_BZip2.zipx", ArchiveType.Zip, true)]
[InlineData("WinZip26_LZMA.zipx", ArchiveType.Zip, true)]

View File

@@ -5,7 +5,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AwesomeAssertions;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Compressors;
@@ -524,7 +523,7 @@ public class CompressionProviderTests
}
[Fact]
public void TarArchive_OpenArchive_UsesCustomGZipProvider()
public void TarReader_OpenReader_UsesCustomGZipProvider()
{
using var archiveStream = new MemoryStream();
using (
@@ -543,9 +542,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 +552,7 @@ public class CompressionProviderTests
}
[Fact]
public async Task TarArchive_OpenAsyncArchive_UsesCustomGZipProvider()
public async Task TarReader_OpenAsyncReader_UsesCustomGZipProvider()
{
using var archiveStream = new MemoryStream();
using (
@@ -572,19 +571,11 @@ 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);
}

View File

@@ -35,6 +35,16 @@ public class TarArchiveAsyncTests : ArchiveTests
);
}
[Fact]
public async ValueTask TarArchiveOpenAsyncArchive_RejectsCompressedTar()
{
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
await Assert.ThrowsAsync<InvalidFormatException>(async () =>
await TarArchive.OpenAsyncArchive(stream)
);
}
[Fact]
public async ValueTask TarArchiveOpenAsyncStream_Throws_On_Unreadable_Stream()
{

View File

@@ -487,13 +487,20 @@ public class TarArchiveTests : ArchiveTests
}
[Fact]
public void TarArchiveStreamRead_Autodetect_CompressedTar()
public void ArchiveFactoryStreamRead_Autodetect_CompressedTar_AsGZip()
{
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
using var archive = ArchiveFactory.OpenArchive(stream);
Assert.Equal(ArchiveType.Tar, archive.Type);
Assert.NotEmpty(archive.Entries);
Assert.Equal(ArchiveType.GZip, archive.Type);
}
[Fact]
public void TarArchiveOpenArchive_RejectsCompressedTar()
{
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
Assert.Throws<InvalidFormatException>(() => TarArchive.OpenArchive(stream));
}
[Fact]