intermediate commit

This commit is contained in:
Adam Hathcock
2026-06-24 16:27:57 +01:00
parent 760f7685f8
commit b8a7cc18d9
7 changed files with 84 additions and 17 deletions

View File

@@ -280,7 +280,7 @@ Archive API:
- `TarArchive.OpenArchive(Stream)` and `TarArchive.OpenAsyncArchive(Stream)` require seekable raw tar streams.
- File/path overloads own the opened file stream.
- Compressed tar wrappers are supported through `TarReader`, not `TarArchive`.
- 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:

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. Compressed tar wrappers such as `.tar.gz` and `.tar.xz` are also reader-only; use `ReaderFactory.OpenReader` or `TarReader.OpenReader` for those files.
`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

View File

@@ -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. Compressed tar wrappers are supported by `ReaderFactory`/`TarReader`, not by `ArchiveFactory`/`TarArchive`.
`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

View File

@@ -84,7 +84,7 @@ 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 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`.
- `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.

View File

@@ -409,7 +409,6 @@ public class ArchiveFactoryTests : TestBase
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Tar.tar", ArchiveType.Tar, true)]
[InlineData("TarCorrupted.tar", 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)]
@@ -567,7 +566,6 @@ public class ArchiveFactoryTests : TestBase
[InlineData("Tar.noEmptyDirs.tar", ArchiveType.Tar, true)]
[InlineData("Tar.tar", ArchiveType.Tar, true)]
[InlineData("TarCorrupted.tar", 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)]
@@ -658,6 +656,58 @@ public class ArchiveFactoryTests : TestBase
Assert.Null(info);
}
[Theory]
[InlineData("Tar.tar.gz")]
[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.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.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.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)]

View File

@@ -35,16 +35,30 @@ public class TarArchiveAsyncTests : ArchiveTests
);
}
[Fact]
public async ValueTask TarArchiveOpenAsyncArchive_RejectsCompressedTar()
[Theory]
[InlineData("Tar.tar.gz")]
[InlineData("Tar.tar.Z")]
public async ValueTask TarArchiveOpenAsyncArchive_RejectsCompressedTar(string archiveName)
{
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
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.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()
{

View File

@@ -486,19 +486,22 @@ public class TarArchiveTests : ArchiveTests
Assert.False(isTar);
}
[Fact]
public void ArchiveFactoryStreamRead_Autodetect_CompressedTar_AsGZip()
[Theory]
[InlineData("Tar.tar.gz")]
[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.GZip, archive.Type);
Assert.Throws<ArchiveOperationException>(() => ArchiveFactory.OpenArchive(stream));
}
[Fact]
public void TarArchiveOpenArchive_RejectsCompressedTar()
[Theory]
[InlineData("Tar.tar.gz")]
[InlineData("Tar.tar.Z")]
public void TarArchiveOpenArchive_RejectsCompressedTar(string archiveName)
{
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName));
Assert.Throws<InvalidFormatException>(() => TarArchive.OpenArchive(stream));
}