From 775a9bf144f16d1b83ad03f48a7b0c9cec7f8c4a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 09:11:57 +0000 Subject: [PATCH 01/22] update agents md --- AGENTS.md | 99 +++++++++++++++++++------------------------------------ 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d3f4b598..6ecf801d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,21 +6,25 @@ applyTo: '**/*.cs' # SharpCompress Development ## About SharpCompress -SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard) for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0, and .NET 8.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios. +SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard). The project currently targets .NET Framework 4.8, .NET Standard 2.0, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios. ## C# Instructions -- Always use the latest version C#, currently C# 13 features. -- Write clear and concise comments for each function. +- Use language features supported by the current project toolchain (`LangVersion=latest`) and existing codebase patterns. +- Add comments for non-obvious logic and important design decisions; avoid redundant comments. - Follow the existing code style and patterns in the codebase. ## General Instructions -- **Agents should NEVER commit to git** - Agents should stage files and leave committing to the user. Only create commits when the user explicitly requests them. +- **Do not commit or stage changes unless the user explicitly asks for it.** - Make only high confidence suggestions when reviewing code changes. - Write code with good maintainability practices, including comments on why certain design decisions were made. - Handle edge cases and write clear exception handling. - For libraries or external dependencies, mention their usage and purpose in comments. - Preserve backward compatibility when making changes to public APIs. +### Workspace Hygiene +- Do not edit generated or machine-local files unless required for the task (for example: `bin/`, `obj/`, `*.csproj.user`). +- Avoid broad formatting-only diffs in unrelated files. + ## Naming Conventions - Follow PascalCase for component names, method names, and public members. @@ -64,7 +68,7 @@ SharpCompress is a pure C# compression library supporting multiple archive forma ## Project Setup and Structure -- The project targets multiple frameworks: .NET Framework 4.62, .NET Standard 2.1, .NET 6.0, and .NET 8.0 +- The project targets multiple frameworks: .NET Framework 4.8, .NET Standard 2.0, .NET 8.0, and .NET 10.0 - Main library is in `src/SharpCompress/` - Tests are in `tests/SharpCompress.Test/` - Performance tests are in `tests/SharpCompress.Performance/` @@ -89,13 +93,18 @@ src/SharpCompress/ tests/SharpCompress.Test/ ├── Zip/, Tar/, Rar/, SevenZip/, GZip/, BZip2/ # Format-specific tests ├── TestBase.cs # Base test class with helper methods - └── TestArchives/ # Test data (not checked into main test project) + +tests/ + ├── SharpCompress.Test/ # Unit/integration tests + ├── SharpCompress.Performance/ # Benchmark tests + └── TestArchives/ # Test data archives ``` ### Factory Pattern -All format types implement factory interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) for auto-detection: -- `ReaderFactory.Open()` - Auto-detects format by probing stream -- `WriterFactory.Open()` - Creates writer for specified `ArchiveType` +Factory implementations can implement one or more interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) depending on format capabilities: +- `ArchiveFactory.OpenArchive()` - Opens archive API objects from seekable streams/files +- `ReaderFactory.OpenReader()` - Auto-detects and opens forward-only readers +- `WriterFactory.OpenWriter()` - Creates a writer for a specified `ArchiveType` - Factories located in: `src/SharpCompress/Factories/` ## Nullable Reference Types @@ -166,71 +175,31 @@ SharpCompress supports multiple archive and compression formats: - Test stream disposal and `LeaveStreamOpen` behavior. - Test edge cases: empty archives, large files, corrupted archives, encrypted archives. +### Validation Expectations +- Run targeted tests for the changed area first. +- Run `dotnet csharpier format .` after code edits. +- Run `dotnet csharpier check .` before handing off changes. + ### Test Organization - Base class: `TestBase` - Provides `TEST_ARCHIVES_PATH`, `SCRATCH_FILES_PATH`, temp directory management - Framework: xUnit with AwesomeAssertions - Test archives: `tests/TestArchives/` - Use existing archives, don't create new ones unnecessarily - Match naming style of nearby test files +### Public API Change Checklist +- Preserve existing public method signatures and behavior when possible. +- If a breaking change is unavoidable, document it and provide a migration path. +- Add or update tests that cover backward compatibility expectations. + +### Stream Ownership and Position Checklist +- Verify `LeaveStreamOpen` behavior for externally owned streams. +- Validate behavior for both seekable and non-seekable streams. +- Ensure stream position assumptions are explicit and tested. + ## Common Pitfalls 1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't 2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction 3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close) 4. **Tar + non-seekable stream** - Must provide file size or it will throw -6. **Format detection** - Use `ReaderFactory.Open()` for auto-detection, test with actual archive files - -### Async Struct-Copy Bug in LZMA RangeCoder - -When implementing async methods on mutable `struct` types (like `BitEncoder` and `BitDecoder` in the LZMA RangeCoder), be aware that the async state machine copies the struct when `await` is encountered. This means mutations to struct fields after the `await` point may not persist back to the original struct stored in arrays or fields. - -**The Bug:** -```csharp -// BAD: async method on mutable struct -public async ValueTask DecodeAsync(Decoder decoder, CancellationToken cancellationToken = default) -{ - var newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; - if (decoder._code < newBound) - { - decoder._range = newBound; - _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; // Mutates _prob - await decoder.Normalize2Async(cancellationToken).ConfigureAwait(false); // Struct gets copied here - return 0; // Original _prob update may be lost - } - // ... -} -``` - -**The Fix:** -Refactor async methods on mutable structs to perform all struct mutations synchronously before any `await`, or use a helper method to separate the await from the struct mutation: - -```csharp -// GOOD: struct mutations happen synchronously, await is conditional -public ValueTask DecodeAsync(Decoder decoder, CancellationToken cancellationToken = default) -{ - var newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; - if (decoder._code < newBound) - { - decoder._range = newBound; - _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; // All mutations complete - return DecodeAsyncHelper(decoder.Normalize2Async(cancellationToken), 0); // Await in helper - } - decoder._range -= newBound; - decoder._code -= newBound; - _prob -= (_prob) >> K_NUM_MOVE_BITS; // All mutations complete - return DecodeAsyncHelper(decoder.Normalize2Async(cancellationToken), 1); // Await in helper -} - -private static async ValueTask DecodeAsyncHelper(ValueTask normalizeTask, uint result) -{ - await normalizeTask.ConfigureAwait(false); - return result; -} -``` - -**Why This Matters:** -In LZMA, the `BitEncoder` and `BitDecoder` structs maintain adaptive probability models in their `_prob` field. When these structs are stored in arrays (e.g., `_models[m]`), the async state machine copy breaks the adaptive model, causing incorrect bit decoding and eventually `DataErrorException` exceptions. - -**Related Files:** -- `src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.Async.cs` - Fixed -- `src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.Async.cs` - Uses readonly structs, so this pattern doesn't apply +5. **Format detection** - Use `ReaderFactory.OpenReader()` for auto-detection, test with actual archive files From 4178c382e152fc5023a738afb165fcaa2180786a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 09:13:41 +0000 Subject: [PATCH 02/22] update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ecaa15e4..d28ed2df 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ RAR is not recommended as it's a proprietary format and the compression is close 7Zip and XZ both are overly complicated. 7Zip does not support streamable formats. XZ has known holes explained here: (http://www.nongnu.org/lzip/xz_inadequate.html) Use Tar/LZip for LZMA compression instead. -ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for. We currently only implement decompression for ZStandard but as we leverage the [ZstdSharp](https://github.com/oleg-st/ZstdSharp) library one could likely add compression support without much trouble (PRs are welcome!). +ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for. ## A Simple Request @@ -46,6 +46,8 @@ XZ BCJ filters support contributed by Louis-Michel Bergeron, on behalf of aDolus 7Zip implementation based on: https://code.google.com/p/managed-lzma/ +Zstandard implementation from: https://github.com/oleg-st/ZstdSharp + LICENSE Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) From d0baa165025dc8a99b6790878f31c3739ba4989f Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 12:14:05 +0000 Subject: [PATCH 03/22] Fix 7z seeking to be contigous in async too --- .../Archives/SevenZip/SevenZipArchive.cs | 10 +- .../SevenZip/SevenZipArchiveAsyncTests.cs | 93 +++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index a3c65a85..de0adc98 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -182,15 +182,15 @@ public partial class SevenZipArchive : AbstractArchive GetEntryStreamAsync( + CancellationToken cancellationToken = default + ) => new(GetEntryStream()); + public override void Dispose() { _currentFolderStream?.Dispose(); diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index d60c7a74..0cde6052 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using SharpCompress.Archives; +using SharpCompress.Archives.SevenZip; +using SharpCompress.Readers; using SharpCompress.Test.Mocks; using Xunit; @@ -224,4 +226,95 @@ public class SevenZipArchiveAsyncTests : ArchiveTests VerifyFiles(); } + + [Fact] + public async Task SevenZipArchive_Solid_ExtractAllEntries_Contiguous_Async() + { + // This test verifies that solid archives iterate entries as contiguous streams + // rather than recreating the decompression stream for each entry + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); + await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + Assert.True(((SevenZipArchive)archive).IsSolid); + + await using var reader = await archive.ExtractAllEntriesAsync(); + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); + } + } + + VerifyFiles(); + } + + [Fact] + public async Task SevenZipArchive_Solid_VerifyStreamReuse() + { + // This test verifies that the folder stream is reused within each folder + // and not recreated for each entry in solid archives + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); + await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + Assert.True(((SevenZipArchive)archive).IsSolid); + + await using var reader = await archive.ExtractAllEntriesAsync(); + + var sevenZipReader = Assert.IsType(reader); + sevenZipReader.DiagnosticsEnabled = true; + + Stream? currentFolderStreamInstance = null; + object? currentFolder = null; + var entryCount = 0; + var entriesInCurrentFolder = 0; + var streamRecreationsWithinFolder = 0; + + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + // Extract the entry to trigger GetEntryStream + using var entryStream = await reader.OpenEntryStreamAsync(); + var buffer = new byte[4096]; + while (entryStream.Read(buffer, 0, buffer.Length) > 0) + { + // Read the stream to completion + } + + entryCount++; + + var folderStream = sevenZipReader.DiagnosticsCurrentFolderStream; + var folder = sevenZipReader.DiagnosticsCurrentFolder; + + Assert.NotNull(folderStream); // Folder stream should exist + + // Check if we're in a new folder + if (currentFolder == null || !ReferenceEquals(currentFolder, folder)) + { + // Starting a new folder + currentFolder = folder; + currentFolderStreamInstance = folderStream; + entriesInCurrentFolder = 1; + } + else + { + // Same folder - verify stream wasn't recreated + entriesInCurrentFolder++; + + if (!ReferenceEquals(currentFolderStreamInstance, folderStream)) + { + // Stream was recreated within the same folder - this is the bug we're testing for! + streamRecreationsWithinFolder++; + } + + currentFolderStreamInstance = folderStream; + } + } + } + + // Verify we actually tested multiple entries + Assert.True(entryCount > 1, "Test should have multiple entries to verify stream reuse"); + + // The critical check: within a single folder, the stream should NEVER be recreated + Assert.Equal(0, streamRecreationsWithinFolder); // Folder stream should remain the same for all entries in the same folder + } } From 7cf7623438a8c9a8874fa3fa6b5d2739193d6502 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 13:36:26 +0000 Subject: [PATCH 04/22] update benchmarks to include async paths --- AGENTS.md | 15 +++-- .../Benchmarks/GZipBenchmarks.cs | 17 ++++++ .../Benchmarks/RarBenchmarks.cs | 27 ++++++++ .../Benchmarks/SevenZipBenchmarks.cs | 52 ++++++++++++++++ .../Benchmarks/TarBenchmarks.cs | 57 +++++++++++++++++ .../Benchmarks/ZipBenchmarks.cs | 45 ++++++++++++++ tests/SharpCompress.Performance/Program.cs | 8 +-- tests/SharpCompress.Performance/README.md | 10 +-- .../baseline-results.md | 61 ++++++++++++------- 9 files changed, 256 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6ecf801d..32bbb942 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,8 +103,11 @@ tests/ ### Factory Pattern Factory implementations can implement one or more interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) depending on format capabilities: - `ArchiveFactory.OpenArchive()` - Opens archive API objects from seekable streams/files +- `ArchiveFactory.OpenAsyncArchive()` - Opens async archive API objects for async archive use cases - `ReaderFactory.OpenReader()` - Auto-detects and opens forward-only readers +- `ReaderFactory.OpenAsyncReader()` - Auto-detects and opens forward-only async readers - `WriterFactory.OpenWriter()` - Creates a writer for a specified `ArchiveType` +- `WriterFactory.OpenAsyncWriter()` - Creates an async writer for async write scenarios - Factories located in: `src/SharpCompress/Factories/` ## Nullable Reference Types @@ -132,6 +135,9 @@ SharpCompress supports multiple archive and compression formats: ### Async/Await Patterns - All I/O operations support async/await with `CancellationToken` - Async methods follow the naming convention: `MethodNameAsync` +- For async archive scenarios, prefer `ArchiveFactory.OpenAsyncArchive(...)` over sync `OpenArchive(...)`. +- For async forward-only read scenarios, prefer `ReaderFactory.OpenAsyncReader(...)` over sync `OpenReader(...)`. +- For async write scenarios, prefer `WriterFactory.OpenAsyncWriter(...)` over sync `OpenWriter(...)`. - Key async methods: - `WriteEntryToAsync` - Extract entry asynchronously - `WriteAllToDirectoryAsync` - Extract all entries asynchronously @@ -199,7 +205,8 @@ SharpCompress supports multiple archive and compression formats: ## Common Pitfalls 1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't -2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction -3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close) -4. **Tar + non-seekable stream** - Must provide file size or it will throw -5. **Format detection** - Use `ReaderFactory.OpenReader()` for auto-detection, test with actual archive files +2. **Don't mix sync and async open paths** - For async workflows use `OpenAsyncArchive`/`OpenAsyncReader`/`OpenAsyncWriter`, not `OpenArchive`/`OpenReader`/`OpenWriter` +3. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction +4. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close) +5. **Tar + non-seekable stream** - Must provide file size or it will throw +6. **Format detection** - Use `ReaderFactory.OpenReader()` / `ReaderFactory.OpenAsyncReader()` for auto-detection, test with actual archive files diff --git a/tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs index 2167e034..e9804009 100644 --- a/tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; @@ -36,6 +37,14 @@ public class GZipBenchmarks gzipStream.Write(_sourceData, 0, _sourceData.Length); } + [Benchmark(Description = "GZip: Compress 100KB (Async)")] + public async Task GZipCompressAsync() + { + using var outputStream = new MemoryStream(); + using var gzipStream = new GZipStream(outputStream, CompressionMode.Compress); + await gzipStream.WriteAsync(_sourceData, 0, _sourceData.Length).ConfigureAwait(false); + } + [Benchmark(Description = "GZip: Decompress 100KB")] public void GZipDecompress() { @@ -43,4 +52,12 @@ public class GZipBenchmarks using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); gzipStream.CopyTo(Stream.Null); } + + [Benchmark(Description = "GZip: Decompress 100KB (Async)")] + public async Task GZipDecompressAsync() + { + using var inputStream = new MemoryStream(_compressedData); + using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); + await gzipStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } } diff --git a/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs index 7a2d6709..4667694b 100644 --- a/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SharpCompress.Archives.Rar; using SharpCompress.Readers; @@ -30,6 +31,18 @@ public class RarBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Rar: Extract all entries (Archive API, Async)")] + public async Task RarExtractArchiveApiAsync() + { + using var stream = new MemoryStream(_rarBytes); + await using var archive = RarArchive.OpenAsyncArchive(stream); + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) + { + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + [Benchmark(Description = "Rar: Extract all entries (Reader API)")] public void RarExtractReaderApi() { @@ -43,4 +56,18 @@ public class RarBenchmarks : ArchiveBenchmarkBase } } } + + [Benchmark(Description = "Rar: Extract all entries (Reader API, Async)")] + public async Task RarExtractReaderApiAsync() + { + using var stream = new MemoryStream(_rarBytes); + await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false); + while (await reader.MoveToNextEntryAsync().ConfigureAwait(false)) + { + if (!reader.Entry.IsDirectory) + { + await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false); + } + } + } } diff --git a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs index 74fdc584..981967d6 100644 --- a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SharpCompress.Archives.SevenZip; @@ -31,6 +32,18 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "7Zip LZMA: Extract all entries (Async)")] + public async Task SevenZipLzmaExtractAsync() + { + using var stream = new MemoryStream(_lzmaBytes); + using var archive = SevenZipArchive.OpenArchive(stream); + foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) + { + using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + [Benchmark(Description = "7Zip LZMA2: Extract all entries")] public void SevenZipLzma2Extract() { @@ -42,4 +55,43 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase entryStream.CopyTo(Stream.Null); } } + + [Benchmark(Description = "7Zip LZMA2: Extract all entries (Async)")] + public async Task SevenZipLzma2ExtractAsync() + { + using var stream = new MemoryStream(_lzma2Bytes); + await using var archive = SevenZipArchive.OpenAsyncArchive(stream); + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) + { + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + + + [Benchmark(Description = "7Zip LZMA2 Reader: Extract all entries")] + public void SevenZipLzma2Extract_Reader() + { + using var stream = new MemoryStream(_lzma2Bytes); + using var archive = SevenZipArchive.OpenArchive(stream); + using var reader = archive.ExtractAllEntries(); + foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) + { + using var entryStream = entry.OpenEntryStream(); + entryStream.CopyTo(Stream.Null); + } + } + + [Benchmark(Description = "7Zip LZMA2 Reader: Extract all entries (Async)")] + public async Task SevenZipLzma2ExtractAsync_Reader() + { + using var stream = new MemoryStream(_lzma2Bytes); + await using var archive = SevenZipArchive.OpenAsyncArchive(stream); + await using var reader = await archive.ExtractAllEntriesAsync(); + while(await reader.MoveToNextEntryAsync().ConfigureAwait(false)) + { + await using var entryStream = await reader.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } } diff --git a/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs index 39d1d97d..f7ad19e9 100644 --- a/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SharpCompress.Archives.Tar; using SharpCompress.Common; @@ -34,6 +35,18 @@ public class TarBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Tar: Extract all entries (Archive API, Async)")] + public async Task TarExtractArchiveApiAsync() + { + using var stream = new MemoryStream(_tarBytes); + await using var archive = TarArchive.OpenAsyncArchive(stream); + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) + { + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + [Benchmark(Description = "Tar: Extract all entries (Reader API)")] public void TarExtractReaderApi() { @@ -48,6 +61,20 @@ public class TarBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Tar: Extract all entries (Reader API, Async)")] + public async Task TarExtractReaderApiAsync() + { + using var stream = new MemoryStream(_tarBytes); + await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false); + while (await reader.MoveToNextEntryAsync().ConfigureAwait(false)) + { + if (!reader.Entry.IsDirectory) + { + await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false); + } + } + } + [Benchmark(Description = "Tar.GZip: Extract all entries")] public void TarGzipExtract() { @@ -60,6 +87,18 @@ public class TarBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Tar.GZip: Extract all entries (Async)")] + public async Task TarGzipExtractAsync() + { + using var stream = new MemoryStream(_tarGzBytes); + await using var archive = TarArchive.OpenAsyncArchive(stream); + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) + { + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + [Benchmark(Description = "Tar: Create archive with small files")] public void TarCreateSmallFiles() { @@ -78,4 +117,22 @@ public class TarBenchmarks : ArchiveBenchmarkBase writer.Write($"file{i}.txt", entryStream); } } + + [Benchmark(Description = "Tar: Create archive with small files (Async)")] + public async Task TarCreateSmallFilesAsync() + { + using var outputStream = new MemoryStream(); + await using var writer = WriterFactory.OpenAsyncWriter( + outputStream, + ArchiveType.Tar, + new WriterOptions(CompressionType.None) { LeaveStreamOpen = true } + ); + + for (int i = 0; i < 10; i++) + { + var data = new byte[1024]; + using var entryStream = new MemoryStream(data); + await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false); + } + } } diff --git a/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs index 00e58a0e..fb95d018 100644 --- a/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -34,6 +35,18 @@ public class ZipBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Zip: Extract all entries (Archive API, Async)")] + public async Task ZipExtractArchiveApiAsync() + { + using var stream = new MemoryStream(_archiveBytes); + await using var archive = ZipArchive.OpenAsyncArchive(stream); + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) + { + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); + } + } + [Benchmark(Description = "Zip: Extract all entries (Reader API)")] public void ZipExtractReaderApi() { @@ -48,6 +61,20 @@ public class ZipBenchmarks : ArchiveBenchmarkBase } } + [Benchmark(Description = "Zip: Extract all entries (Reader API, Async)")] + public async Task ZipExtractReaderApiAsync() + { + using var stream = new MemoryStream(_archiveBytes); + await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false); + while (await reader.MoveToNextEntryAsync().ConfigureAwait(false)) + { + if (!reader.Entry.IsDirectory) + { + await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false); + } + } + } + [Benchmark(Description = "Zip: Create archive with small files")] public void ZipCreateSmallFiles() { @@ -66,4 +93,22 @@ public class ZipBenchmarks : ArchiveBenchmarkBase writer.Write($"file{i}.txt", entryStream); } } + + [Benchmark(Description = "Zip: Create archive with small files (Async)")] + public async Task ZipCreateSmallFilesAsync() + { + using var outputStream = new MemoryStream(); + await using var writer = WriterFactory.OpenAsyncWriter( + outputStream, + ArchiveType.Zip, + new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = true } + ); + + for (int i = 0; i < 10; i++) + { + var data = new byte[1024]; + using var entryStream = new MemoryStream(data); + await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false); + } + } } diff --git a/tests/SharpCompress.Performance/Program.cs b/tests/SharpCompress.Performance/Program.cs index 75d4b16b..35e257b8 100644 --- a/tests/SharpCompress.Performance/Program.cs +++ b/tests/SharpCompress.Performance/Program.cs @@ -20,10 +20,10 @@ public class Program // Default: Run BenchmarkDotNet var config = DefaultConfig.Instance.AddJob( Job.Default.WithToolchain(InProcessEmitToolchain.Instance) - .WithWarmupCount(3) // Minimal warmup iterations for CI - .WithIterationCount(10) // Minimal measurement iterations for CI - .WithInvocationCount(10) - .WithUnrollFactor(1) + .WithWarmupCount(5) // Minimal warmup iterations for CI + .WithIterationCount(30) // Minimal measurement iterations for CI + .WithInvocationCount(30) + .WithUnrollFactor(2) ); BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); diff --git a/tests/SharpCompress.Performance/README.md b/tests/SharpCompress.Performance/README.md index 91ebfb33..43e9df9e 100644 --- a/tests/SharpCompress.Performance/README.md +++ b/tests/SharpCompress.Performance/README.md @@ -5,11 +5,11 @@ This project contains performance benchmarks for SharpCompress using [BenchmarkD ## Overview The benchmarks test all major archive formats supported by SharpCompress: -- **Zip**: Read (Archive & Reader API) and Write operations -- **Tar**: Read (Archive & Reader API) and Write operations, including Tar.GZip -- **Rar**: Read operations (Archive & Reader API) -- **7Zip**: Read operations for LZMA and LZMA2 compression -- **GZip**: Compression and decompression +- **Zip**: Read (Archive & Reader API) and Write operations, each with sync and async variants +- **Tar**: Read (Archive & Reader API) and Write operations, including Tar.GZip, each with sync and async variants +- **Rar**: Read operations (Archive & Reader API), each with sync and async variants +- **7Zip**: Read operations for LZMA and LZMA2 compression, each with sync and async variants +- **GZip**: Compression and decompression, each with sync and async variants ## Running Benchmarks diff --git a/tests/SharpCompress.Performance/baseline-results.md b/tests/SharpCompress.Performance/baseline-results.md index 479a7deb..2b25a9c1 100644 --- a/tests/SharpCompress.Performance/baseline-results.md +++ b/tests/SharpCompress.Performance/baseline-results.md @@ -1,23 +1,38 @@ -| Method | Mean | Error | StdDev | Allocated | -|------------------------- |-----------:|---------:|---------:|----------:| -| 'GZip: Compress 100KB' | 3,268.7 μs | 28.50 μs | 16.96 μs | 519.2 KB | -| 'GZip: Decompress 100KB' | 436.6 μs | 3.23 μs | 1.69 μs | 34.18 KB | -| Method | Mean | Error | StdDev | Allocated | -|----------------------------------------- |---------:|----------:|----------:|----------:| -| 'Rar: Extract all entries (Archive API)' | 2.054 ms | 0.3927 ms | 0.2598 ms | 91.09 KB | -| 'Rar: Extract all entries (Reader API)' | 2.235 ms | 0.0253 ms | 0.0132 ms | 149.48 KB | -| Method | Mean | Error | StdDev | Allocated | -|---------------------------------- |---------:|----------:|----------:|----------:| -| '7Zip LZMA: Extract all entries' | 9.124 ms | 2.1930 ms | 1.4505 ms | 272.8 KB | -| '7Zip LZMA2: Extract all entries' | 7.810 ms | 0.1323 ms | 0.0788 ms | 272.58 KB | -| Method | Mean | Error | StdDev | Allocated | -|----------------------------------------- |----------:|---------:|---------:|----------:| -| 'Tar: Extract all entries (Archive API)' | 56.36 μs | 3.312 μs | 1.971 μs | 16.65 KB | -| 'Tar: Extract all entries (Reader API)' | 175.34 μs | 2.616 μs | 1.557 μs | 213.36 KB | -| 'Tar.GZip: Extract all entries' | NA | NA | NA | NA | -| 'Tar: Create archive with small files' | 51.38 μs | 2.349 μs | 1.398 μs | 68.7 KB | -| Method | Mean | Error | StdDev | Gen0 | Allocated | -|----------------------------------------- |-----------:|---------:|---------:|---------:|-----------:| -| 'Zip: Extract all entries (Archive API)' | 1,188.4 μs | 28.62 μs | 14.97 μs | - | 181.66 KB | -| 'Zip: Extract all entries (Reader API)' | 1,137.0 μs | 5.58 μs | 2.92 μs | - | 123.19 KB | -| 'Zip: Create archive with small files' | 258.2 μs | 8.98 μs | 4.70 μs | 100.0000 | 2806.93 KB | \ No newline at end of file +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|--------------------------------- |-----------:|----------:|----------:|--------:|--------:|--------:|----------:| +| 'GZip: Compress 100KB' | 3,903.0 μs | 299.60 μs | 448.43 μs | 33.3333 | 33.3333 | 33.3333 | 519.29 KB | +| 'GZip: Compress 100KB (Async)' | 3,792.5 μs | 224.39 μs | 335.86 μs | 33.3333 | 33.3333 | 33.3333 | 519.33 KB | +| 'GZip: Decompress 100KB' | 204.0 μs | 11.96 μs | 17.15 μs | - | - | - | 33.89 KB | +| 'GZip: Decompress 100KB (Async)' | 222.2 μs | 11.88 μs | 17.42 μs | - | - | - | 34.17 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------------------------------------------ |-----------:|---------:|---------:|---------:|---------:|---------:|-----------:| +| 'Rar: Extract all entries (Archive API)' | 947.7 μs | 75.69 μs | 98.42 μs | - | - | - | 90.59 KB | +| 'Rar: Extract all entries (Archive API, Async)' | 1,030.6 μs | 43.69 μs | 64.04 μs | - | - | - | 95.72 KB | +| 'Rar: Extract all entries (Reader API)' | 1,181.5 μs | 43.18 μs | 61.92 μs | - | - | - | 148.75 KB | +| 'Rar: Extract all entries (Reader API, Async)' | 1,349.2 μs | 45.16 μs | 67.59 μs | 500.0000 | 500.0000 | 500.0000 | 4775.16 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------------------------------------------- |----------:|----------:|----------:|---------:|---------:|---------:|-----------:| +| '7Zip LZMA: Extract all entries' | 7.694 ms | 0.2467 ms | 0.3616 ms | 33.3333 | 33.3333 | 33.3333 | 272.68 KB | +| '7Zip LZMA: Extract all entries (Async)' | 31.835 ms | 1.7927 ms | 2.6277 ms | 266.6667 | 66.6667 | 33.3333 | 3402.8 KB | +| '7Zip LZMA2: Extract all entries' | 8.686 ms | 0.4837 ms | 0.7090 ms | 33.3333 | 33.3333 | 33.3333 | 272.42 KB | +| '7Zip LZMA2: Extract all entries (Async)' | 27.521 ms | 1.4124 ms | 2.1140 ms | 266.6667 | 66.6667 | 33.3333 | 3409.68 KB | +| '7Zip LZMA2 Reader: Extract all entries' | 8.047 ms | 0.3851 ms | 0.5399 ms | 33.3333 | 33.3333 | 33.3333 | 273.03 KB | +| '7Zip LZMA2 Reader: Extract all entries (Async)' | 16.332 ms | 0.5697 ms | 0.8351 ms | 200.0000 | 100.0000 | 100.0000 | 2420.81 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------------------------------------------ |----------:|----------:|----------:|--------:|--------:|--------:|----------:| +| 'Tar: Extract all entries (Archive API)' | 25.41 μs | 1.016 μs | 1.424 μs | - | - | - | 16.32 KB | +| 'Tar: Extract all entries (Archive API, Async)' | 66.86 μs | 6.858 μs | 10.265 μs | - | - | - | 14.56 KB | +| 'Tar: Extract all entries (Reader API)' | 180.28 μs | 27.631 μs | 40.500 μs | 66.6667 | 66.6667 | 66.6667 | 341.23 KB | +| 'Tar: Extract all entries (Reader API, Async)' | 273.87 μs | 53.148 μs | 79.549 μs | 66.6667 | 66.6667 | 66.6667 | 376.08 KB | +| 'Tar.GZip: Extract all entries' | NA | NA | NA | NA | NA | NA | NA | +| 'Tar.GZip: Extract all entries (Async)' | NA | NA | NA | NA | NA | NA | NA | +| 'Tar: Create archive with small files' | 31.85 μs | 2.580 μs | 3.354 μs | - | - | - | 68.11 KB | +| 'Tar: Create archive with small files (Async)' | 32.85 μs | 1.716 μs | 2.516 μs | - | - | - | 68.07 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|------------------------------------------------ |---------:|---------:|---------:|---------:|--------:|-----------:| +| 'Zip: Extract all entries (Archive API)' | 715.3 μs | 44.24 μs | 64.84 μs | - | - | 180.21 KB | +| 'Zip: Extract all entries (Archive API, Async)' | 559.5 μs | 23.80 μs | 33.36 μs | - | - | 125.47 KB | +| 'Zip: Extract all entries (Reader API)' | 631.8 μs | 60.82 μs | 89.14 μs | - | - | 121.06 KB | +| 'Zip: Extract all entries (Reader API, Async)' | 658.1 μs | 48.01 μs | 71.86 μs | - | - | 123.34 KB | +| 'Zip: Create archive with small files' | 225.4 μs | 12.51 μs | 17.94 μs | 200.0000 | 66.6667 | 2806.25 KB | +| 'Zip: Create archive with small files (Async)' | 310.4 μs | 13.46 μs | 20.14 μs | 200.0000 | 66.6667 | 2811.4 KB | \ No newline at end of file From fbec7dc083d238036ef4a81d022eed0bb14fbea7 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 13:57:06 +0000 Subject: [PATCH 05/22] generate github actions baseline --- .../baseline-results.md | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/tests/SharpCompress.Performance/baseline-results.md b/tests/SharpCompress.Performance/baseline-results.md index 2b25a9c1..6d9874a1 100644 --- a/tests/SharpCompress.Performance/baseline-results.md +++ b/tests/SharpCompress.Performance/baseline-results.md @@ -1,38 +1,49 @@ -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|--------------------------------- |-----------:|----------:|----------:|--------:|--------:|--------:|----------:| -| 'GZip: Compress 100KB' | 3,903.0 μs | 299.60 μs | 448.43 μs | 33.3333 | 33.3333 | 33.3333 | 519.29 KB | -| 'GZip: Compress 100KB (Async)' | 3,792.5 μs | 224.39 μs | 335.86 μs | 33.3333 | 33.3333 | 33.3333 | 519.33 KB | -| 'GZip: Decompress 100KB' | 204.0 μs | 11.96 μs | 17.15 μs | - | - | - | 33.89 KB | -| 'GZip: Decompress 100KB (Async)' | 222.2 μs | 11.88 μs | 17.42 μs | - | - | - | 34.17 KB | -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------------------------------------------ |-----------:|---------:|---------:|---------:|---------:|---------:|-----------:| -| 'Rar: Extract all entries (Archive API)' | 947.7 μs | 75.69 μs | 98.42 μs | - | - | - | 90.59 KB | -| 'Rar: Extract all entries (Archive API, Async)' | 1,030.6 μs | 43.69 μs | 64.04 μs | - | - | - | 95.72 KB | -| 'Rar: Extract all entries (Reader API)' | 1,181.5 μs | 43.18 μs | 61.92 μs | - | - | - | 148.75 KB | -| 'Rar: Extract all entries (Reader API, Async)' | 1,349.2 μs | 45.16 μs | 67.59 μs | 500.0000 | 500.0000 | 500.0000 | 4775.16 KB | +| Method | Mean | Error | StdDev | Allocated | +|---------------------------- |---------:|---------:|---------:|----------:| +| SharpCompress_0_44_Original | 581.8 ms | 11.56 ms | 17.65 ms | 48.77 MB | +| Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------- |-----------:|----------:|----------:|-----------:|---------:|---------:|---------:|----------:| +| ZipArchiveRead | 959.2 μs | 52.16 μs | 153.78 μs | 928.7 μs | 27.3438 | 5.8594 | - | 345.75 KB | +| TarArchiveRead | 252.1 μs | 20.97 μs | 61.82 μs | 251.9 μs | 12.2070 | 5.8594 | - | 154.78 KB | +| TarGzArchiveRead | 600.9 μs | 19.25 μs | 53.98 μs | 607.8 μs | 16.6016 | 6.8359 | - | 204.95 KB | +| TarBz2ArchiveRead | NA | NA | NA | NA | NA | NA | NA | NA | +| SevenZipArchiveRead | 8,354.4 μs | 273.01 μs | 747.35 μs | 8,093.2 μs | 109.3750 | 109.3750 | 109.3750 | 787.99 KB | +| RarArchiveRead | 1,648.6 μs | 131.91 μs | 388.94 μs | 1,617.6 μs | 17.5781 | 5.8594 | - | 222.62 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|--------------------------------- |-----------:|--------:|---------:|--------:|--------:|--------:|----------:| +| 'GZip: Compress 100KB' | 3,317.1 μs | 7.15 μs | 10.02 μs | 33.3333 | 33.3333 | 33.3333 | 519.31 KB | +| 'GZip: Compress 100KB (Async)' | 3,280.3 μs | 8.30 μs | 11.63 μs | 33.3333 | 33.3333 | 33.3333 | 519.46 KB | +| 'GZip: Decompress 100KB' | 432.5 μs | 2.43 μs | 3.56 μs | - | - | - | 33.92 KB | +| 'GZip: Decompress 100KB (Async)' | 442.8 μs | 1.20 μs | 1.76 μs | - | - | - | 34.24 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------------------------------------------ |-----------:|----------:|----------:|---------:|---------:|---------:|-----------:| +| 'Rar: Extract all entries (Archive API)' | 908.2 μs | 12.42 μs | 17.01 μs | - | - | - | 90.68 KB | +| 'Rar: Extract all entries (Archive API, Async)' | 1,175.4 μs | 118.74 μs | 177.72 μs | - | - | - | 96.09 KB | +| 'Rar: Extract all entries (Reader API)' | 1,215.1 μs | 2.26 μs | 3.09 μs | - | - | - | 148.85 KB | +| 'Rar: Extract all entries (Reader API, Async)' | 1,592.0 μs | 22.58 μs | 33.10 μs | 500.0000 | 500.0000 | 500.0000 | 4776.76 KB | | Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |------------------------------------------------- |----------:|----------:|----------:|---------:|---------:|---------:|-----------:| -| '7Zip LZMA: Extract all entries' | 7.694 ms | 0.2467 ms | 0.3616 ms | 33.3333 | 33.3333 | 33.3333 | 272.68 KB | -| '7Zip LZMA: Extract all entries (Async)' | 31.835 ms | 1.7927 ms | 2.6277 ms | 266.6667 | 66.6667 | 33.3333 | 3402.8 KB | -| '7Zip LZMA2: Extract all entries' | 8.686 ms | 0.4837 ms | 0.7090 ms | 33.3333 | 33.3333 | 33.3333 | 272.42 KB | -| '7Zip LZMA2: Extract all entries (Async)' | 27.521 ms | 1.4124 ms | 2.1140 ms | 266.6667 | 66.6667 | 33.3333 | 3409.68 KB | -| '7Zip LZMA2 Reader: Extract all entries' | 8.047 ms | 0.3851 ms | 0.5399 ms | 33.3333 | 33.3333 | 33.3333 | 273.03 KB | -| '7Zip LZMA2 Reader: Extract all entries (Async)' | 16.332 ms | 0.5697 ms | 0.8351 ms | 200.0000 | 100.0000 | 100.0000 | 2420.81 KB | -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------------------------------------------ |----------:|----------:|----------:|--------:|--------:|--------:|----------:| -| 'Tar: Extract all entries (Archive API)' | 25.41 μs | 1.016 μs | 1.424 μs | - | - | - | 16.32 KB | -| 'Tar: Extract all entries (Archive API, Async)' | 66.86 μs | 6.858 μs | 10.265 μs | - | - | - | 14.56 KB | -| 'Tar: Extract all entries (Reader API)' | 180.28 μs | 27.631 μs | 40.500 μs | 66.6667 | 66.6667 | 66.6667 | 341.23 KB | -| 'Tar: Extract all entries (Reader API, Async)' | 273.87 μs | 53.148 μs | 79.549 μs | 66.6667 | 66.6667 | 66.6667 | 376.08 KB | -| 'Tar.GZip: Extract all entries' | NA | NA | NA | NA | NA | NA | NA | -| 'Tar.GZip: Extract all entries (Async)' | NA | NA | NA | NA | NA | NA | NA | -| 'Tar: Create archive with small files' | 31.85 μs | 2.580 μs | 3.354 μs | - | - | - | 68.11 KB | -| 'Tar: Create archive with small files (Async)' | 32.85 μs | 1.716 μs | 2.516 μs | - | - | - | 68.07 KB | +| '7Zip LZMA: Extract all entries' | 7.723 ms | 0.0111 ms | 0.0152 ms | 33.3333 | 33.3333 | 33.3333 | 272.68 KB | +| '7Zip LZMA: Extract all entries (Async)' | 35.827 ms | 0.0381 ms | 0.0546 ms | 200.0000 | 33.3333 | 33.3333 | 3402.82 KB | +| '7Zip LZMA2: Extract all entries' | 7.758 ms | 0.0074 ms | 0.0104 ms | 33.3333 | 33.3333 | 33.3333 | 272.46 KB | +| '7Zip LZMA2: Extract all entries (Async)' | 36.317 ms | 0.0345 ms | 0.0506 ms | 200.0000 | 33.3333 | 33.3333 | 3409.72 KB | +| '7Zip LZMA2 Reader: Extract all entries' | 7.706 ms | 0.0114 ms | 0.0163 ms | 33.3333 | 33.3333 | 33.3333 | 273.03 KB | +| '7Zip LZMA2 Reader: Extract all entries (Async)' | 22.951 ms | 0.0973 ms | 0.1426 ms | 100.0000 | 100.0000 | 100.0000 | 2420.81 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------------------------------------------ |----------:|---------:|---------:|--------:|--------:|--------:|----------:| +| 'Tar: Extract all entries (Archive API)' | 40.82 μs | 0.292 μs | 0.427 μs | - | - | - | 16.36 KB | +| 'Tar: Extract all entries (Archive API, Async)' | 105.12 μs | 6.183 μs | 9.254 μs | - | - | - | 14.57 KB | +| 'Tar: Extract all entries (Reader API)' | 187.89 μs | 1.571 μs | 2.254 μs | 66.6667 | 66.6667 | 66.6667 | 341.24 KB | +| 'Tar: Extract all entries (Reader API, Async)' | 229.78 μs | 4.852 μs | 6.802 μs | 66.6667 | 66.6667 | 66.6667 | 376.64 KB | +| 'Tar.GZip: Extract all entries' | NA | NA | NA | NA | NA | NA | NA | +| 'Tar.GZip: Extract all entries (Async)' | NA | NA | NA | NA | NA | NA | NA | +| 'Tar: Create archive with small files' | 46.98 μs | 0.287 μs | 0.394 μs | - | - | - | 68.11 KB | +| 'Tar: Create archive with small files (Async)' | 53.14 μs | 0.352 μs | 0.493 μs | - | - | - | 68.11 KB | | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------------------------------------ |---------:|---------:|---------:|---------:|--------:|-----------:| -| 'Zip: Extract all entries (Archive API)' | 715.3 μs | 44.24 μs | 64.84 μs | - | - | 180.21 KB | -| 'Zip: Extract all entries (Archive API, Async)' | 559.5 μs | 23.80 μs | 33.36 μs | - | - | 125.47 KB | -| 'Zip: Extract all entries (Reader API)' | 631.8 μs | 60.82 μs | 89.14 μs | - | - | 121.06 KB | -| 'Zip: Extract all entries (Reader API, Async)' | 658.1 μs | 48.01 μs | 71.86 μs | - | - | 123.34 KB | -| 'Zip: Create archive with small files' | 225.4 μs | 12.51 μs | 17.94 μs | 200.0000 | 66.6667 | 2806.25 KB | -| 'Zip: Create archive with small files (Async)' | 310.4 μs | 13.46 μs | 20.14 μs | 200.0000 | 66.6667 | 2811.4 KB | \ No newline at end of file +| 'Zip: Extract all entries (Archive API)' | 556.7 μs | 3.38 μs | 4.74 μs | - | - | 180.22 KB | +| 'Zip: Extract all entries (Archive API, Async)' | 615.7 μs | 15.98 μs | 22.92 μs | - | - | 125.52 KB | +| 'Zip: Extract all entries (Reader API)' | 542.2 μs | 1.10 μs | 1.46 μs | - | - | 121.04 KB | +| 'Zip: Extract all entries (Reader API, Async)' | 562.8 μs | 2.42 μs | 3.55 μs | - | - | 123.34 KB | +| 'Zip: Create archive with small files' | 271.1 μs | 12.93 μs | 18.95 μs | 166.6667 | 33.3333 | 2806.28 KB | +| 'Zip: Create archive with small files (Async)' | 394.3 μs | 25.59 μs | 36.71 μs | 166.6667 | 33.3333 | 2811.42 KB | \ No newline at end of file From 3689b893dba6b009dae24917b6e22f72e6cd4c4a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 14:00:37 +0000 Subject: [PATCH 06/22] Update tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs index 981967d6..80bda877 100644 --- a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs @@ -36,7 +36,7 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase public async Task SevenZipLzmaExtractAsync() { using var stream = new MemoryStream(_lzmaBytes); - using var archive = SevenZipArchive.OpenArchive(stream); + await using var archive = SevenZipArchive.OpenAsyncArchive(stream); foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); From 8aa93f4e34a63e5fce3414c5ec4845478b995af9 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 14:02:27 +0000 Subject: [PATCH 07/22] fix fmt --- .../SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs index 80bda877..2868e0ae 100644 --- a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs @@ -68,7 +68,6 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase } } - [Benchmark(Description = "7Zip LZMA2 Reader: Extract all entries")] public void SevenZipLzma2Extract_Reader() { @@ -88,7 +87,7 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase using var stream = new MemoryStream(_lzma2Bytes); await using var archive = SevenZipArchive.OpenAsyncArchive(stream); await using var reader = await archive.ExtractAllEntriesAsync(); - while(await reader.MoveToNextEntryAsync().ConfigureAwait(false)) + while (await reader.MoveToNextEntryAsync().ConfigureAwait(false)) { await using var entryStream = await reader.OpenEntryStreamAsync().ConfigureAwait(false); await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); From 98d0f1913e09e324db3dc015ad821765cf48ced6 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 14:19:21 +0000 Subject: [PATCH 08/22] make sure things compile adam --- .../Benchmarks/SevenZipBenchmarks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs index 2868e0ae..8a249b00 100644 --- a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs @@ -37,9 +37,9 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase { using var stream = new MemoryStream(_lzmaBytes); await using var archive = SevenZipArchive.OpenAsyncArchive(stream); - foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { - using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); + await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false); } } From 103ae6063141c8c6c34a203395afe5b4bdc2c2b6 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:10:55 +0000 Subject: [PATCH 09/22] codex found problems --- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 295 ++++++++++++++++-- 1 file changed, 275 insertions(+), 20 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index af389904..1812c004 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -231,9 +231,12 @@ internal partial class Unpack return; } + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. if ( - !ReadBlockHeader(Inp, ref BlockHeader) - || !ReadTables(Inp, ref BlockHeader, ref BlockTables) + !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) + || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) || !TablesRead5 ) { @@ -249,6 +252,8 @@ internal partial class Unpack { var FileDone = false; + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. while ( Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 @@ -261,8 +266,8 @@ internal partial class Unpack break; } if ( - !ReadBlockHeader(Inp, ref BlockHeader) - || !ReadTables(Inp, ref BlockHeader, ref BlockTables) + !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) + || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) ) { return; @@ -276,14 +281,20 @@ internal partial class Unpack if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) { - await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); + UnpWriteBuf(); if (WrittenFileSize > DestUnpSize) { return; } + + if (Suspended) + { + FileExtracted = false; + return; + } } - uint MainSlot = DecodeNumber(Inp, BlockTables.LD); + var MainSlot = DecodeNumber(Inp, BlockTables.LD); if (MainSlot < 256) { if (Fragmented) @@ -298,7 +309,7 @@ internal partial class Unpack } if (MainSlot >= 262) { - uint Length = SlotToLength(Inp, MainSlot - 262); + var Length = SlotToLength(Inp, MainSlot - 262); uint DBits, Distance = 1, @@ -320,16 +331,16 @@ internal partial class Unpack { if (DBits > 4) { - Distance += ((Inp.getbits() >> (int)(20 - DBits)) << 4); + Distance += ((Inp.getbits32() >> (int)(36 - DBits)) << 4); Inp.addbits(DBits - 4); } - uint LowDist = DecodeNumber(Inp, BlockTables.LDD); + var LowDist = DecodeNumber(Inp, BlockTables.LDD); Distance += LowDist; } else { - Distance += Inp.getbits() >> (int)(16 - DBits); + Distance += Inp.getbits32() >> (int)(32 - DBits); Inp.addbits(DBits); } } @@ -349,13 +360,23 @@ internal partial class Unpack InsertOldDist(Distance); LastLength = Length; - CopyString(Length, Distance); + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } continue; } if (MainSlot == 256) { var Filter = new UnpackFilter(); - if (!ReadFilter(Inp, Filter) || !AddFilter(Filter)) + if ( + !await ReadFilterAsync(Inp, Filter, cancellationToken).ConfigureAwait(false) + || !AddFilter(Filter) + ) { break; } @@ -365,29 +386,44 @@ internal partial class Unpack { if (LastLength != 0) { - CopyString((uint)LastLength, (uint)OldDist[0]); + if (Fragmented) + { + FragWindow.CopyString(LastLength, OldDist[0], ref UnpPtr, MaxWinMask); + } + else + { + CopyString(LastLength, OldDist[0]); + } } continue; } if (MainSlot < 262) { - uint DistNum = MainSlot - 258; - uint Distance = (uint)OldDist[(int)DistNum]; - for (var I = (int)DistNum; I > 0; I--) + var DistNum = MainSlot - 258; + var Distance = OldDist[DistNum]; + for (var I = DistNum; I > 0; I--) { OldDist[I] = OldDist[I - 1]; } + OldDist[0] = Distance; - uint LengthSlot = DecodeNumber(Inp, BlockTables.RD); - uint Length = SlotToLength(Inp, LengthSlot); + var LengthSlot = DecodeNumber(Inp, BlockTables.RD); + var Length = SlotToLength(Inp, LengthSlot); LastLength = Length; - CopyString(Length, Distance); + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } continue; } } - await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); + UnpWriteBuf(); } private uint ReadFilterData(BitInput Inp) @@ -433,6 +469,39 @@ internal partial class Unpack return true; } + private async System.Threading.Tasks.Task ReadFilterAsync( + BitInput Inp, + UnpackFilter Filter, + System.Threading.CancellationToken cancellationToken = default + ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Filter.BlockStart = ReadFilterData(Inp); + Filter.BlockLength = ReadFilterData(Inp); + if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) + { + Filter.BlockLength = 0; + } + + Filter.Type = (byte)(Inp.fgetbits() >> 13); + Inp.faddbits(3); + + if (Filter.Type == FILTER_DELTA) + { + Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); + Inp.faddbits(5); + } + + return true; + } + private bool AddFilter(UnpackFilter Filter) { if (Filters.Count >= MAX_UNPACK_FILTERS) @@ -1184,6 +1253,61 @@ internal partial class Unpack return true; } + private async System.Threading.Tasks.Task ReadBlockHeaderAsync( + BitInput Inp, + System.Threading.CancellationToken cancellationToken = default + ) + { + BlockHeader.HeaderSize = 0; + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Inp.faddbits((uint)((8 - Inp.InBit) & 7)); + + var BlockFlags = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. + + if (ByteCount == 4) + { + return false; + } + + BlockHeader.HeaderSize = (int)(2 + ByteCount); + + BlockHeader.BlockBitSize = (BlockFlags & 7) + 1; + + var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + + var BlockSize = 0; + for (uint I = 0; I < ByteCount; I++) + { + BlockSize += (int)((Inp.fgetbits() >> 8) << (int)(I * 8)); + Inp.addbits(8); + } + + BlockHeader.BlockSize = BlockSize; + var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); + if (CheckSum != SavedCheckSum) + { + return false; + } + + BlockHeader.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + + BlockHeader.LastBlockInFile = (BlockFlags & 0x40) != 0; + BlockHeader.TablePresent = (BlockFlags & 0x80) != 0; + return true; + } + private bool ReadTables( BitInput Inp, ref UnpackBlockHeader Header, @@ -1316,6 +1440,137 @@ internal partial class Unpack return true; } + private async System.Threading.Tasks.Task ReadTablesAsync( + BitInput Inp, + System.Threading.CancellationToken cancellationToken = default + ) + { + if (!BlockHeader.TablePresent) + { + return true; + } + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var BitLength = new byte[checked((int)BC)]; + for (int I = 0; I < BC; I++) + { + uint Length = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (Length == 15) + { + uint ZeroCount = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (ZeroCount == 0) + { + BitLength[I] = 15; + } + else + { + ZeroCount += 2; + while (ZeroCount-- > 0 && I < BitLength.Length) + { + BitLength[I++] = 0; + } + + I--; + } + } + else + { + BitLength[I] = (byte)Length; + } + } + + MakeDecodeTables(BitLength, 0, BlockTables.BD, BC); + + var Table = new byte[checked((int)HUFF_TABLE_SIZE)]; + const int TableSize = checked((int)HUFF_TABLE_SIZE); + for (int I = 0; I < TableSize; ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var Number = DecodeNumber(Inp, BlockTables.BD); + if (Number < 16) + { + Table[I] = (byte)Number; + I++; + } + else if (Number < 18) + { + uint N; + if (Number == 16) + { + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + if (I == 0) + { + // We cannot have "repeat previous" code at the first position. + // Multiple such codes would shift Inp position without changing I, + // which can lead to reading beyond of Inp boundary in mutithreading + // mode, where Inp.ExternalBuffer disables bounds check and we just + // reserve a lot of buffer space to not need such check normally. + return false; + } + else + { + while (N-- > 0 && I < TableSize) + { + Table[I] = Table[I - 1]; + I++; + } + } + } + else + { + uint N; + if (Number == 18) + { + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } + } + } + TablesRead5 = true; + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop) + { + return false; + } + + MakeDecodeTables(Table, 0, BlockTables.LD, NC); + MakeDecodeTables(Table, (int)NC, BlockTables.DD, DC); + MakeDecodeTables(Table, (int)(NC + DC), BlockTables.LDD, LDC); + MakeDecodeTables(Table, (int)(NC + DC + LDC), BlockTables.RD, RC); + return true; + } + private void InitFilters() => //Filters.SoftReset(); Filters.Clear(); From e786c007679dd6fcbd03a05588d3065554808cd6 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:20:51 +0000 Subject: [PATCH 10/22] divide async and sync logic --- .../Rar/UnpackV2017/Unpack.unpack50_async.cs | 717 ++++++++++++++++++ .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 703 ----------------- 2 files changed, 717 insertions(+), 703 deletions(-) create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs new file mode 100644 index 00000000..5be4308a --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs @@ -0,0 +1,717 @@ +#nullable disable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Tasks; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; +using size_t = System.UInt32; + +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack +{ + private async Task Unpack5Async( + bool Solid, + CancellationToken cancellationToken = default + ) + { + FileExtracted = true; + + if (!Suspended) + { + UnpInitData(Solid); + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if ( + !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) + || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) + || !TablesRead5 + ) + { + return; + } + } + + while (true) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr >= ReadBorder) + { + var FileDone = false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while ( + Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + && Inp.InBit >= BlockHeader.BlockBitSize + ) + { + if (BlockHeader.LastBlockInFile) + { + FileDone = true; + break; + } + if ( + !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) + || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) + ) + { + return; + } + } + if (FileDone || !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) + { + UnpWriteBuf(); + if (WrittenFileSize > DestUnpSize) + { + return; + } + + if (Suspended) + { + FileExtracted = false; + return; + } + } + + var MainSlot = DecodeNumber(Inp, BlockTables.LD); + if (MainSlot < 256) + { + if (Fragmented) + { + FragWindow[UnpPtr++] = (byte)MainSlot; + } + else + { + Window[UnpPtr++] = (byte)MainSlot; + } + continue; + } + if (MainSlot >= 262) + { + var Length = SlotToLength(Inp, MainSlot - 262); + + uint DBits, + Distance = 1, + DistSlot = DecodeNumber(Inp, BlockTables.DD); + if (DistSlot < 4) + { + DBits = 0; + Distance += DistSlot; + } + else + { + DBits = (DistSlot / 2) - 1; + Distance += (2 | (DistSlot & 1)) << (int)DBits; + } + + if (DBits > 0) + { + if (DBits >= 4) + { + if (DBits > 4) + { + Distance += ((Inp.getbits32() >> (int)(36 - DBits)) << 4); + Inp.addbits(DBits - 4); + } + + var LowDist = DecodeNumber(Inp, BlockTables.LDD); + Distance += LowDist; + } + else + { + Distance += Inp.getbits32() >> (int)(32 - DBits); + Inp.addbits(DBits); + } + } + + if (Distance > 0x100) + { + Length++; + if (Distance > 0x2000) + { + Length++; + if (Distance > 0x40000) + { + Length++; + } + } + } + + InsertOldDist(Distance); + LastLength = Length; + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } + continue; + } + if (MainSlot == 256) + { + var Filter = new UnpackFilter(); + if ( + !await ReadFilterAsync(Inp, Filter, cancellationToken).ConfigureAwait(false) + || !AddFilter(Filter) + ) + { + break; + } + continue; + } + if (MainSlot == 257) + { + if (LastLength != 0) + { + if (Fragmented) + { + FragWindow.CopyString(LastLength, OldDist[0], ref UnpPtr, MaxWinMask); + } + else + { + CopyString(LastLength, OldDist[0]); + } + } + continue; + } + if (MainSlot < 262) + { + var DistNum = MainSlot - 258; + var Distance = OldDist[DistNum]; + for (var I = DistNum; I > 0; I--) + { + OldDist[I] = OldDist[I - 1]; + } + + OldDist[0] = Distance; + + var LengthSlot = DecodeNumber(Inp, BlockTables.RD); + var Length = SlotToLength(Inp, LengthSlot); + LastLength = Length; + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } + + continue; + } + } + UnpWriteBuf(); + } + + private async Task ReadFilterAsync( + BitInput Inp, + UnpackFilter Filter, + CancellationToken cancellationToken = default + ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Filter.BlockStart = ReadFilterData(Inp); + Filter.BlockLength = ReadFilterData(Inp); + if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) + { + Filter.BlockLength = 0; + } + + Filter.Type = (byte)(Inp.fgetbits() >> 13); + Inp.faddbits(3); + + if (Filter.Type == FILTER_DELTA) + { + Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); + Inp.faddbits(5); + } + + return true; + } + + private async Task UnpReadBufAsync( + CancellationToken cancellationToken = default + ) + { + var DataSize = ReadTop - Inp.InAddr; // Data left to process. + if (DataSize < 0) + { + return false; + } + + BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; + if (Inp.InAddr > MAX_SIZE / 2) + { + if (DataSize > 0) + { + Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); + } + + Inp.InAddr = 0; + ReadTop = DataSize; + } + else + { + DataSize = ReadTop; + } + + var ReadCode = 0; + if (MAX_SIZE != DataSize) + { + ReadCode = await UnpIO_UnpReadAsync( + Inp.InBuf, + DataSize, + MAX_SIZE - DataSize, + cancellationToken + ) + .ConfigureAwait(false); + } + + if (ReadCode > 0) // Can be also -1. + { + ReadTop += ReadCode; + } + + ReadBorder = ReadTop - 30; + BlockHeader.BlockStart = Inp.InAddr; + if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. + { + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + } + return ReadCode != -1; + } + + private async Task UnpWriteBufAsync( + CancellationToken cancellationToken = default + ) + { + var WrittenBorder = WrPtr; + var FullWriteSize = (UnpPtr - WrittenBorder) & MaxWinMask; + var WriteSizeLeft = FullWriteSize; + var NotAllFiltersProcessed = false; + + for (var I = 0; I < Filters.Count; I++) + { + var flt = Filters[I]; + if (flt.Type == FILTER_NONE) + { + continue; + } + + if (flt.NextWindow) + { + if (((flt.BlockStart - WrPtr) & MaxWinMask) <= FullWriteSize) + { + flt.NextWindow = false; + } + continue; + } + + var BlockStart = flt.BlockStart; + var BlockLength = flt.BlockLength; + if (((BlockStart - WrittenBorder) & MaxWinMask) < WriteSizeLeft) + { + if (WrittenBorder != BlockStart) + { + await UnpWriteAreaAsync(WrittenBorder, BlockStart, cancellationToken) + .ConfigureAwait(false); + WrittenBorder = BlockStart; + WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; + } + if (BlockLength <= WriteSizeLeft) + { + if (BlockLength > 0) + { + var BlockEnd = (BlockStart + BlockLength) & MaxWinMask; + + FilterSrcMemory = EnsureCapacity( + FilterSrcMemory, + checked((int)BlockLength) + ); + var Mem = FilterSrcMemory; + if (BlockStart < BlockEnd || BlockEnd == 0) + { + if (Fragmented) + { + FragWindow.CopyData(Mem, 0, BlockStart, BlockLength); + } + else + { + Buffer.BlockCopy(Window, (int)BlockStart, Mem, 0, (int)BlockLength); + } + } + else + { + var FirstPartLength = MaxWinSize - BlockStart; + if (Fragmented) + { + FragWindow.CopyData(Mem, 0, BlockStart, FirstPartLength); + FragWindow.CopyData(Mem, FirstPartLength, 0, BlockEnd); + } + else + { + Buffer.BlockCopy( + Window, + (int)BlockStart, + Mem, + 0, + (int)FirstPartLength + ); + Buffer.BlockCopy( + Window, + 0, + Mem, + (int)FirstPartLength, + (int)BlockEnd + ); + } + } + + var OutMem = ApplyFilter(Mem, BlockLength, flt); + + Filters[I].Type = FILTER_NONE; + + if (OutMem != null) + { + await UnpIO_UnpWriteAsync(OutMem, 0, BlockLength, cancellationToken) + .ConfigureAwait(false); + WrittenFileSize += BlockLength; + } + + WrittenBorder = BlockEnd; + WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; + } + } + else + { + NotAllFiltersProcessed = true; + for (var J = I; J < Filters.Count; J++) + { + var fltj = Filters[J]; + if ( + fltj.Type != FILTER_NONE + && fltj.NextWindow == false + && ((fltj.BlockStart - WrPtr) & MaxWinMask) < FullWriteSize + ) + { + fltj.NextWindow = true; + } + } + break; + } + } + } + + var EmptyCount = 0; + for (var I = 0; I < Filters.Count; I++) + { + if (EmptyCount > 0) + { + Filters[I - EmptyCount] = Filters[I]; + } + + if (Filters[I].Type == FILTER_NONE) + { + EmptyCount++; + } + } + if (EmptyCount > 0) + { + Filters.RemoveRange(Filters.Count - EmptyCount, EmptyCount); + } + + if (!NotAllFiltersProcessed) + { + await UnpWriteAreaAsync(WrittenBorder, UnpPtr, cancellationToken).ConfigureAwait(false); + WrPtr = UnpPtr; + } + + WriteBorder = (UnpPtr + Math.Min(MaxWinSize, UNPACK_MAX_WRITE)) & MaxWinMask; + + if ( + WriteBorder == UnpPtr + || WrPtr != UnpPtr + && ((WrPtr - UnpPtr) & MaxWinMask) < ((WriteBorder - UnpPtr) & MaxWinMask) + ) + { + WriteBorder = WrPtr; + } + } + + private async Task UnpWriteAreaAsync( + size_t StartPtr, + size_t EndPtr, + CancellationToken cancellationToken = default + ) + { + if (EndPtr != StartPtr) + { + UnpSomeRead = true; + } + + if (EndPtr < StartPtr) + { + UnpAllBuf = true; + } + + if (Fragmented) + { + var SizeToWrite = (EndPtr - StartPtr) & MaxWinMask; + while (SizeToWrite > 0) + { + var BlockSize = FragWindow.GetBlockSize(StartPtr, SizeToWrite); + FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); + await UnpWriteDataAsync(__buffer, __offset, BlockSize, cancellationToken) + .ConfigureAwait(false); + SizeToWrite -= BlockSize; + StartPtr = (StartPtr + BlockSize) & MaxWinMask; + } + } + else if (EndPtr < StartPtr) + { + await UnpWriteDataAsync(Window, StartPtr, MaxWinSize - StartPtr, cancellationToken) + .ConfigureAwait(false); + await UnpWriteDataAsync(Window, 0, EndPtr, cancellationToken).ConfigureAwait(false); + } + else + { + await UnpWriteDataAsync(Window, StartPtr, EndPtr - StartPtr, cancellationToken) + .ConfigureAwait(false); + } + } + + private async Task UnpWriteDataAsync( + byte[] Data, + size_t offset, + size_t Size, + CancellationToken cancellationToken = default + ) + { + if (WrittenFileSize >= DestUnpSize) + { + return; + } + + var WriteSize = Size; + var LeftToWrite = DestUnpSize - WrittenFileSize; + if (WriteSize > LeftToWrite) + { + WriteSize = (size_t)LeftToWrite; + } + + await UnpIO_UnpWriteAsync(Data, offset, WriteSize, cancellationToken).ConfigureAwait(false); + WrittenFileSize += Size; + } + + private async Task ReadBlockHeaderAsync( + BitInput Inp, + CancellationToken cancellationToken = default + ) + { + BlockHeader.HeaderSize = 0; + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Inp.faddbits((uint)((8 - Inp.InBit) & 7)); + + var BlockFlags = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. + + if (ByteCount == 4) + { + return false; + } + + BlockHeader.HeaderSize = (int)(2 + ByteCount); + + BlockHeader.BlockBitSize = (BlockFlags & 7) + 1; + + var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + + var BlockSize = 0; + for (uint I = 0; I < ByteCount; I++) + { + BlockSize += (int)((Inp.fgetbits() >> 8) << (int)(I * 8)); + Inp.addbits(8); + } + + BlockHeader.BlockSize = BlockSize; + var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); + if (CheckSum != SavedCheckSum) + { + return false; + } + + BlockHeader.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + + BlockHeader.LastBlockInFile = (BlockFlags & 0x40) != 0; + BlockHeader.TablePresent = (BlockFlags & 0x80) != 0; + return true; + } + + private async Task ReadTablesAsync( + BitInput Inp, + CancellationToken cancellationToken = default + ) + { + if (!BlockHeader.TablePresent) + { + return true; + } + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var BitLength = new byte[checked((int)BC)]; + for (int I = 0; I < BC; I++) + { + uint Length = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (Length == 15) + { + uint ZeroCount = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (ZeroCount == 0) + { + BitLength[I] = 15; + } + else + { + ZeroCount += 2; + while (ZeroCount-- > 0 && I < BitLength.Length) + { + BitLength[I++] = 0; + } + + I--; + } + } + else + { + BitLength[I] = (byte)Length; + } + } + + MakeDecodeTables(BitLength, 0, BlockTables.BD, BC); + + var Table = new byte[checked((int)HUFF_TABLE_SIZE)]; + const int TableSize = checked((int)HUFF_TABLE_SIZE); + for (int I = 0; I < TableSize; ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var Number = DecodeNumber(Inp, BlockTables.BD); + if (Number < 16) + { + Table[I] = (byte)Number; + I++; + } + else if (Number < 18) + { + uint N; + if (Number == 16) + { + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + if (I == 0) + { + // We cannot have "repeat previous" code at the first position. + // Multiple such codes would shift Inp position without changing I, + // which can lead to reading beyond of Inp boundary in mutithreading + // mode, where Inp.ExternalBuffer disables bounds check and we just + // reserve a lot of buffer space to not need such check normally. + return false; + } + else + { + while (N-- > 0 && I < TableSize) + { + Table[I] = Table[I - 1]; + I++; + } + } + } + else + { + uint N; + if (Number == 18) + { + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } + } + } + TablesRead5 = true; + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop) + { + return false; + } + + MakeDecodeTables(Table, 0, BlockTables.LD, NC); + MakeDecodeTables(Table, (int)NC, BlockTables.DD, DC); + MakeDecodeTables(Table, (int)(NC + DC), BlockTables.LDD, LDC); + MakeDecodeTables(Table, (int)(NC + DC + LDC), BlockTables.RD, RC); + return true; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 1812c004..9194ad40 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -216,216 +216,6 @@ internal partial class Unpack UnpWriteBuf(); } - private async System.Threading.Tasks.Task Unpack5Async( - bool Solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - FileExtracted = true; - - if (!Suspended) - { - UnpInitData(Solid); - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - - // Check TablesRead5 to be sure that we read tables at least once - // regardless of current block header TablePresent flag. - // So we can safefly use these tables below. - if ( - !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) - || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) - || !TablesRead5 - ) - { - return; - } - } - - while (true) - { - UnpPtr &= MaxWinMask; - - if (Inp.InAddr >= ReadBorder) - { - var FileDone = false; - - // We use 'while', because for empty block containing only Huffman table, - // we'll be on the block border once again just after reading the table. - while ( - Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 - || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 - && Inp.InBit >= BlockHeader.BlockBitSize - ) - { - if (BlockHeader.LastBlockInFile) - { - FileDone = true; - break; - } - if ( - !await ReadBlockHeaderAsync(Inp, cancellationToken).ConfigureAwait(false) - || !await ReadTablesAsync(Inp, cancellationToken).ConfigureAwait(false) - ) - { - return; - } - } - if (FileDone || !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) - { - UnpWriteBuf(); - if (WrittenFileSize > DestUnpSize) - { - return; - } - - if (Suspended) - { - FileExtracted = false; - return; - } - } - - var MainSlot = DecodeNumber(Inp, BlockTables.LD); - if (MainSlot < 256) - { - if (Fragmented) - { - FragWindow[UnpPtr++] = (byte)MainSlot; - } - else - { - Window[UnpPtr++] = (byte)MainSlot; - } - continue; - } - if (MainSlot >= 262) - { - var Length = SlotToLength(Inp, MainSlot - 262); - - uint DBits, - Distance = 1, - DistSlot = DecodeNumber(Inp, BlockTables.DD); - if (DistSlot < 4) - { - DBits = 0; - Distance += DistSlot; - } - else - { - DBits = (DistSlot / 2) - 1; - Distance += (2 | (DistSlot & 1)) << (int)DBits; - } - - if (DBits > 0) - { - if (DBits >= 4) - { - if (DBits > 4) - { - Distance += ((Inp.getbits32() >> (int)(36 - DBits)) << 4); - Inp.addbits(DBits - 4); - } - - var LowDist = DecodeNumber(Inp, BlockTables.LDD); - Distance += LowDist; - } - else - { - Distance += Inp.getbits32() >> (int)(32 - DBits); - Inp.addbits(DBits); - } - } - - if (Distance > 0x100) - { - Length++; - if (Distance > 0x2000) - { - Length++; - if (Distance > 0x40000) - { - Length++; - } - } - } - - InsertOldDist(Distance); - LastLength = Length; - if (Fragmented) - { - FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); - } - else - { - CopyString(Length, Distance); - } - continue; - } - if (MainSlot == 256) - { - var Filter = new UnpackFilter(); - if ( - !await ReadFilterAsync(Inp, Filter, cancellationToken).ConfigureAwait(false) - || !AddFilter(Filter) - ) - { - break; - } - continue; - } - if (MainSlot == 257) - { - if (LastLength != 0) - { - if (Fragmented) - { - FragWindow.CopyString(LastLength, OldDist[0], ref UnpPtr, MaxWinMask); - } - else - { - CopyString(LastLength, OldDist[0]); - } - } - continue; - } - if (MainSlot < 262) - { - var DistNum = MainSlot - 258; - var Distance = OldDist[DistNum]; - for (var I = DistNum; I > 0; I--) - { - OldDist[I] = OldDist[I - 1]; - } - - OldDist[0] = Distance; - - var LengthSlot = DecodeNumber(Inp, BlockTables.RD); - var Length = SlotToLength(Inp, LengthSlot); - LastLength = Length; - if (Fragmented) - { - FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); - } - else - { - CopyString(Length, Distance); - } - - continue; - } - } - UnpWriteBuf(); - } - private uint ReadFilterData(BitInput Inp) { var ByteCount = (Inp.fgetbits() >> 14) + 1; @@ -469,39 +259,6 @@ internal partial class Unpack return true; } - private async System.Threading.Tasks.Task ReadFilterAsync( - BitInput Inp, - UnpackFilter Filter, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - Filter.BlockStart = ReadFilterData(Inp); - Filter.BlockLength = ReadFilterData(Inp); - if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) - { - Filter.BlockLength = 0; - } - - Filter.Type = (byte)(Inp.fgetbits() >> 13); - Inp.faddbits(3); - - if (Filter.Type == FILTER_DELTA) - { - Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); - Inp.faddbits(5); - } - - return true; - } - private bool AddFilter(UnpackFilter Filter) { if (Filters.Count >= MAX_UNPACK_FILTERS) @@ -576,58 +333,6 @@ internal partial class Unpack return ReadCode != -1; } - private async System.Threading.Tasks.Task UnpReadBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var DataSize = ReadTop - Inp.InAddr; // Data left to process. - if (DataSize < 0) - { - return false; - } - - BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; - if (Inp.InAddr > MAX_SIZE / 2) - { - if (DataSize > 0) - { - Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); - } - - Inp.InAddr = 0; - ReadTop = DataSize; - } - else - { - DataSize = ReadTop; - } - - var ReadCode = 0; - if (MAX_SIZE != DataSize) - { - ReadCode = await UnpIO_UnpReadAsync( - Inp.InBuf, - DataSize, - MAX_SIZE - DataSize, - cancellationToken - ) - .ConfigureAwait(false); - } - - if (ReadCode > 0) // Can be also -1. - { - ReadTop += ReadCode; - } - - ReadBorder = ReadTop - 30; - BlockHeader.BlockStart = Inp.InAddr; - if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. - { - ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - } - return ReadCode != -1; - } - private void UnpWriteBuf() { var WrittenBorder = WrPtr; @@ -822,163 +527,6 @@ internal partial class Unpack } } - private async System.Threading.Tasks.Task UnpWriteBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var WrittenBorder = WrPtr; - var FullWriteSize = (UnpPtr - WrittenBorder) & MaxWinMask; - var WriteSizeLeft = FullWriteSize; - var NotAllFiltersProcessed = false; - - for (var I = 0; I < Filters.Count; I++) - { - var flt = Filters[I]; - if (flt.Type == FILTER_NONE) - { - continue; - } - - if (flt.NextWindow) - { - if (((flt.BlockStart - WrPtr) & MaxWinMask) <= FullWriteSize) - { - flt.NextWindow = false; - } - continue; - } - - var BlockStart = flt.BlockStart; - var BlockLength = flt.BlockLength; - if (((BlockStart - WrittenBorder) & MaxWinMask) < WriteSizeLeft) - { - if (WrittenBorder != BlockStart) - { - await UnpWriteAreaAsync(WrittenBorder, BlockStart, cancellationToken) - .ConfigureAwait(false); - WrittenBorder = BlockStart; - WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; - } - if (BlockLength <= WriteSizeLeft) - { - if (BlockLength > 0) - { - var BlockEnd = (BlockStart + BlockLength) & MaxWinMask; - - FilterSrcMemory = EnsureCapacity( - FilterSrcMemory, - checked((int)BlockLength) - ); - var Mem = FilterSrcMemory; - if (BlockStart < BlockEnd || BlockEnd == 0) - { - if (Fragmented) - { - FragWindow.CopyData(Mem, 0, BlockStart, BlockLength); - } - else - { - Buffer.BlockCopy(Window, (int)BlockStart, Mem, 0, (int)BlockLength); - } - } - else - { - var FirstPartLength = MaxWinSize - BlockStart; - if (Fragmented) - { - FragWindow.CopyData(Mem, 0, BlockStart, FirstPartLength); - FragWindow.CopyData(Mem, FirstPartLength, 0, BlockEnd); - } - else - { - Buffer.BlockCopy( - Window, - (int)BlockStart, - Mem, - 0, - (int)FirstPartLength - ); - Buffer.BlockCopy( - Window, - 0, - Mem, - (int)FirstPartLength, - (int)BlockEnd - ); - } - } - - var OutMem = ApplyFilter(Mem, BlockLength, flt); - - Filters[I].Type = FILTER_NONE; - - if (OutMem != null) - { - await UnpIO_UnpWriteAsync(OutMem, 0, BlockLength, cancellationToken) - .ConfigureAwait(false); - WrittenFileSize += BlockLength; - } - - WrittenBorder = BlockEnd; - WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; - } - } - else - { - NotAllFiltersProcessed = true; - for (var J = I; J < Filters.Count; J++) - { - var fltj = Filters[J]; - if ( - fltj.Type != FILTER_NONE - && fltj.NextWindow == false - && ((fltj.BlockStart - WrPtr) & MaxWinMask) < FullWriteSize - ) - { - fltj.NextWindow = true; - } - } - break; - } - } - } - - var EmptyCount = 0; - for (var I = 0; I < Filters.Count; I++) - { - if (EmptyCount > 0) - { - Filters[I - EmptyCount] = Filters[I]; - } - - if (Filters[I].Type == FILTER_NONE) - { - EmptyCount++; - } - } - if (EmptyCount > 0) - { - Filters.RemoveRange(Filters.Count - EmptyCount, EmptyCount); - } - - if (!NotAllFiltersProcessed) - { - await UnpWriteAreaAsync(WrittenBorder, UnpPtr, cancellationToken).ConfigureAwait(false); - WrPtr = UnpPtr; - } - - WriteBorder = (UnpPtr + Math.Min(MaxWinSize, UNPACK_MAX_WRITE)) & MaxWinMask; - - if ( - WriteBorder == UnpPtr - || WrPtr != UnpPtr - && ((WrPtr - UnpPtr) & MaxWinMask) < ((WriteBorder - UnpPtr) & MaxWinMask) - ) - { - WriteBorder = WrPtr; - } - } - private byte[] ApplyFilter(byte[] __d, uint DataSize, UnpackFilter Flt) { var Data = 0; @@ -1110,48 +658,6 @@ internal partial class Unpack } } - private async System.Threading.Tasks.Task UnpWriteAreaAsync( - size_t StartPtr, - size_t EndPtr, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (EndPtr != StartPtr) - { - UnpSomeRead = true; - } - - if (EndPtr < StartPtr) - { - UnpAllBuf = true; - } - - if (Fragmented) - { - var SizeToWrite = (EndPtr - StartPtr) & MaxWinMask; - while (SizeToWrite > 0) - { - var BlockSize = FragWindow.GetBlockSize(StartPtr, SizeToWrite); - FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); - await UnpWriteDataAsync(__buffer, __offset, BlockSize, cancellationToken) - .ConfigureAwait(false); - SizeToWrite -= BlockSize; - StartPtr = (StartPtr + BlockSize) & MaxWinMask; - } - } - else if (EndPtr < StartPtr) - { - await UnpWriteDataAsync(Window, StartPtr, MaxWinSize - StartPtr, cancellationToken) - .ConfigureAwait(false); - await UnpWriteDataAsync(Window, 0, EndPtr, cancellationToken).ConfigureAwait(false); - } - else - { - await UnpWriteDataAsync(Window, StartPtr, EndPtr - StartPtr, cancellationToken) - .ConfigureAwait(false); - } - } - private void UnpWriteData(byte[] Data, size_t offset, size_t Size) { if (WrittenFileSize >= DestUnpSize) @@ -1170,29 +676,6 @@ internal partial class Unpack WrittenFileSize += Size; } - private async System.Threading.Tasks.Task UnpWriteDataAsync( - byte[] Data, - size_t offset, - size_t Size, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (WrittenFileSize >= DestUnpSize) - { - return; - } - - var WriteSize = Size; - var LeftToWrite = DestUnpSize - WrittenFileSize; - if (WriteSize > LeftToWrite) - { - WriteSize = (size_t)LeftToWrite; - } - - await UnpIO_UnpWriteAsync(Data, offset, WriteSize, cancellationToken).ConfigureAwait(false); - WrittenFileSize += Size; - } - private void UnpInitData50(bool Solid) { if (!Solid) @@ -1253,61 +736,6 @@ internal partial class Unpack return true; } - private async System.Threading.Tasks.Task ReadBlockHeaderAsync( - BitInput Inp, - System.Threading.CancellationToken cancellationToken = default - ) - { - BlockHeader.HeaderSize = 0; - - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - Inp.faddbits((uint)((8 - Inp.InBit) & 7)); - - var BlockFlags = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. - - if (ByteCount == 4) - { - return false; - } - - BlockHeader.HeaderSize = (int)(2 + ByteCount); - - BlockHeader.BlockBitSize = (BlockFlags & 7) + 1; - - var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - - var BlockSize = 0; - for (uint I = 0; I < ByteCount; I++) - { - BlockSize += (int)((Inp.fgetbits() >> 8) << (int)(I * 8)); - Inp.addbits(8); - } - - BlockHeader.BlockSize = BlockSize; - var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); - if (CheckSum != SavedCheckSum) - { - return false; - } - - BlockHeader.BlockStart = Inp.InAddr; - ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - - BlockHeader.LastBlockInFile = (BlockFlags & 0x40) != 0; - BlockHeader.TablePresent = (BlockFlags & 0x80) != 0; - return true; - } - private bool ReadTables( BitInput Inp, ref UnpackBlockHeader Header, @@ -1440,137 +868,6 @@ internal partial class Unpack return true; } - private async System.Threading.Tasks.Task ReadTablesAsync( - BitInput Inp, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (!BlockHeader.TablePresent) - { - return true; - } - - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - var BitLength = new byte[checked((int)BC)]; - for (int I = 0; I < BC; I++) - { - uint Length = (byte)(Inp.fgetbits() >> 12); - Inp.faddbits(4); - if (Length == 15) - { - uint ZeroCount = (byte)(Inp.fgetbits() >> 12); - Inp.faddbits(4); - if (ZeroCount == 0) - { - BitLength[I] = 15; - } - else - { - ZeroCount += 2; - while (ZeroCount-- > 0 && I < BitLength.Length) - { - BitLength[I++] = 0; - } - - I--; - } - } - else - { - BitLength[I] = (byte)Length; - } - } - - MakeDecodeTables(BitLength, 0, BlockTables.BD, BC); - - var Table = new byte[checked((int)HUFF_TABLE_SIZE)]; - const int TableSize = checked((int)HUFF_TABLE_SIZE); - for (int I = 0; I < TableSize; ) - { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - var Number = DecodeNumber(Inp, BlockTables.BD); - if (Number < 16) - { - Table[I] = (byte)Number; - I++; - } - else if (Number < 18) - { - uint N; - if (Number == 16) - { - N = (Inp.fgetbits() >> 13) + 3; - Inp.faddbits(3); - } - else - { - N = (Inp.fgetbits() >> 9) + 11; - Inp.faddbits(7); - } - if (I == 0) - { - // We cannot have "repeat previous" code at the first position. - // Multiple such codes would shift Inp position without changing I, - // which can lead to reading beyond of Inp boundary in mutithreading - // mode, where Inp.ExternalBuffer disables bounds check and we just - // reserve a lot of buffer space to not need such check normally. - return false; - } - else - { - while (N-- > 0 && I < TableSize) - { - Table[I] = Table[I - 1]; - I++; - } - } - } - else - { - uint N; - if (Number == 18) - { - N = (Inp.fgetbits() >> 13) + 3; - Inp.faddbits(3); - } - else - { - N = (Inp.fgetbits() >> 9) + 11; - Inp.faddbits(7); - } - while (N-- > 0 && I < TableSize) - { - Table[I++] = 0; - } - } - } - TablesRead5 = true; - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop) - { - return false; - } - - MakeDecodeTables(Table, 0, BlockTables.LD, NC); - MakeDecodeTables(Table, (int)NC, BlockTables.DD, DC); - MakeDecodeTables(Table, (int)(NC + DC), BlockTables.LDD, LDC); - MakeDecodeTables(Table, (int)(NC + DC + LDC), BlockTables.RD, RC); - return true; - } - private void InitFilters() => //Filters.SoftReset(); Filters.Clear(); From 218af5a8b3e1ea129ed3865f5af000bfd187adf3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:27:53 +0000 Subject: [PATCH 11/22] validate and make sure rar5 methods are the same --- .../Rar/UnpackV2017/Unpack.unpack50_async.cs | 18 +++------ .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 40 +++++++++---------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs index 5be4308a..59004430 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs @@ -3,7 +3,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Tasks; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; using size_t = System.UInt32; @@ -12,10 +11,7 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017; internal partial class Unpack { - private async Task Unpack5Async( - bool Solid, - CancellationToken cancellationToken = default - ) + private async Task Unpack5Async(bool Solid, CancellationToken cancellationToken = default) { FileExtracted = true; @@ -77,7 +73,7 @@ internal partial class Unpack if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) { - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken); if (WrittenFileSize > DestUnpSize) { return; @@ -219,7 +215,7 @@ internal partial class Unpack continue; } } - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken); } private async Task ReadFilterAsync( @@ -255,9 +251,7 @@ internal partial class Unpack return true; } - private async Task UnpReadBufAsync( - CancellationToken cancellationToken = default - ) + private async Task UnpReadBufAsync(CancellationToken cancellationToken = default) { var DataSize = ReadTop - Inp.InAddr; // Data left to process. if (DataSize < 0) @@ -307,9 +301,7 @@ internal partial class Unpack return ReadCode != -1; } - private async Task UnpWriteBufAsync( - CancellationToken cancellationToken = default - ) + private async Task UnpWriteBufAsync(CancellationToken cancellationToken = default) { var WrittenBorder = WrPtr; var FullWriteSize = (UnpPtr - WrittenBorder) & MaxWinMask; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 9194ad40..c614e591 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -25,7 +25,7 @@ internal partial class Unpack // regardless of current block header TablePresent flag. // So we can safefly use these tables below. if ( - !ReadBlockHeader(Inp, ref BlockHeader) + !ReadBlockHeader(Inp) || !ReadTables(Inp, ref BlockHeader, ref BlockTables) || !TablesRead5 ) @@ -684,9 +684,9 @@ internal partial class Unpack } } - private bool ReadBlockHeader(BitInput Inp, ref UnpackBlockHeader Header) + private bool ReadBlockHeader(BitInput Inp) { - Header.HeaderSize = 0; + BlockHeader.HeaderSize = 0; if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) { @@ -707,9 +707,9 @@ internal partial class Unpack return false; } - Header.HeaderSize = (int)(2 + ByteCount); + BlockHeader.HeaderSize = (int)(2 + ByteCount); - Header.BlockBitSize = (BlockFlags & 7) + 1; + BlockHeader.BlockBitSize = (BlockFlags & 7) + 1; var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); Inp.faddbits(8); @@ -721,28 +721,24 @@ internal partial class Unpack Inp.addbits(8); } - Header.BlockSize = BlockSize; + BlockHeader.BlockSize = BlockSize; var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); if (CheckSum != SavedCheckSum) { return false; } - Header.BlockStart = Inp.InAddr; - ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); + BlockHeader.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - Header.LastBlockInFile = (BlockFlags & 0x40) != 0; - Header.TablePresent = (BlockFlags & 0x80) != 0; + BlockHeader.LastBlockInFile = (BlockFlags & 0x40) != 0; + BlockHeader.TablePresent = (BlockFlags & 0x80) != 0; return true; } - private bool ReadTables( - BitInput Inp, - ref UnpackBlockHeader Header, - ref UnpackBlockTables Tables - ) + private bool ReadTables(BitInput Inp) { - if (!Header.TablePresent) + if (!BlockHeader.TablePresent) { return true; } @@ -785,7 +781,7 @@ internal partial class Unpack } } - MakeDecodeTables(BitLength, 0, Tables.BD, BC); + MakeDecodeTables(BitLength, 0, BlockTables.BD, BC); Span Table = stackalloc byte[checked((int)HUFF_TABLE_SIZE)]; const int TableSize = checked((int)HUFF_TABLE_SIZE); @@ -799,7 +795,7 @@ internal partial class Unpack } } - var Number = DecodeNumber(Inp, Tables.BD); + var Number = DecodeNumber(Inp, BlockTables.BD); if (Number < 16) { Table[I] = (byte)Number; @@ -861,10 +857,10 @@ internal partial class Unpack return false; } - MakeDecodeTables(Table, 0, Tables.LD, NC); - MakeDecodeTables(Table, (int)NC, Tables.DD, DC); - MakeDecodeTables(Table, (int)(NC + DC), Tables.LDD, LDC); - MakeDecodeTables(Table, (int)(NC + DC + LDC), Tables.RD, RC); + MakeDecodeTables(Table, 0, BlockTables.LD, NC); + MakeDecodeTables(Table, (int)NC, BlockTables.DD, DC); + MakeDecodeTables(Table, (int)(NC + DC), BlockTables.LDD, LDC); + MakeDecodeTables(Table, (int)(NC + DC + LDC), BlockTables.RD, RC); return true; } From cd5da3da5dde514a8bf2483343476f028eddd511 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:35:41 +0000 Subject: [PATCH 12/22] moved and validated more async code --- .../Rar/UnpackV2017/Unpack.unpack15_async.cs | 100 ++++++ .../Rar/UnpackV2017/Unpack.unpack15_cpp.cs | 96 ------ .../Rar/UnpackV2017/Unpack.unpack20_async.cs | 319 ++++++++++++++++++ .../Rar/UnpackV2017/Unpack.unpack20_cpp.cs | 318 ----------------- .../Rar/UnpackV2017/Unpack.unpack50_cpp.cs | 11 +- 5 files changed, 421 insertions(+), 423 deletions(-) create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs new file mode 100644 index 00000000..e615527e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs @@ -0,0 +1,100 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack +{ + private async Task Unpack15Async(bool Solid, CancellationToken cancellationToken = default) + { + UnpInitData(Solid); + UnpInitData15(Solid); + await UnpReadBufAsync(cancellationToken).ConfigureAwait(false); + if (!Solid) + { + InitHuff(); + UnpPtr = 0; + } + else + { + UnpPtr = WrPtr; + } + + --DestUnpSize; + if (DestUnpSize >= 0) + { + GetFlagsBuf(); + FlagsCnt = 8; + } + + while (DestUnpSize >= 0) + { + UnpPtr &= MaxWinMask; + + if ( + Inp.InAddr > ReadTop - 30 + && !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false) + ) + { + break; + } + + if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) + { + UnpWriteBuf20(); + } + + if (StMode != 0) + { + HuffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt = 7; + } + + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + LongLZ(); + } + else + { + HuffDecode(); + } + } + else + { + FlagBuf <<= 1; + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt = 7; + } + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + HuffDecode(); + } + else + { + LongLZ(); + } + } + else + { + FlagBuf <<= 1; + ShortLZ(); + } + } + } + UnpWriteBuf20(); + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs index 9a2882b7..c1886df8 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -200,102 +200,6 @@ internal partial class Unpack UnpWriteBuf20(); } - private async System.Threading.Tasks.Task Unpack15Async( - bool Solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - UnpInitData(Solid); - UnpInitData15(Solid); - await UnpReadBufAsync(cancellationToken).ConfigureAwait(false); - if (!Solid) - { - InitHuff(); - UnpPtr = 0; - } - else - { - UnpPtr = WrPtr; - } - - --DestUnpSize; - if (DestUnpSize >= 0) - { - GetFlagsBuf(); - FlagsCnt = 8; - } - - while (DestUnpSize >= 0) - { - UnpPtr &= MaxWinMask; - - if ( - Inp.InAddr > ReadTop - 30 - && !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false) - ) - { - break; - } - - if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) - { - await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); - } - - if (StMode != 0) - { - HuffDecode(); - continue; - } - - if (--FlagsCnt < 0) - { - GetFlagsBuf(); - FlagsCnt = 7; - } - - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - LongLZ(); - } - else - { - HuffDecode(); - } - } - else - { - FlagBuf <<= 1; - if (--FlagsCnt < 0) - { - GetFlagsBuf(); - FlagsCnt = 7; - } - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - HuffDecode(); - } - else - { - LongLZ(); - } - } - else - { - FlagBuf <<= 1; - ShortLZ(); - } - } - } - await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); - } - //#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos]) private uint GetShortLen1(uint pos) => ((pos) == 1 ? (uint)(Buf60 + 3) : ShortLen1[pos]); diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs new file mode 100644 index 00000000..0619189a --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs @@ -0,0 +1,319 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; +using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; + +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack +{ + private async Task Unpack20Async(bool Solid, CancellationToken cancellationToken = default) + { + uint Bits; + + if (Suspended) + { + UnpPtr = WrPtr; + } + else + { + UnpInitData(Solid); + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + if ( + (!Solid || !TablesRead2) + && !await ReadTables20Async(cancellationToken).ConfigureAwait(false) + ) + { + return; + } + + --DestUnpSize; + } + + while (DestUnpSize >= 0) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr > ReadTop - 30) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) + { + UnpWriteBuf20(); + if (Suspended) + { + return; + } + } + if (UnpAudioBlock) + { + var AudioNumber = DecodeNumber(Inp, MD[UnpCurChannel]); + + if (AudioNumber == 256) + { + if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) + { + break; + } + + continue; + } + Window[UnpPtr++] = DecodeAudio((int)AudioNumber); + if (++UnpCurChannel == UnpChannels) + { + UnpCurChannel = 0; + } + + --DestUnpSize; + continue; + } + + var Number = DecodeNumber(Inp, BlockTables.LD); + if (Number < 256) + { + Window[UnpPtr++] = (byte)Number; + --DestUnpSize; + continue; + } + if (Number > 269) + { + var Length = (uint)(LDecode[Number -= 270] + 3); + if ((Bits = LBits[Number]) > 0) + { + Length += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + + var DistNumber = DecodeNumber(Inp, BlockTables.DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) + { + Distance += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000L) + { + Length++; + } + } + + CopyString20(Length, Distance); + continue; + } + if (Number == 269) + { + if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) + { + break; + } + + continue; + } + if (Number == 256) + { + CopyString20(LastLength, LastDist); + continue; + } + if (Number < 261) + { + var Distance = OldDist[(OldDistPtr - (Number - 256)) & 3]; + var LengthNumber = DecodeNumber(Inp, BlockTables.RD); + var Length = (uint)(LDecode[LengthNumber] + 2); + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + if (Distance >= 0x101) + { + Length++; + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000) + { + Length++; + } + } + } + CopyString20(Length, Distance); + continue; + } + if (Number < 270) + { + var Distance = (uint)(SDDecode[Number -= 261] + 1); + if ((Bits = SDBits[Number]) > 0) + { + Distance += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + CopyString20(2, Distance); + continue; + } + } + ReadLastTables(); + UnpWriteBuf20(); + } + + private async Task UnpWriteBuf20Async(CancellationToken cancellationToken = default) + { + if (UnpPtr != WrPtr) + { + UnpSomeRead = true; + } + + if (UnpPtr < WrPtr) + { + await UnpIO_UnpWriteAsync( + Window, + WrPtr, + (uint)(-(int)WrPtr & MaxWinMask), + cancellationToken + ) + .ConfigureAwait(false); + await UnpIO_UnpWriteAsync(Window, 0, UnpPtr, cancellationToken).ConfigureAwait(false); + UnpAllBuf = true; + } + else + { + await UnpIO_UnpWriteAsync(Window, WrPtr, UnpPtr - WrPtr, cancellationToken) + .ConfigureAwait(false); + } + + WrPtr = UnpPtr; + } + + private async Task ReadTables20Async(CancellationToken cancellationToken = default) + { + byte[] BitLength = new byte[checked((int)BC20)]; + byte[] Table = new byte[checked((int)MC20 * 4)]; + if (Inp.InAddr > ReadTop - 25) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var BitField = Inp.getbits(); + UnpAudioBlock = (BitField & 0x8000) != 0; + + if ((BitField & 0x4000) != 0) + { + Array.Clear(UnpOldTable20, 0, UnpOldTable20.Length); + } + + Inp.addbits(2); + + uint TableSize; + if (UnpAudioBlock) + { + UnpChannels = ((BitField >> 12) & 3) + 1; + if (UnpCurChannel >= UnpChannels) + { + UnpCurChannel = 0; + } + + Inp.addbits(2); + TableSize = MC20 * UnpChannels; + } + else + { + TableSize = NC20 + DC20 + RC20; + } + + for (int I = 0; I < checked((int)BC20); I++) + { + BitLength[I] = (byte)(Inp.getbits() >> 12); + Inp.addbits(4); + } + MakeDecodeTables(BitLength, 0, BlockTables.BD, BC20); + for (int I = 0; I < checked((int)TableSize); ) + { + if (Inp.InAddr > ReadTop - 5) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var Number = DecodeNumber(Inp, BlockTables.BD); + if (Number < 16) + { + Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); + I++; + } + else if (Number == 16) + { + var N = (Inp.getbits() >> 14) + 3; + Inp.addbits(2); + if (I == 0) + { + return false; // We cannot have "repeat previous" code at the first position. + } + else + { + while (N-- > 0 && I < TableSize) + { + Table[I] = Table[I - 1]; + I++; + } + } + } + else + { + uint N; + if (Number == 17) + { + N = (Inp.getbits() >> 13) + 3; + Inp.addbits(3); + } + else + { + N = (Inp.getbits() >> 9) + 11; + Inp.addbits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } + } + } + TablesRead2 = true; + if (Inp.InAddr > ReadTop) + { + return true; + } + + if (UnpAudioBlock) + { + for (int I = 0; I < UnpChannels; I++) + { + MakeDecodeTables(Table, (int)(I * MC20), MD[I], MC20); + } + } + else + { + MakeDecodeTables(Table, 0, BlockTables.LD, NC20); + MakeDecodeTables(Table, (int)NC20, BlockTables.DD, DC20); + MakeDecodeTables(Table, (int)(NC20 + DC20), BlockTables.RD, RC20); + } + Array.Copy(Table, 0, this.UnpOldTable20, 0, UnpOldTable20.Length); + return true; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 725d70ce..55b4174c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -342,170 +342,6 @@ internal partial class Unpack UnpWriteBuf20(); } - private async System.Threading.Tasks.Task Unpack20Async( - bool Solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - uint Bits; - - if (Suspended) - { - UnpPtr = WrPtr; - } - else - { - UnpInitData(Solid); - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - - if ( - (!Solid || !TablesRead2) - && !await ReadTables20Async(cancellationToken).ConfigureAwait(false) - ) - { - return; - } - - --DestUnpSize; - } - - while (DestUnpSize >= 0) - { - UnpPtr &= MaxWinMask; - - if (Inp.InAddr > ReadTop - 30) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) - { - await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); - if (Suspended) - { - return; - } - } - if (UnpAudioBlock) - { - var AudioNumber = DecodeNumber(Inp, MD[UnpCurChannel]); - - if (AudioNumber == 256) - { - if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) - { - break; - } - - continue; - } - Window[UnpPtr++] = DecodeAudio((int)AudioNumber); - if (++UnpCurChannel == UnpChannels) - { - UnpCurChannel = 0; - } - - --DestUnpSize; - continue; - } - - var Number = DecodeNumber(Inp, BlockTables.LD); - if (Number < 256) - { - Window[UnpPtr++] = (byte)Number; - --DestUnpSize; - continue; - } - if (Number > 269) - { - var Length = (uint)(LDecode[Number -= 270] + 3); - if ((Bits = LBits[Number]) > 0) - { - Length += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - - var DistNumber = DecodeNumber(Inp, BlockTables.DD); - var Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - Distance += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000L) - { - Length++; - } - } - - CopyString20(Length, Distance); - continue; - } - if (Number == 269) - { - if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) - { - break; - } - - continue; - } - if (Number == 256) - { - CopyString20(LastLength, LastDist); - continue; - } - if (Number < 261) - { - var Distance = OldDist[(OldDistPtr - (Number - 256)) & 3]; - var LengthNumber = DecodeNumber(Inp, BlockTables.RD); - var Length = (uint)(LDecode[LengthNumber] + 2); - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - if (Distance >= 0x101) - { - Length++; - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000) - { - Length++; - } - } - } - CopyString20(Length, Distance); - continue; - } - if (Number < 270) - { - var Distance = (uint)(SDDecode[Number -= 261] + 1); - if ((Bits = SDBits[Number]) > 0) - { - Distance += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - CopyString20(2, Distance); - continue; - } - } - ReadLastTables(); - await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); - } - private void UnpWriteBuf20() { if (UnpPtr != WrPtr) @@ -527,36 +363,6 @@ internal partial class Unpack WrPtr = UnpPtr; } - private async System.Threading.Tasks.Task UnpWriteBuf20Async( - System.Threading.CancellationToken cancellationToken = default - ) - { - if (UnpPtr != WrPtr) - { - UnpSomeRead = true; - } - - if (UnpPtr < WrPtr) - { - await UnpIO_UnpWriteAsync( - Window, - WrPtr, - (uint)(-(int)WrPtr & MaxWinMask), - cancellationToken - ) - .ConfigureAwait(false); - await UnpIO_UnpWriteAsync(Window, 0, UnpPtr, cancellationToken).ConfigureAwait(false); - UnpAllBuf = true; - } - else - { - await UnpIO_UnpWriteAsync(Window, WrPtr, UnpPtr - WrPtr, cancellationToken) - .ConfigureAwait(false); - } - - WrPtr = UnpPtr; - } - private bool ReadTables20() { Span BitLength = stackalloc byte[checked((int)BC20)]; @@ -677,130 +483,6 @@ internal partial class Unpack return true; } - private async System.Threading.Tasks.Task ReadTables20Async( - System.Threading.CancellationToken cancellationToken = default - ) - { - byte[] BitLength = new byte[checked((int)BC20)]; - byte[] Table = new byte[checked((int)MC20 * 4)]; - if (Inp.InAddr > ReadTop - 25) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - var BitField = Inp.getbits(); - UnpAudioBlock = (BitField & 0x8000) != 0; - - if ((BitField & 0x4000) != 0) - { - Array.Clear(UnpOldTable20, 0, UnpOldTable20.Length); - } - - Inp.addbits(2); - - uint TableSize; - if (UnpAudioBlock) - { - UnpChannels = ((BitField >> 12) & 3) + 1; - if (UnpCurChannel >= UnpChannels) - { - UnpCurChannel = 0; - } - - Inp.addbits(2); - TableSize = MC20 * UnpChannels; - } - else - { - TableSize = NC20 + DC20 + RC20; - } - - for (int I = 0; I < checked((int)BC20); I++) - { - BitLength[I] = (byte)(Inp.getbits() >> 12); - Inp.addbits(4); - } - MakeDecodeTables(BitLength, 0, BlockTables.BD, BC20); - for (int I = 0; I < checked((int)TableSize); ) - { - if (Inp.InAddr > ReadTop - 5) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - var Number = DecodeNumber(Inp, BlockTables.BD); - if (Number < 16) - { - Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xF); - I++; - } - else if (Number < 18) - { - uint N; - if (Number == 16) - { - N = (Inp.getbits() >> 14) + 3; - Inp.addbits(2); - } - else - { - N = (Inp.getbits() >> 13) + 11; - Inp.addbits(3); - } - if (I == 0) - { - return false; - } - - while (N-- > 0 && I < checked((int)TableSize)) - { - Table[I] = Table[I - 1]; - I++; - } - } - else - { - uint N; - if (Number == 18) - { - N = (Inp.getbits() >> 13) + 3; - Inp.addbits(3); - } - else - { - N = (Inp.getbits() >> 9) + 11; - Inp.addbits(7); - } - - while (N-- > 0 && I < checked((int)TableSize)) - { - Table[I++] = 0; - } - } - } - if (UnpAudioBlock) - { - for (int I = 0; I < UnpChannels; I++) - { - MakeDecodeTables(Table, (int)(I * MC20), MD[I], MC20); - } - } - else - { - MakeDecodeTables(Table, 0, BlockTables.LD, NC20); - MakeDecodeTables(Table, (int)NC20, BlockTables.DD, DC20); - MakeDecodeTables(Table, (int)(NC20 + DC20), BlockTables.RD, RC20); - } - Array.Copy(Table, 0, this.UnpOldTable20, 0, UnpOldTable20.Length); - return true; - } - private void ReadLastTables() { if (ReadTop >= Inp.InAddr + 5) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index c614e591..2959021b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -24,11 +24,7 @@ internal partial class Unpack // Check TablesRead5 to be sure that we read tables at least once // regardless of current block header TablePresent flag. // So we can safefly use these tables below. - if ( - !ReadBlockHeader(Inp) - || !ReadTables(Inp, ref BlockHeader, ref BlockTables) - || !TablesRead5 - ) + if (!ReadBlockHeader(Inp) || !ReadTables(Inp) || !TablesRead5) { return; } @@ -55,10 +51,7 @@ internal partial class Unpack FileDone = true; break; } - if ( - !ReadBlockHeader(Inp, ref BlockHeader) - || !ReadTables(Inp, ref BlockHeader, ref BlockTables) - ) + if (!ReadBlockHeader(Inp) || !ReadTables(Inp)) { return; } From ab1dd45e9c29bc98f6749b81d721a5e4f5fae964 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:47:20 +0000 Subject: [PATCH 13/22] more moved and validated --- .../Compressors/Rar/UnpackV1/Unpack.Async.cs | 603 ++++++++++++++++++ .../Compressors/Rar/UnpackV1/Unpack.cs | 600 ----------------- .../Rar/UnpackV1/Unpack15.Async.cs | 162 +++++ .../Compressors/Rar/UnpackV1/Unpack15.cs | 160 ----- .../Rar/UnpackV1/Unpack20.Async.cs | 275 ++++++++ .../Compressors/Rar/UnpackV1/Unpack20.cs | 271 -------- .../Rar/UnpackV1/Unpack50.Async.cs | 321 ++++++++++ .../Compressors/Rar/UnpackV1/Unpack50.cs | 348 ---------- 8 files changed, 1361 insertions(+), 1379 deletions(-) create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs create mode 100644 src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs new file mode 100644 index 00000000..37655cf1 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs @@ -0,0 +1,603 @@ +using System; +using System.Buffers; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Rar.Headers; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; +using SharpCompress.Compressors.Rar.UnpackV1.PPM; +using SharpCompress.Compressors.Rar.VM; + +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal sealed partial class Unpack +{ + public async Task DoUnpackAsync( + FileHeader fileHeader, + Stream readStream, + Stream writeStream, + CancellationToken cancellationToken = default + ) + { + destUnpSize = fileHeader.UncompressedSize; + this.fileHeader = fileHeader; + this.readStream = readStream; + this.writeStream = writeStream; + if (!fileHeader.IsSolid) + { + Init(); + } + suspended = false; + await DoUnpackAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task DoUnpackAsync(CancellationToken cancellationToken = default) + { + if (fileHeader.CompressionMethod == 0) + { + await UnstoreFileAsync(cancellationToken).ConfigureAwait(false); + return; + } + switch (fileHeader.CompressionAlgorithm) + { + case 15: + await unpack15Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); + break; + case 20: + case 26: + await unpack20Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); + break; + case 29: + case 36: + await Unpack29Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); + break; + case 50: + await Unpack5Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); + break; + default: + throw new InvalidFormatException( + "unknown rar compression version " + fileHeader.CompressionAlgorithm + ); + } + } + + private async Task UnstoreFileAsync(CancellationToken cancellationToken = default) + { + var buffer = new byte[(int)Math.Min(0x10000, destUnpSize)]; + do + { + var code = await readStream + .ReadAsync(buffer, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false); + if (code == 0 || code == -1) + { + break; + } + code = code < destUnpSize ? code : (int)destUnpSize; + await writeStream.WriteAsync(buffer, 0, code, cancellationToken).ConfigureAwait(false); + destUnpSize -= code; + } while (!suspended && destUnpSize > 0); + } + + private async Task Unpack29Async(bool solid, CancellationToken cancellationToken = default) + { + int[] DDecode = new int[PackDef.DC]; + byte[] DBits = new byte[PackDef.DC]; + + int Bits; + + if (DDecode[1] == 0) + { + int Dist = 0, + BitLength = 0, + Slot = 0; + for (var I = 0; I < DBitLengthCounts.Length; I++, BitLength++) + { + var count = DBitLengthCounts[I]; + for (var J = 0; J < count; J++, Slot++, Dist += (1 << BitLength)) + { + DDecode[Slot] = Dist; + DBits[Slot] = (byte)BitLength; + } + } + } + + FileExtracted = true; + + if (!suspended) + { + UnpInitData(solid); + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + if ((!solid || !tablesRead) && !ReadTables()) + { + return; + } + } + + if (ppmError) + { + return; + } + + while (true) + { + unpPtr &= PackDef.MAXWINMASK; + + if (inAddr > readBorder) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) + { + UnpWriteBuf(); + if (destUnpSize < 0) + { + return; + } + if (suspended) + { + FileExtracted = false; + return; + } + } + if (unpBlockType == BlockTypes.BLOCK_PPM) + { + var Ch = ppm.DecodeChar(); + if (Ch == -1) + { + ppmError = true; + break; + } + if (Ch == PpmEscChar) + { + var NextCh = ppm.DecodeChar(); + if (NextCh == 0) + { + if (!ReadTables()) + { + break; + } + continue; + } + if (NextCh == 2 || NextCh == -1) + { + break; + } + if (NextCh == 3) + { + if (!ReadVMCodePPM()) + { + break; + } + continue; + } + if (NextCh == 4) + { + int Distance = 0, + Length = 0; + var failed = false; + for (var I = 0; I < 4 && !failed; I++) + { + var ch = ppm.DecodeChar(); + if (ch == -1) + { + failed = true; + } + else + { + if (I == 3) + { + Length = ch & 0xff; + } + else + { + Distance = (Distance << 8) + (ch & 0xff); + } + } + } + if (failed) + { + break; + } + CopyString(Length + 32, Distance + 2); + continue; + } + if (NextCh == 5) + { + var Length = ppm.DecodeChar(); + if (Length == -1) + { + break; + } + CopyString(Length + 4, 1); + continue; + } + } + window[unpPtr++] = (byte)Ch; + continue; + } + + var Number = this.decodeNumber(LD); + if (Number < 256) + { + window[unpPtr++] = (byte)Number; + continue; + } + if (Number >= 271) + { + var Length = LDecode[Number -= 271] + 3; + if ((Bits = LBits[Number]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + + var DistNumber = this.decodeNumber(DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) + { + if (DistNumber > 9) + { + if (Bits > 4) + { + Distance += ((Utility.URShift(GetBits(), (20 - Bits))) << 4); + AddBits(Bits - 4); + } + if (lowDistRepCount > 0) + { + lowDistRepCount--; + Distance += prevLowDist; + } + else + { + var LowDist = this.decodeNumber(LDD); + if (LowDist == 16) + { + lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; + Distance += prevLowDist; + } + else + { + Distance += LowDist; + prevLowDist = LowDist; + } + } + } + else + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + } + + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000L) + { + Length++; + } + } + + InsertOldDist(Distance); + InsertLastMatch(Length, Distance); + CopyString(Length, Distance); + continue; + } + if (Number == 256) + { + if (!ReadEndOfBlock()) + { + break; + } + continue; + } + if (Number == 257) + { + if (!ReadVMCode()) + { + break; + } + continue; + } + if (Number == 258) + { + if (lastLength != 0) + { + CopyString(lastLength, lastDist); + } + continue; + } + if (Number < 263) + { + var DistNum = Number - 259; + var Distance = oldDist[DistNum]; + for (var I = DistNum; I > 0; I--) + { + oldDist[I] = oldDist[I - 1]; + } + oldDist[0] = Distance; + + var LengthNumber = this.decodeNumber(RD); + var Length = LDecode[LengthNumber] + 2; + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + InsertLastMatch(Length, Distance); + CopyString(Length, Distance); + continue; + } + if (Number < 272) + { + var Distance = SDDecode[Number -= 263] + 1; + if ((Bits = SDBits[Number]) > 0) + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + InsertOldDist(Distance); + InsertLastMatch(2, Distance); + CopyString(2, Distance); + } + } + UnpWriteBuf(); + } + + private async Task UnpWriteBufAsync(CancellationToken cancellationToken = default) + { + var WrittenBorder = wrPtr; + var WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + for (var I = 0; I < prgStack.Count; I++) + { + var flt = prgStack[I]; + if (flt is null) + { + continue; + } + if (flt.NextWindow) + { + flt.NextWindow = false; + continue; + } + var BlockStart = flt.BlockStart; + var BlockLength = flt.BlockLength; + if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) + { + if (WrittenBorder != BlockStart) + { + await UnpWriteAreaAsync(WrittenBorder, BlockStart, cancellationToken) + .ConfigureAwait(false); + WrittenBorder = BlockStart; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + } + if (BlockLength <= WriteSize) + { + var BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; + if (BlockStart < BlockEnd || BlockEnd == 0) + { + rarVM.setMemory(0, window, BlockStart, BlockLength); + } + else + { + var FirstPartLength = PackDef.MAXWINSIZE - BlockStart; + rarVM.setMemory(0, window, BlockStart, FirstPartLength); + rarVM.setMemory(FirstPartLength, window, 0, BlockEnd); + } + + var ParentPrg = filters[flt.ParentFilter].Program; + var Prg = flt.Program; + + if (ParentPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + Prg.GlobalData.Clear(); + for ( + var i = 0; + i < ParentPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) + { + Prg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = ParentPrg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; + } + } + + ExecuteCode(Prg); + + if (Prg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + if (ParentPrg.GlobalData.Count < Prg.GlobalData.Count) + { + ParentPrg.GlobalData.SetSize(Prg.GlobalData.Count); + } + + for (var i = 0; i < Prg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) + { + ParentPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = Prg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; + } + } + else + { + ParentPrg.GlobalData.Clear(); + } + + var FilteredDataOffset = Prg.FilteredDataOffset; + var FilteredDataSize = Prg.FilteredDataSize; + var FilteredData = ArrayPool.Shared.Rent(FilteredDataSize); + try + { + Array.Copy( + rarVM.Mem, + FilteredDataOffset, + FilteredData, + 0, + FilteredDataSize + ); + + prgStack[I] = null; + while (I + 1 < prgStack.Count) + { + var NextFilter = prgStack[I + 1]; + if ( + NextFilter is null + || NextFilter.BlockStart != BlockStart + || NextFilter.BlockLength != FilteredDataSize + || NextFilter.NextWindow + ) + { + break; + } + + rarVM.setMemory(0, FilteredData, 0, FilteredDataSize); + + var pPrg = filters[NextFilter.ParentFilter].Program; + var NextPrg = NextFilter.Program; + + if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count); + + for ( + var i = 0; + i < pPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) + { + NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = + pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; + } + } + + ExecuteCode(NextPrg); + + if (NextPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count) + { + pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count); + } + + for ( + var i = 0; + i < NextPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) + { + pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = + NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; + } + } + else + { + pPrg.GlobalData.Clear(); + } + + FilteredDataOffset = NextPrg.FilteredDataOffset; + FilteredDataSize = NextPrg.FilteredDataSize; + if (FilteredData.Length < FilteredDataSize) + { + ArrayPool.Shared.Return(FilteredData); + FilteredData = ArrayPool.Shared.Rent(FilteredDataSize); + } + for (var i = 0; i < FilteredDataSize; i++) + { + FilteredData[i] = NextPrg.GlobalData[FilteredDataOffset + i]; + } + + I++; + prgStack[I] = null; + } + + await writeStream + .WriteAsync(FilteredData, 0, FilteredDataSize, cancellationToken) + .ConfigureAwait(false); + writtenFileSize += FilteredDataSize; + destUnpSize -= FilteredDataSize; + WrittenBorder = BlockEnd; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + } + finally + { + ArrayPool.Shared.Return(FilteredData); + } + } + else + { + for (var J = I; J < prgStack.Count; J++) + { + var filt = prgStack[J]; + if (filt != null && filt.NextWindow) + { + filt.NextWindow = false; + } + } + wrPtr = WrittenBorder; + return; + } + } + } + + await UnpWriteAreaAsync(WrittenBorder, unpPtr, cancellationToken).ConfigureAwait(false); + wrPtr = unpPtr; + } + + private async Task UnpWriteAreaAsync( + int startPtr, + int endPtr, + CancellationToken cancellationToken = default + ) + { + if (endPtr < startPtr) + { + await UnpWriteDataAsync( + window, + startPtr, + -startPtr & PackDef.MAXWINMASK, + cancellationToken + ) + .ConfigureAwait(false); + await UnpWriteDataAsync(window, 0, endPtr, cancellationToken).ConfigureAwait(false); + } + else + { + await UnpWriteDataAsync(window, startPtr, endPtr - startPtr, cancellationToken) + .ConfigureAwait(false); + } + } + + private async Task UnpWriteDataAsync( + byte[] data, + int offset, + int size, + CancellationToken cancellationToken = default + ) + { + if (destUnpSize < 0) + { + return; + } + var writeSize = size; + if (writeSize > destUnpSize) + { + writeSize = (int)destUnpSize; + } + await writeStream + .WriteAsync(data, offset, writeSize, cancellationToken) + .ConfigureAwait(false); + + writtenFileSize += size; + destUnpSize -= size; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 18c2c2e3..c4548ef8 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -150,25 +150,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack DoUnpack(); } - public async System.Threading.Tasks.Task DoUnpackAsync( - FileHeader fileHeader, - Stream readStream, - Stream writeStream, - System.Threading.CancellationToken cancellationToken = default - ) - { - destUnpSize = fileHeader.UncompressedSize; - this.fileHeader = fileHeader; - this.readStream = readStream; - this.writeStream = writeStream; - if (!fileHeader.IsSolid) - { - Init(); - } - suspended = false; - await DoUnpackAsync(cancellationToken).ConfigureAwait(false); - } - public void DoUnpack() { if (fileHeader.CompressionMethod == 0) @@ -203,42 +184,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack } } - public async System.Threading.Tasks.Task DoUnpackAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - if (fileHeader.CompressionMethod == 0) - { - await UnstoreFileAsync(cancellationToken).ConfigureAwait(false); - return; - } - switch (fileHeader.CompressionAlgorithm) - { - case 15: // rar 1.5 compression - await unpack15Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); - break; - - case 20: // rar 2.x compression - case 26: // files larger than 2GB - await unpack20Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); - break; - - case 29: // rar 3.x compression - case 36: // alternative hash - await Unpack29Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); - break; - - case 50: // rar 5.x compression - await Unpack5Async(fileHeader.IsSolid, cancellationToken).ConfigureAwait(false); - break; - - default: - throw new InvalidFormatException( - "unknown rar compression version " + fileHeader.CompressionAlgorithm - ); - } - } - private void UnstoreFile() { Span buffer = stackalloc byte[(int)Math.Min(0x10000, destUnpSize)]; @@ -255,26 +200,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack } while (!suspended && destUnpSize > 0); } - private async System.Threading.Tasks.Task UnstoreFileAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var buffer = new byte[(int)Math.Min(0x10000, destUnpSize)]; - do - { - var code = await readStream - .ReadAsync(buffer, 0, buffer.Length, cancellationToken) - .ConfigureAwait(false); - if (code == 0 || code == -1) - { - break; - } - code = code < destUnpSize ? code : (int)destUnpSize; - await writeStream.WriteAsync(buffer, 0, code, cancellationToken).ConfigureAwait(false); - destUnpSize -= code; - } while (!suspended && destUnpSize > 0); - } - private void Unpack29(bool solid) { Span DDecode = stackalloc int[PackDef.DC]; @@ -553,281 +478,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack UnpWriteBuf(); } - private async System.Threading.Tasks.Task Unpack29Async( - bool solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - int[] DDecode = new int[PackDef.DC]; - byte[] DBits = new byte[PackDef.DC]; - - int Bits; - - if (DDecode[1] == 0) - { - int Dist = 0, - BitLength = 0, - Slot = 0; - for (var I = 0; I < DBitLengthCounts.Length; I++, BitLength++) - { - var count = DBitLengthCounts[I]; - for (var J = 0; J < count; J++, Slot++, Dist += (1 << BitLength)) - { - DDecode[Slot] = Dist; - DBits[Slot] = (byte)BitLength; - } - } - } - - FileExtracted = true; - - if (!suspended) - { - UnpInitData(solid); - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - if ((!solid || !tablesRead) && !ReadTables()) - { - return; - } - } - - if (ppmError) - { - return; - } - - while (true) - { - unpPtr &= PackDef.MAXWINMASK; - - if (inAddr > readBorder) - { - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) - { - await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - if (destUnpSize < 0) - { - return; - } - if (suspended) - { - FileExtracted = false; - return; - } - } - if (unpBlockType == BlockTypes.BLOCK_PPM) - { - var ch = ppm.DecodeChar(); - if (ch == -1) - { - ppmError = true; - break; - } - if (ch == PpmEscChar) - { - var nextCh = ppm.DecodeChar(); - if (nextCh == 0) - { - if (!ReadTables()) - { - break; - } - continue; - } - if (nextCh == 2 || nextCh == -1) - { - break; - } - if (nextCh == 3) - { - if (!ReadVMCode()) - { - break; - } - continue; - } - if (nextCh == 4) - { - uint Distance = 0, - Length = 0; - var failed = false; - for (var I = 0; I < 4 && !failed; I++) - { - var ch2 = ppm.DecodeChar(); - if (ch2 == -1) - { - failed = true; - } - else if (I == 3) - { - Length = (uint)ch2; - } - else - { - Distance = (Distance << 8) + (uint)ch2; - } - } - if (failed) - { - break; - } - - CopyString(Length + 32, Distance + 2); - continue; - } - if (nextCh == 5) - { - var length = ppm.DecodeChar(); - if (length == -1) - { - break; - } - CopyString((uint)(length + 4), 1); - continue; - } - } - window[unpPtr++] = (byte)ch; - continue; - } - - var Number = this.decodeNumber(LD); - if (Number < 256) - { - window[unpPtr++] = (byte)Number; - continue; - } - if (Number >= 271) - { - var Length = LDecode[Number -= 271] + 3; - if ((Bits = LBits[Number]) > 0) - { - Length += GetBits() >> (16 - Bits); - AddBits(Bits); - } - - var DistNumber = this.decodeNumber(DD); - var Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - if (DistNumber > 9) - { - if (Bits > 4) - { - Distance += (GetBits() >> (20 - Bits)) << 4; - AddBits(Bits - 4); - } - if (lowDistRepCount > 0) - { - lowDistRepCount--; - Distance += prevLowDist; - } - else - { - var LowDist = this.decodeNumber(LDD); - if (LowDist == 16) - { - lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; - Distance += prevLowDist; - } - else - { - Distance += LowDist; - prevLowDist = (int)LowDist; - } - } - } - else - { - Distance += GetBits() >> (16 - Bits); - AddBits(Bits); - } - } - - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000) - { - Length++; - } - } - - InsertOldDist(Distance); - lastLength = Length; - CopyString(Length, Distance); - continue; - } - if (Number == 256) - { - if (!ReadEndOfBlock()) - { - break; - } - continue; - } - if (Number == 257) - { - if (!ReadVMCode()) - { - break; - } - continue; - } - if (Number == 258) - { - if (lastLength != 0) - { - CopyString(lastLength, oldDist[0]); - } - - continue; - } - if (Number < 263) - { - var DistNum = Number - 259; - var Distance = (uint)oldDist[DistNum]; - for (var I = DistNum; I > 0; I--) - { - oldDist[I] = oldDist[I - 1]; - } - oldDist[0] = (int)Distance; - - var LengthNumber = this.decodeNumber(RD); - var Length = LDecode[LengthNumber] + 2; - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += GetBits() >> (16 - Bits); - AddBits(Bits); - } - lastLength = Length; - CopyString((uint)Length, Distance); - continue; - } - if (Number < 272) - { - var Distance = SDDecode[Number -= 263] + 1; - if ((Bits = SDBits[Number]) > 0) - { - Distance += GetBits() >> (16 - Bits); - AddBits(Bits); - } - InsertOldDist((uint)Distance); - lastLength = 2; - CopyString(2, (uint)Distance); - } - } - await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - } - private void UnpWriteBuf() { var WrittenBorder = wrPtr; @@ -1684,256 +1334,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack } } - private async System.Threading.Tasks.Task UnpWriteBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var WrittenBorder = wrPtr; - var WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; - for (var I = 0; I < prgStack.Count; I++) - { - var flt = prgStack[I]; - if (flt is null) - { - continue; - } - if (flt.NextWindow) - { - flt.NextWindow = false; - continue; - } - var BlockStart = flt.BlockStart; - var BlockLength = flt.BlockLength; - if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) - { - if (WrittenBorder != BlockStart) - { - await UnpWriteAreaAsync(WrittenBorder, BlockStart, cancellationToken) - .ConfigureAwait(false); - WrittenBorder = BlockStart; - WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; - } - if (BlockLength <= WriteSize) - { - var BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; - if (BlockStart < BlockEnd || BlockEnd == 0) - { - rarVM.setMemory(0, window, BlockStart, BlockLength); - } - else - { - var FirstPartLength = PackDef.MAXWINSIZE - BlockStart; - rarVM.setMemory(0, window, BlockStart, FirstPartLength); - rarVM.setMemory(FirstPartLength, window, 0, BlockEnd); - } - - var ParentPrg = filters[flt.ParentFilter].Program; - var Prg = flt.Program; - - if (ParentPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - Prg.GlobalData.Clear(); - for ( - var i = 0; - i < ParentPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; - i++ - ) - { - Prg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = ParentPrg.GlobalData[ - RarVM.VM_FIXEDGLOBALSIZE + i - ]; - } - } - - ExecuteCode(Prg); - - if (Prg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - if (ParentPrg.GlobalData.Count < Prg.GlobalData.Count) - { - ParentPrg.GlobalData.SetSize(Prg.GlobalData.Count); - } - - for (var i = 0; i < Prg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) - { - ParentPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = Prg.GlobalData[ - RarVM.VM_FIXEDGLOBALSIZE + i - ]; - } - } - else - { - ParentPrg.GlobalData.Clear(); - } - - var FilteredDataOffset = Prg.FilteredDataOffset; - var FilteredDataSize = Prg.FilteredDataSize; - var FilteredData = ArrayPool.Shared.Rent(FilteredDataSize); - try - { - Array.Copy( - rarVM.Mem, - FilteredDataOffset, - FilteredData, - 0, - FilteredDataSize - ); - - prgStack[I] = null; - while (I + 1 < prgStack.Count) - { - var NextFilter = prgStack[I + 1]; - if ( - NextFilter is null - || NextFilter.BlockStart != BlockStart - || NextFilter.BlockLength != FilteredDataSize - || NextFilter.NextWindow - ) - { - break; - } - - rarVM.setMemory(0, FilteredData, 0, FilteredDataSize); - - var pPrg = filters[NextFilter.ParentFilter].Program; - var NextPrg = NextFilter.Program; - - if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count); - - for ( - var i = 0; - i < pPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; - i++ - ) - { - NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - - ExecuteCode(NextPrg); - - if (NextPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count) - { - pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count); - } - - for ( - var i = 0; - i < NextPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; - i++ - ) - { - pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - else - { - pPrg.GlobalData.Clear(); - } - - FilteredDataOffset = NextPrg.FilteredDataOffset; - FilteredDataSize = NextPrg.FilteredDataSize; - if (FilteredData.Length < FilteredDataSize) - { - ArrayPool.Shared.Return(FilteredData); - FilteredData = ArrayPool.Shared.Rent(FilteredDataSize); - } - for (var i = 0; i < FilteredDataSize; i++) - { - FilteredData[i] = NextPrg.GlobalData[FilteredDataOffset + i]; - } - - I++; - prgStack[I] = null; - } - - await writeStream - .WriteAsync(FilteredData, 0, FilteredDataSize, cancellationToken) - .ConfigureAwait(false); - writtenFileSize += FilteredDataSize; - destUnpSize -= FilteredDataSize; - WrittenBorder = BlockEnd; - WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; - } - finally - { - ArrayPool.Shared.Return(FilteredData); - } - } - else - { - for (var J = I; J < prgStack.Count; J++) - { - var filt = prgStack[J]; - if (filt != null && filt.NextWindow) - { - filt.NextWindow = false; - } - } - wrPtr = WrittenBorder; - return; - } - } - } - - await UnpWriteAreaAsync(WrittenBorder, unpPtr, cancellationToken).ConfigureAwait(false); - wrPtr = unpPtr; - } - - private async System.Threading.Tasks.Task UnpWriteAreaAsync( - int startPtr, - int endPtr, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (endPtr < startPtr) - { - await UnpWriteDataAsync( - window, - startPtr, - -startPtr & PackDef.MAXWINMASK, - cancellationToken - ) - .ConfigureAwait(false); - await UnpWriteDataAsync(window, 0, endPtr, cancellationToken).ConfigureAwait(false); - } - else - { - await UnpWriteDataAsync(window, startPtr, endPtr - startPtr, cancellationToken) - .ConfigureAwait(false); - } - } - - private async System.Threading.Tasks.Task UnpWriteDataAsync( - byte[] data, - int offset, - int size, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (destUnpSize < 0) - { - return; - } - var writeSize = size; - if (writeSize > destUnpSize) - { - writeSize = (int)destUnpSize; - } - await writeStream - .WriteAsync(data, offset, writeSize, cancellationToken) - .ConfigureAwait(false); - - writtenFileSize += size; - destUnpSize -= size; - } - private void CleanUp() { if (ppm != null) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs new file mode 100644 index 00000000..2975fbbf --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs @@ -0,0 +1,162 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; + +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack +{ + private async Task unpack15Async(bool solid, CancellationToken cancellationToken = default) + { + if (suspended) + { + unpPtr = wrPtr; + } + else + { + UnpInitData(solid); + oldUnpInitData(solid); + await unpReadBufAsync(cancellationToken).ConfigureAwait(false); + if (!solid) + { + initHuff(); + unpPtr = 0; + } + else + { + unpPtr = wrPtr; + } + --destUnpSize; + } + if (destUnpSize >= 0) + { + getFlagsBuf(); + FlagsCnt = 8; + } + + while (destUnpSize >= 0) + { + unpPtr &= PackDef.MAXWINMASK; + + if ( + inAddr > readTop - 30 + && !await unpReadBufAsync(cancellationToken).ConfigureAwait(false) + ) + { + break; + } + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) + { + oldUnpWriteBuf(); + if (suspended) + { + return; + } + } + if (StMode != 0) + { + huffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + getFlagsBuf(); + FlagsCnt = 7; + } + + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + longLZ(); + } + else + { + huffDecode(); + } + } + else + { + FlagBuf <<= 1; + if (--FlagsCnt < 0) + { + getFlagsBuf(); + FlagsCnt = 7; + } + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + huffDecode(); + } + else + { + longLZ(); + } + } + else + { + FlagBuf <<= 1; + shortLZ(); + } + } + } + oldUnpWriteBuf(); + } + + private async Task unpReadBufAsync(CancellationToken cancellationToken = default) + { + var dataSize = readTop - inAddr; + if (dataSize < 0) + { + return false; + } + if (inAddr > MAX_SIZE / 2) + { + if (dataSize > 0) + { + Array.Copy(InBuf, inAddr, InBuf, 0, dataSize); + } + inAddr = 0; + readTop = dataSize; + } + else + { + dataSize = readTop; + } + + var readCode = await readStream + .ReadAsync(InBuf, dataSize, (MAX_SIZE - dataSize) & ~0xf, cancellationToken) + .ConfigureAwait(false); + if (readCode > 0) + { + readTop += readCode; + } + readBorder = readTop - 30; + return readCode != -1; + } + + private async Task oldUnpWriteBufAsync(CancellationToken cancellationToken = default) + { + if (unpPtr < wrPtr) + { + await writeStream + .WriteAsync(window, wrPtr, -wrPtr & PackDef.MAXWINMASK, cancellationToken) + .ConfigureAwait(false); + await writeStream + .WriteAsync(window, 0, unpPtr, cancellationToken) + .ConfigureAwait(false); + } + else + { + await writeStream + .WriteAsync(window, wrPtr, unpPtr - wrPtr, cancellationToken) + .ConfigureAwait(false); + } + wrPtr = unpPtr; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs index 4b9db7d6..96c9ac6f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs @@ -316,110 +316,6 @@ internal partial class Unpack oldUnpWriteBuf(); } - private async System.Threading.Tasks.Task unpack15Async( - bool solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (suspended) - { - unpPtr = wrPtr; - } - else - { - UnpInitData(solid); - oldUnpInitData(solid); - await unpReadBufAsync(cancellationToken).ConfigureAwait(false); - if (!solid) - { - initHuff(); - unpPtr = 0; - } - else - { - unpPtr = wrPtr; - } - --destUnpSize; - } - if (destUnpSize >= 0) - { - getFlagsBuf(); - FlagsCnt = 8; - } - - while (destUnpSize >= 0) - { - unpPtr &= PackDef.MAXWINMASK; - - if ( - inAddr > readTop - 30 - && !await unpReadBufAsync(cancellationToken).ConfigureAwait(false) - ) - { - break; - } - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) - { - await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - if (suspended) - { - return; - } - } - if (StMode != 0) - { - huffDecode(); - continue; - } - - if (--FlagsCnt < 0) - { - getFlagsBuf(); - FlagsCnt = 7; - } - - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - longLZ(); - } - else - { - huffDecode(); - } - } - else - { - FlagBuf <<= 1; - if (--FlagsCnt < 0) - { - getFlagsBuf(); - FlagsCnt = 7; - } - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - huffDecode(); - } - else - { - longLZ(); - } - } - else - { - FlagBuf <<= 1; - shortLZ(); - } - } - } - await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - } - private bool unpReadBuf() { var dataSize = readTop - inAddr; @@ -455,40 +351,6 @@ internal partial class Unpack return (readCode != -1); } - private async System.Threading.Tasks.Task unpReadBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var dataSize = readTop - inAddr; - if (dataSize < 0) - { - return (false); - } - if (inAddr > MAX_SIZE / 2) - { - if (dataSize > 0) - { - Array.Copy(InBuf, inAddr, InBuf, 0, dataSize); - } - inAddr = 0; - readTop = dataSize; - } - else - { - dataSize = readTop; - } - - var readCode = await readStream - .ReadAsync(InBuf, dataSize, (MAX_SIZE - dataSize) & ~0xf, cancellationToken) - .ConfigureAwait(false); - if (readCode > 0) - { - readTop += readCode; - } - readBorder = readTop - 30; - return (readCode != -1); - } - private int getShortLen1(int pos) => pos == 1 ? Buf60 + 3 : ShortLen1[pos]; private int getShortLen2(int pos) => pos == 3 ? Buf60 + 3 : ShortLen2[pos]; @@ -952,26 +814,4 @@ internal partial class Unpack } wrPtr = unpPtr; } - - private async System.Threading.Tasks.Task oldUnpWriteBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - if (unpPtr < wrPtr) - { - await writeStream - .WriteAsync(window, wrPtr, -wrPtr & PackDef.MAXWINMASK, cancellationToken) - .ConfigureAwait(false); - await writeStream - .WriteAsync(window, 0, unpPtr, cancellationToken) - .ConfigureAwait(false); - } - else - { - await writeStream - .WriteAsync(window, wrPtr, unpPtr - wrPtr, cancellationToken) - .ConfigureAwait(false); - } - wrPtr = unpPtr; - } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs new file mode 100644 index 00000000..69bc6de0 --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs @@ -0,0 +1,275 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; + +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack +{ + private async Task unpack20Async(bool solid, CancellationToken cancellationToken = default) + { + int Bits; + + if (suspended) + { + unpPtr = wrPtr; + } + else + { + UnpInitData(solid); + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + if (!solid) + { + if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) + { + return; + } + } + --destUnpSize; + } + + while (destUnpSize >= 0) + { + unpPtr &= PackDef.MAXWINMASK; + + if (inAddr > readTop - 30) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + break; + } + } + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) + { + oldUnpWriteBuf(); + if (suspended) + { + return; + } + } + if (UnpAudioBlock != 0) + { + var AudioNumber = this.decodeNumber(MD[UnpCurChannel]); + + if (AudioNumber == 256) + { + if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) + { + break; + } + continue; + } + window[unpPtr++] = DecodeAudio(AudioNumber); + if (++UnpCurChannel == UnpChannels) + { + UnpCurChannel = 0; + } + --destUnpSize; + continue; + } + + var Number = this.decodeNumber(LD); + if (Number < 256) + { + window[unpPtr++] = (byte)Number; + --destUnpSize; + continue; + } + if (Number > 269) + { + var Length = LDecode[Number -= 270] + 3; + if ((Bits = LBits[Number]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + + var DistNumber = this.decodeNumber(DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000L) + { + Length++; + } + } + + CopyString20(Length, Distance); + continue; + } + if (Number == 269) + { + if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) + { + break; + } + continue; + } + if (Number == 256) + { + CopyString20(lastLength, lastDist); + continue; + } + if (Number < 261) + { + var Distance = oldDist[(oldDistPtr - (Number - 256)) & 3]; + var LengthNumber = this.decodeNumber(RD); + var Length = LDecode[LengthNumber] + 2; + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + if (Distance >= 0x101) + { + Length++; + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000) + { + Length++; + } + } + } + CopyString20(Length, Distance); + continue; + } + if (Number < 270) + { + var Distance = SDDecode[Number -= 261] + 1; + if ((Bits = SDBits[Number]) > 0) + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + CopyString20(2, Distance); + } + } + ReadLastTables(); + oldUnpWriteBuf(); + } + + private async Task ReadTables20Async(CancellationToken cancellationToken = default) + { + byte[] BitLength = new byte[PackDef.BC20]; + byte[] Table = new byte[PackDef.MC20 * 4]; + int TableSize, + N, + I; + if (inAddr > readTop - 25) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + var BitField = GetBits(); + UnpAudioBlock = (BitField & 0x8000); + + if (0 == (BitField & 0x4000)) + { + new Span(UnpOldTable20).Clear(); + } + AddBits(2); + + if (UnpAudioBlock != 0) + { + UnpChannels = ((Utility.URShift(BitField, 12)) & 3) + 1; + if (UnpCurChannel >= UnpChannels) + { + UnpCurChannel = 0; + } + AddBits(2); + TableSize = PackDef.MC20 * UnpChannels; + } + else + { + TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; + } + for (I = 0; I < PackDef.BC20; I++) + { + BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); + AddBits(4); + } + UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); + I = 0; + while (I < TableSize) + { + if (inAddr > readTop - 5) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + var Number = this.decodeNumber(BD); + if (Number < 16) + { + Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); + I++; + } + else if (Number == 16) + { + N = (Utility.URShift(GetBits(), 14)) + 3; + AddBits(2); + while (N-- > 0 && I < TableSize) + { + Table[I] = Table[I - 1]; + I++; + } + } + else + { + if (Number == 17) + { + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); + } + else + { + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } + } + } + if (inAddr > readTop) + { + return true; + } + if (UnpAudioBlock != 0) + { + for (I = 0; I < UnpChannels; I++) + { + UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); + } + } + else + { + UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); + } + + for (var i = 0; i < UnpOldTable20.Length; i++) + { + UnpOldTable20[i] = Table[i]; + } + return true; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs index 2b941eab..69a266bb 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs @@ -368,163 +368,6 @@ internal partial class Unpack oldUnpWriteBuf(); } - private async System.Threading.Tasks.Task unpack20Async( - bool solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - int Bits; - - if (suspended) - { - unpPtr = wrPtr; - } - else - { - UnpInitData(solid); - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - if (!solid) - { - if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) - { - return; - } - } - --destUnpSize; - } - - while (destUnpSize >= 0) - { - unpPtr &= PackDef.MAXWINMASK; - - if (inAddr > readTop - 30) - { - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - break; - } - } - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) - { - await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - if (suspended) - { - return; - } - } - if (UnpAudioBlock != 0) - { - var AudioNumber = this.decodeNumber(MD[UnpCurChannel]); - - if (AudioNumber == 256) - { - if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) - { - break; - } - continue; - } - window[unpPtr++] = DecodeAudio(AudioNumber); - if (++UnpCurChannel == UnpChannels) - { - UnpCurChannel = 0; - } - --destUnpSize; - continue; - } - - var Number = this.decodeNumber(LD); - if (Number < 256) - { - window[unpPtr++] = (byte)Number; - --destUnpSize; - continue; - } - if (Number > 269) - { - var Length = LDecode[Number -= 270] + 3; - if ((Bits = LBits[Number]) > 0) - { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - - var DistNumber = this.decodeNumber(DD); - var Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - Distance += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000L) - { - Length++; - } - } - - CopyString20(Length, Distance); - continue; - } - if (Number == 269) - { - if (!await ReadTables20Async(cancellationToken).ConfigureAwait(false)) - { - break; - } - continue; - } - if (Number == 256) - { - CopyString20(lastLength, lastDist); - continue; - } - if (Number < 261) - { - var Distance = oldDist[(oldDistPtr - (Number - 256)) & 3]; - var LengthNumber = this.decodeNumber(RD); - var Length = LDecode[LengthNumber] + 2; - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - if (Distance >= 0x101) - { - Length++; - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000) - { - Length++; - } - } - } - CopyString20(Length, Distance); - continue; - } - if (Number < 270) - { - var Distance = SDDecode[Number -= 261] + 1; - if ((Bits = SDBits[Number]) > 0) - { - Distance += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - CopyString20(2, Distance); - } - } - ReadLastTables(); - await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); - } - private void CopyString20(int Length, int Distance) { lastDist = oldDist[oldDistPtr++ & 3] = Distance; @@ -691,120 +534,6 @@ internal partial class Unpack return (true); } - private async System.Threading.Tasks.Task ReadTables20Async( - System.Threading.CancellationToken cancellationToken = default - ) - { - byte[] BitLength = new byte[PackDef.BC20]; - byte[] Table = new byte[PackDef.MC20 * 4]; - int TableSize, - N, - I; - if (inAddr > readTop - 25) - { - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return (false); - } - } - var BitField = GetBits(); - UnpAudioBlock = (BitField & 0x8000); - - if (0 == (BitField & 0x4000)) - { - new Span(UnpOldTable20).Clear(); - } - AddBits(2); - - if (UnpAudioBlock != 0) - { - UnpChannels = ((Utility.URShift(BitField, 12)) & 3) + 1; - if (UnpCurChannel >= UnpChannels) - { - UnpCurChannel = 0; - } - AddBits(2); - TableSize = PackDef.MC20 * UnpChannels; - } - else - { - TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; - } - for (I = 0; I < PackDef.BC20; I++) - { - BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); - AddBits(4); - } - UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); - I = 0; - while (I < TableSize) - { - if (inAddr > readTop - 5) - { - if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return (false); - } - } - var Number = this.decodeNumber(BD); - if (Number < 16) - { - Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); - I++; - } - else if (Number == 16) - { - N = (Utility.URShift(GetBits(), 14)) + 3; - AddBits(2); - while (N-- > 0 && I < TableSize) - { - Table[I] = Table[I - 1]; - I++; - } - } - else - { - if (Number == 17) - { - N = (Utility.URShift(GetBits(), 13)) + 3; - AddBits(3); - } - else - { - N = (Utility.URShift(GetBits(), 9)) + 11; - AddBits(7); - } - while (N-- > 0 && I < TableSize) - { - Table[I++] = 0; - } - } - } - if (inAddr > readTop) - { - return (true); - } - if (UnpAudioBlock != 0) - { - for (I = 0; I < UnpChannels; I++) - { - UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); - } - } - else - { - UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); - UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); - UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); - } - - for (var i = 0; i < UnpOldTable20.Length; i++) - { - UnpOldTable20[i] = Table[i]; - } - return (true); - } - private void unpInitData20(bool Solid) { if (!Solid) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs new file mode 100644 index 00000000..48df787e --- /dev/null +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs @@ -0,0 +1,321 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Compressors.Rar.UnpackV1.Decode; + +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack +{ + private async Task UnpReadBufAsync(CancellationToken cancellationToken = default) + { + var DataSize = ReadTop - Inp.InAddr; // Data left to process. + if (DataSize < 0) + { + return false; + } + + BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; + if (Inp.InAddr > MAX_SIZE / 2) + { + if (DataSize > 0) + { + Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); + } + + Inp.InAddr = 0; + ReadTop = DataSize; + } + else + { + DataSize = ReadTop; + } + + var ReadCode = 0; + if (MAX_SIZE != DataSize) + { + ReadCode = await readStream + .ReadAsync(InBuf, DataSize, MAX_SIZE - DataSize, cancellationToken) + .ConfigureAwait(false); + } + + if (ReadCode > 0) // Can be also -1. + { + ReadTop += ReadCode; + } + + ReadBorder = ReadTop - 30; + BlockHeader.BlockStart = Inp.InAddr; + if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. + { + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + } + return ReadCode != -1; + } + + public async Task Unpack5Async(bool Solid, CancellationToken cancellationToken = default) + { + FileExtracted = true; + + if (!Suspended) + { + UnpInitData(Solid); + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return; + } + + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if ( + !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) + || !ReadTables() + || !TablesRead5 + ) + { + return; + } + } + + while (true) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr >= ReadBorder) + { + var FileDone = false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while ( + Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + && Inp.InBit >= BlockHeader.BlockBitSize + ) + { + if (BlockHeader.LastBlockInFile) + { + FileDone = true; + break; + } + if ( + !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) + || !ReadTables() + ) + { + return; + } + } + if (FileDone || !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + if ( + ((WriteBorder - UnpPtr) & MaxWinMask) < PackDef.MAX_LZ_MATCH + 3 + && WriteBorder != UnpPtr + ) + { + UnpWriteBuf(); + if (WrittenFileSize > DestUnpSize) + { + return; + } + + if (Suspended) + { + FileExtracted = false; + return; + } + } + + var MainSlot = this.DecodeNumber(LD); + if (MainSlot < 256) + { + Window[UnpPtr++] = (byte)MainSlot; + continue; + } + if (MainSlot >= 262) + { + var Length = SlotToLength(MainSlot - 262); + + int DBits; + uint Distance = 1, + DistSlot = this.DecodeNumber(DD); + if (DistSlot < 4) + { + DBits = 0; + Distance += DistSlot; + } + else + { + DBits = (int)((DistSlot / 2) - 1); + Distance += (2 | (DistSlot & 1)) << DBits; + } + + if (DBits > 0) + { + if (DBits >= 4) + { + if (DBits > 4) + { + Distance += ((Inp.getbits() >> (36 - DBits)) << 4); + Inp.AddBits(DBits - 4); + } + var LowDist = this.DecodeNumber(LDD); + Distance += LowDist; + } + else + { + Distance += Inp.getbits() >> (32 - DBits); + Inp.AddBits(DBits); + } + } + + if (Distance > 0x100) + { + Length++; + if (Distance > 0x2000) + { + Length++; + if (Distance > 0x40000) + { + Length++; + } + } + } + + InsertOldDist(Distance); + LastLength = Length; + CopyString(Length, Distance); + continue; + } + if (MainSlot == 256) + { + var Filter = new UnpackFilter(); + if ( + !await ReadFilterAsync(Filter, cancellationToken).ConfigureAwait(false) + || !AddFilter(Filter) + ) + { + break; + } + + continue; + } + if (MainSlot == 257) + { + if (LastLength != 0) + { + CopyString(LastLength, OldDistN(0)); + } + + continue; + } + if (MainSlot < 262) + { + var DistNum = (int)(MainSlot - 258); + var Distance = OldDistN(DistNum); + for (var I = DistNum; I > 0; I--) + { + SetOldDistN(I, OldDistN(I - 1)); + } + + SetOldDistN(0, Distance); + + var LengthSlot = this.DecodeNumber(RD); + var Length = SlotToLength(LengthSlot); + LastLength = Length; + CopyString(Length, Distance); + continue; + } + } + UnpWriteBuf(); + } + + private async Task ReadBlockHeaderAsync(CancellationToken cancellationToken = default) + { + Header.HeaderSize = 0; + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Inp.faddbits((uint)((8 - Inp.InBit) & 7)); + + var BlockFlags = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); + + if (ByteCount == 4) + { + return false; + } + + Header.HeaderSize = (int)(2 + ByteCount); + + Header.BlockBitSize = (BlockFlags & 7) + 1; + + var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + + var BlockSize = 0; + for (var I = 0; I < ByteCount; I++) + { + BlockSize += (int)(Inp.fgetbits() >> 8) << (I * 8); + Inp.AddBits(8); + } + + Header.BlockSize = BlockSize; + var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); + if (CheckSum != SavedCheckSum) + { + return false; + } + + Header.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); + + Header.LastBlockInFile = (BlockFlags & 0x40) != 0; + Header.TablePresent = (BlockFlags & 0x80) != 0; + return true; + } + + private async Task ReadFilterAsync( + UnpackFilter Filter, + CancellationToken cancellationToken = default + ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + { + if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + Filter.uBlockStart = ReadFilterData(); + Filter.uBlockLength = ReadFilterData(); + if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) + { + Filter.BlockLength = 0; + } + + Filter.Type = (byte)(Inp.fgetbits() >> 13); + Inp.faddbits(3); + + if (Filter.Type == (byte)FilterType.FILTER_DELTA) + { + Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); + Inp.faddbits(5); + } + + return true; + } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs index 11047d6c..e3352cfc 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs @@ -479,354 +479,6 @@ internal partial class Unpack return ReadCode != -1; } - private async System.Threading.Tasks.Task UnpReadBufAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - var DataSize = ReadTop - Inp.InAddr; // Data left to process. - if (DataSize < 0) - { - return false; - } - - BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; - if (Inp.InAddr > MAX_SIZE / 2) - { - if (DataSize > 0) - { - Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); - } - - Inp.InAddr = 0; - ReadTop = DataSize; - } - else - { - DataSize = ReadTop; - } - - var ReadCode = 0; - if (MAX_SIZE != DataSize) - { - ReadCode = await readStream - .ReadAsync(InBuf, DataSize, MAX_SIZE - DataSize, cancellationToken) - .ConfigureAwait(false); - } - - if (ReadCode > 0) // Can be also -1. - { - ReadTop += ReadCode; - } - - ReadBorder = ReadTop - 30; - BlockHeader.BlockStart = Inp.InAddr; - if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. - { - ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - } - return ReadCode != -1; - } - - public async System.Threading.Tasks.Task Unpack5Async( - bool Solid, - System.Threading.CancellationToken cancellationToken = default - ) - { - FileExtracted = true; - - if (!Suspended) - { - UnpInitData(Solid); - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - - // Check TablesRead5 to be sure that we read tables at least once - // regardless of current block header TablePresent flag. - // So we can safefly use these tables below. - if ( - !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) - || !ReadTables() - || !TablesRead5 - ) - { - return; - } - } - - while (true) - { - UnpPtr &= MaxWinMask; - - if (Inp.InAddr >= ReadBorder) - { - var FileDone = false; - - // We use 'while', because for empty block containing only Huffman table, - // we'll be on the block border once again just after reading the table. - while ( - Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 - || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 - && Inp.InBit >= BlockHeader.BlockBitSize - ) - { - if (BlockHeader.LastBlockInFile) - { - FileDone = true; - break; - } - if ( - !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) - || !ReadTables() - ) - { - return; - } - } - if (FileDone || !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - if ( - ((WriteBorder - UnpPtr) & MaxWinMask) < PackDef.MAX_LZ_MATCH + 3 - && WriteBorder != UnpPtr - ) - { - UnpWriteBuf(); - if (WrittenFileSize > DestUnpSize) - { - return; - } - - if (Suspended) - { - FileExtracted = false; - return; - } - } - - //uint MainSlot=DecodeNumber(Inp,LD); - var MainSlot = this.DecodeNumber(LD); - if (MainSlot < 256) - { - // if (Fragmented) - // FragWindow[UnpPtr++]=(byte)MainSlot; - // else - Window[UnpPtr++] = (byte)MainSlot; - continue; - } - if (MainSlot >= 262) - { - var Length = SlotToLength(MainSlot - 262); - - //uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); - int DBits; - uint Distance = 1, - DistSlot = this.DecodeNumber(DD); - if (DistSlot < 4) - { - DBits = 0; - Distance += DistSlot; - } - else - { - //DBits=DistSlot/2 - 1; - DBits = (int)((DistSlot / 2) - 1); - Distance += (2 | (DistSlot & 1)) << DBits; - } - - if (DBits > 0) - { - if (DBits >= 4) - { - if (DBits > 4) - { - Distance += ((Inp.getbits() >> (36 - DBits)) << 4); - Inp.AddBits(DBits - 4); - } - //uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); - var LowDist = this.DecodeNumber(LDD); - Distance += LowDist; - } - else - { - Distance += Inp.getbits() >> (32 - DBits); - Inp.AddBits(DBits); - } - } - - if (Distance > 0x100) - { - Length++; - if (Distance > 0x2000) - { - Length++; - if (Distance > 0x40000) - { - Length++; - } - } - } - - InsertOldDist(Distance); - LastLength = Length; - // if (Fragmented) - // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); - // else - CopyString(Length, Distance); - continue; - } - if (MainSlot == 256) - { - var Filter = new UnpackFilter(); - if ( - !await ReadFilterAsync(Filter, cancellationToken).ConfigureAwait(false) - || !AddFilter(Filter) - ) - { - break; - } - - continue; - } - if (MainSlot == 257) - { - if (LastLength != 0) - // if (Fragmented) - // FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); - // else - //CopyString(LastLength,OldDist[0]); - { - CopyString(LastLength, OldDistN(0)); - } - - continue; - } - if (MainSlot < 262) - { - //uint DistNum=MainSlot-258; - var DistNum = (int)(MainSlot - 258); - //uint Distance=OldDist[DistNum]; - var Distance = OldDistN(DistNum); - //for (uint I=DistNum;I>0;I--) - for (var I = DistNum; I > 0; I--) - //OldDistN[I]=OldDistN(I-1); - { - SetOldDistN(I, OldDistN(I - 1)); - } - - //OldDistN[0]=Distance; - SetOldDistN(0, Distance); - - var LengthSlot = this.DecodeNumber(RD); - var Length = SlotToLength(LengthSlot); - LastLength = Length; - // if (Fragmented) - // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); - // else - CopyString(Length, Distance); - continue; - } - } - UnpWriteBuf(); - } - - private async System.Threading.Tasks.Task ReadBlockHeaderAsync( - System.Threading.CancellationToken cancellationToken = default - ) - { - Header.HeaderSize = 0; - - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - //Inp.faddbits((8-Inp.InBit)&7); - Inp.faddbits((uint)((8 - Inp.InBit) & 7)); - - var BlockFlags = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - //uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. - var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. - - if (ByteCount == 4) - { - return false; - } - - //Header.HeaderSize=2+ByteCount; - Header.HeaderSize = (int)(2 + ByteCount); - - Header.BlockBitSize = (BlockFlags & 7) + 1; - - var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - - var BlockSize = 0; - //for (uint I=0;I>8)<<(I*8); - BlockSize += (int)(Inp.fgetbits() >> 8) << (I * 8); - Inp.AddBits(8); - } - - Header.BlockSize = BlockSize; - var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); - if (CheckSum != SavedCheckSum) - { - return false; - } - - Header.BlockStart = Inp.InAddr; - ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); - - Header.LastBlockInFile = (BlockFlags & 0x40) != 0; - Header.TablePresent = (BlockFlags & 0x80) != 0; - return true; - } - - private async System.Threading.Tasks.Task ReadFilterAsync( - UnpackFilter Filter, - System.Threading.CancellationToken cancellationToken = default - ) - { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) - { - if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - } - - Filter.uBlockStart = ReadFilterData(); - Filter.uBlockLength = ReadFilterData(); - if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) - { - Filter.BlockLength = 0; - } - - //Filter.Type=Inp.fgetbits()>>13; - Filter.Type = (byte)(Inp.fgetbits() >> 13); - Inp.faddbits(3); - - if (Filter.Type == (byte)FilterType.FILTER_DELTA) - { - //Filter.Channels=(Inp.fgetbits()>>11)+1; - Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); - Inp.faddbits(5); - } - - return true; - } - //? // void UnpWriteBuf() // { From 6f50545c3175e03458ba2fbce3acedc15723e149 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 11 Feb 2026 16:48:37 +0000 Subject: [PATCH 14/22] more cleaning --- .../Compressors/Rar/UnpackV2017/Unpack.cs | 22 +++++++++---------- .../Rar/UnpackV2017/Unpack.unpack_cpp.cs | 6 +++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index e8b29a40..7784d98a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common.Rar.Headers; using size_t = System.UInt32; @@ -23,11 +25,11 @@ internal partial class Unpack : IRarUnpack // NOTE: caller has logic to check for -1 for error we throw instead. readStream.Read(buf, offset, count); - private async System.Threading.Tasks.Task UnpIO_UnpReadAsync( + private async Task UnpIO_UnpReadAsync( byte[] buf, int offset, int count, - System.Threading.CancellationToken cancellationToken = default + CancellationToken cancellationToken = default ) => // NOTE: caller has logic to check for -1 for error we throw instead. await readStream.ReadAsync(buf, offset, count, cancellationToken).ConfigureAwait(false); @@ -35,11 +37,11 @@ internal partial class Unpack : IRarUnpack private void UnpIO_UnpWrite(byte[] buf, size_t offset, uint count) => writeStream.Write(buf, checked((int)offset), checked((int)count)); - private async System.Threading.Tasks.Task UnpIO_UnpWriteAsync( + private async Task UnpIO_UnpWriteAsync( byte[] buf, size_t offset, uint count, - System.Threading.CancellationToken cancellationToken = default + CancellationToken cancellationToken = default ) => await writeStream .WriteAsync(buf, checked((int)offset), checked((int)count), cancellationToken) @@ -66,11 +68,11 @@ internal partial class Unpack : IRarUnpack DoUnpack(); } - public async System.Threading.Tasks.Task DoUnpackAsync( + public async Task DoUnpackAsync( FileHeader fileHeader, Stream readStream, Stream writeStream, - System.Threading.CancellationToken cancellationToken = default + CancellationToken cancellationToken = default ) { DestUnpSize = fileHeader.UncompressedSize; @@ -97,9 +99,7 @@ internal partial class Unpack : IRarUnpack } } - public async System.Threading.Tasks.Task DoUnpackAsync( - System.Threading.CancellationToken cancellationToken = default - ) + public async Task DoUnpackAsync(CancellationToken cancellationToken = default) { if (fileHeader.IsStored) { @@ -133,9 +133,7 @@ internal partial class Unpack : IRarUnpack } while (!Suspended); } - private async System.Threading.Tasks.Task UnstoreFileAsync( - System.Threading.CancellationToken cancellationToken = default - ) + private async Task UnstoreFileAsync(CancellationToken cancellationToken = default) { var buffer = new byte[(int)Math.Min(0x10000, DestUnpSize)]; do diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 0fe817a6..c518e11e 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -2,6 +2,8 @@ using System; using System.Buffers; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; @@ -196,10 +198,10 @@ internal sealed partial class Unpack : BitInput } } - private async System.Threading.Tasks.Task DoUnpackAsync( + private async Task DoUnpackAsync( uint Method, bool Solid, - System.Threading.CancellationToken cancellationToken = default + CancellationToken cancellationToken = default ) { // Methods <50 will crash in Fragmented mode when accessing NULL Window. From b2f1d007c69551edaba56c2fe1a5658e127ce282 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 08:50:18 +0000 Subject: [PATCH 15/22] Clean up some code paths --- src/SharpCompress/Archives/ArchiveFactory.cs | 3 - .../Archives/Tar/TarArchive.Factory.cs | 13 +--- .../Readers/Tar/TarReader.Factory.cs | 78 +++++++++++++++++++ src/SharpCompress/Readers/Tar/TarReader.cs | 78 ------------------- .../Tar/TarArchiveAsyncTests.cs | 2 +- .../SharpCompress.Test/Tar/TarArchiveTests.cs | 33 ++++++-- 6 files changed, 110 insertions(+), 97 deletions(-) diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 4da070b5..ea31d457 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -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.Options; using SharpCompress.Factories; -using SharpCompress.IO; using SharpCompress.Readers; namespace SharpCompress.Archives; diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 1290f97a..883169eb 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -37,12 +37,9 @@ public partial class TarArchive ) { fileInfo.NotNull(nameof(fileInfo)); - return new TarArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } - ) + return OpenArchive( + [fileInfo], + readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } ); } @@ -90,9 +87,7 @@ public partial class TarArchive throw new ArgumentException("Stream must be seekable", nameof(stream)); } - return new TarArchive( - new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) - ); + return OpenArchive([stream], readerOptions); } public static IWritableAsyncArchive OpenAsyncArchive( diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs index b8f41e60..6dc75437 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -1,5 +1,13 @@ using System.IO; +using SharpCompress.Archives.GZip; +using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Compressors; +using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.Deflate; +using SharpCompress.Compressors.LZMA; +using SharpCompress.Compressors.ZStandard; +using SharpCompress.IO; namespace SharpCompress.Readers.Tar; @@ -38,4 +46,74 @@ public partial class TarReader fileInfo.NotNull(nameof(fileInfo)); return OpenReader(fileInfo.OpenRead(), readerOptions); } + + /// + /// Opens a TarReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) + { + stream.NotNull(nameof(stream)); + options ??= new ReaderOptions(); + var sharpCompressStream = SharpCompressStream.Create( + stream, + bufferSize: options.RewindableBufferSize + ); + long pos = sharpCompressStream.Position; + if (GZipArchive.IsGZipFile(sharpCompressStream)) + { + sharpCompressStream.Position = pos; + var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.GZip); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (BZip2Stream.IsBZip2(sharpCompressStream)) + { + sharpCompressStream.Position = pos; + var testStream = BZip2Stream.Create( + sharpCompressStream, + CompressionMode.Decompress, + false + ); + if (TarArchive.IsTarFile(testStream)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.BZip2); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (ZStandardStream.IsZStandard(sharpCompressStream)) + { + sharpCompressStream.Position = pos; + var testStream = new ZStandardStream(sharpCompressStream); + if (TarArchive.IsTarFile(testStream)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.ZStandard); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (LZipStream.IsLZipFile(sharpCompressStream)) + { + sharpCompressStream.Position = pos; + var testStream = new LZipStream(sharpCompressStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.LZip); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.None); + } } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index 2c561c15..ce7c822b 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using SharpCompress.Archives.GZip; -using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Common.Tar; using SharpCompress.Compressors; @@ -45,80 +43,6 @@ public partial class TarReader : AbstractReader }; } - #region OpenReader - - /// - /// Opens a TarReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static IReader OpenReader(Stream stream, ReaderOptions? options = null) - { - stream.NotNull(nameof(stream)); - options = options ?? new ReaderOptions(); - var sharpCompressStream = SharpCompressStream.Create( - stream, - bufferSize: options.RewindableBufferSize - ); - long pos = sharpCompressStream.Position; - if (GZipArchive.IsGZipFile(sharpCompressStream)) - { - sharpCompressStream.Position = pos; - var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - sharpCompressStream.Position = pos; - return new TarReader(sharpCompressStream, options, CompressionType.GZip); - } - throw new InvalidFormatException("Not a tar file."); - } - sharpCompressStream.Position = pos; - if (BZip2Stream.IsBZip2(sharpCompressStream)) - { - sharpCompressStream.Position = pos; - var testStream = BZip2Stream.Create( - sharpCompressStream, - CompressionMode.Decompress, - false - ); - if (TarArchive.IsTarFile(testStream)) - { - sharpCompressStream.Position = pos; - return new TarReader(sharpCompressStream, options, CompressionType.BZip2); - } - throw new InvalidFormatException("Not a tar file."); - } - sharpCompressStream.Position = pos; - if (ZStandardStream.IsZStandard(sharpCompressStream)) - { - sharpCompressStream.Position = pos; - var testStream = new ZStandardStream(sharpCompressStream); - if (TarArchive.IsTarFile(testStream)) - { - sharpCompressStream.Position = pos; - return new TarReader(sharpCompressStream, options, CompressionType.ZStandard); - } - throw new InvalidFormatException("Not a tar file."); - } - sharpCompressStream.Position = pos; - if (LZipStream.IsLZipFile(sharpCompressStream)) - { - sharpCompressStream.Position = pos; - var testStream = new LZipStream(sharpCompressStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - sharpCompressStream.Position = pos; - return new TarReader(sharpCompressStream, options, CompressionType.LZip); - } - throw new InvalidFormatException("Not a tar file."); - } - sharpCompressStream.Position = pos; - return new TarReader(sharpCompressStream, options, CompressionType.None); - } - - #endregion OpenReader - protected override IEnumerable GetEntries(Stream stream) => TarEntry.GetEntries( StreamingMode.Streaming, @@ -127,6 +51,4 @@ public partial class TarReader : AbstractReader Options.ArchiveEncoding, Options ); - - // GetEntriesAsync moved to TarReader.Async.cs } diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index 39df3cc7..a27431d2 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -253,7 +253,7 @@ public class TarArchiveAsyncTests : ArchiveTests var numberOfEntries = 0; await using ( - var archiveFactory = TarArchive.OpenAsyncArchive(new AsyncOnlyStream(memoryStream)) + var archiveFactory = await ArchiveFactory.OpenAsyncArchive(new AsyncOnlyStream(memoryStream)) ) { await foreach (var entry in archiveFactory.EntriesAsync) diff --git a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs index 058806d1..4d607935 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -7,6 +7,7 @@ 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; using Xunit; @@ -23,6 +24,26 @@ public class TarArchiveTests : ArchiveTests [Fact] public void TarArchivePathRead() => ArchiveFileRead("Tar.tar"); + [Fact] + public void TarArchiveStreamRead_Autodetect_CompressedTar() + { + 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); + } + + [Fact] + public void TarArchiveStreamRead_Throws_On_NonSeekable_Stream() + { + using Stream stream = new ForwardOnlyStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")) + ); + + Assert.Throws(() => ArchiveFactory.OpenArchive(stream)); + } + [Fact] public void Tar_FileName_Exactly_100_Characters() { @@ -53,7 +74,7 @@ public class TarArchiveTests : ArchiveTests // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.OpenArchive(unmodified)) + using (var archive2 = ArchiveFactory.OpenArchive(unmodified)) { Assert.Equal(1, archive2.Entries.Count()); Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key)); @@ -72,7 +93,7 @@ public class TarArchiveTests : ArchiveTests public void Tar_NonUstarArchiveWithLongNameDoesNotSkipEntriesAfterTheLongOne() { var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "very long filename.tar"); - using var archive = TarArchive.OpenArchive(unmodified); + using var archive = ArchiveFactory.OpenArchive(unmodified); Assert.Equal(5, archive.Entries.Count()); Assert.Contains("very long filename/", archive.Entries.Select(entry => entry.Key)); Assert.Contains( @@ -119,7 +140,7 @@ public class TarArchiveTests : ArchiveTests // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.OpenArchive(unmodified)) + using (var archive2 = ArchiveFactory.OpenArchive(unmodified)) { Assert.Equal(1, archive2.Entries.Count()); Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key)); @@ -138,7 +159,7 @@ public class TarArchiveTests : ArchiveTests public void Tar_UstarArchivePathReadLongName() { var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "ustar with long names.tar"); - using var archive = TarArchive.OpenArchive(unmodified); + using var archive = ArchiveFactory.OpenArchive(unmodified); Assert.Equal(6, archive.Entries.Count()); Assert.Contains("Directory/", archive.Entries.Select(entry => entry.Key)); Assert.Contains( @@ -285,9 +306,9 @@ public class TarArchiveTests : ArchiveTests var numberOfEntries = 0; - using (var archiveFactory = TarArchive.OpenArchive(memoryStream)) + using (var archive = ArchiveFactory.OpenArchive(memoryStream)) { - foreach (var entry in archiveFactory.Entries) + foreach (var entry in archive.Entries) { ++numberOfEntries; From bae660381c35a33e2ec614c009a7411648789e0f Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 09:48:06 +0000 Subject: [PATCH 16/22] TarArchive should use a compression method like TarReader --- .../Archives/Tar/TarArchive.Factory.cs | 27 ++++++------ src/SharpCompress/Archives/Tar/TarArchive.cs | 42 +++++++++++++++---- src/SharpCompress/Factories/TarFactory.cs | 19 +++++++++ src/SharpCompress/Readers/ReaderFactory.cs | 2 - .../Tar/TarArchiveAsyncTests.cs | 4 +- .../SharpCompress.Test/Tar/TarArchiveTests.cs | 30 ++++++++----- 6 files changed, 90 insertions(+), 34 deletions(-) diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 883169eb..0c851bc1 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -7,6 +7,7 @@ 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; @@ -50,13 +51,14 @@ public partial class TarArchive { fileInfos.NotNull(nameof(fileInfos)); var files = fileInfos.ToArray(); - return new TarArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } - ) + var sourceStream = new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } ); + var compressionType = TarFactory.GetCompressionType(sourceStream); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); } public static IWritableArchive OpenArchive( @@ -66,13 +68,14 @@ public partial class TarArchive { streams.NotNull(nameof(streams)); var strms = streams.ToArray(); - return new TarArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) + var sourceStream = new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() ); + var compressionType = TarFactory.GetCompressionType(sourceStream); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); } public static IWritableArchive OpenArchive( diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index 70dc93d2..130bb47f 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -2,38 +2,60 @@ 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.Options; using SharpCompress.Common.Tar; using SharpCompress.Common.Tar.Headers; +using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.Deflate; +using SharpCompress.Compressors.LZMA; +using SharpCompress.Compressors.Lzw; +using SharpCompress.Compressors.Xz; +using SharpCompress.Compressors.ZStandard; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Tar; -using SharpCompress.Writers; using SharpCompress.Writers.Tar; +using CompressionMode = SharpCompress.Compressors.CompressionMode; +using Constants = SharpCompress.Common.Constants; namespace SharpCompress.Archives.Tar; public partial class TarArchive : AbstractWritableArchive { + private readonly CompressionType _compressionType; + protected override IEnumerable LoadVolumes(SourceStream sourceStream) { sourceStream.NotNull("SourceStream is null").LoadAllParts(); return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); } - private TarArchive(SourceStream sourceStream) - : base(ArchiveType.Tar, sourceStream) { } + internal TarArchive(SourceStream sourceStream, CompressionType compressionType) + : base(ArchiveType.Tar, sourceStream) + { + _compressionType = compressionType; + } private TarArchive() : base(ArchiveType.Tar) { } + private Stream GetStream(Stream stream) => + _compressionType switch + { + CompressionType.BZip2 => BZip2Stream.Create(stream, CompressionMode.Decompress, false), + CompressionType.GZip => new GZipStream(stream, CompressionMode.Decompress), + CompressionType.ZStandard => new ZStandardStream(stream), + CompressionType.LZip => new LZipStream(stream, CompressionMode.Decompress), + CompressionType.Xz => new XZStream(stream), + CompressionType.Lzw => new LzwStream(stream), + CompressionType.None => stream, + _ => throw new NotSupportedException("Invalid compression type: " + _compressionType), + }; + protected override IEnumerable LoadEntries(IEnumerable volumes) { - var stream = volumes.Single().Stream; + var stream = GetStream(volumes.Single().Stream); if (stream.CanSeek) { stream.Position = 0; @@ -41,7 +63,9 @@ public partial class TarArchive TarHeader? previousHeader = null; foreach ( var header in TarHeaderFactory.ReadHeader( - StreamingMode.Seekable, + _compressionType == CompressionType.None + ? StreamingMode.Seekable + : StreamingMode.Streaming, stream, ReaderOptions.ArchiveEncoding ) @@ -154,6 +178,6 @@ public partial class TarArchive { var stream = Volumes.Single().Stream; stream.Position = 0; - return TarReader.OpenReader(stream); + return TarReader.OpenReader(GetStream(stream)); } } diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index abc800e1..55d3009b 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -109,6 +109,25 @@ public class TarFactory #endregion + public static CompressionType GetCompressionType(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + foreach (var wrapper in TarWrapper.Wrappers) + { + stream.Seek(0, SeekOrigin.Begin); + if (wrapper.IsMatch(stream)) + { + stream.Seek(0, SeekOrigin.Begin); + var decompressedStream = wrapper.CreateStream(stream); + if (TarArchive.IsTarFile(decompressedStream)) + { + return wrapper.CompressionType; + } + } + } + throw new InvalidFormatException("Not a tar file."); + } + #region IArchiveFactory /// diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 0a65c5cf..6a729b55 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -1,8 +1,6 @@ using System; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Factories; using SharpCompress.IO; diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index a27431d2..4dde878f 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -253,7 +253,9 @@ public class TarArchiveAsyncTests : ArchiveTests var numberOfEntries = 0; await using ( - var archiveFactory = await ArchiveFactory.OpenAsyncArchive(new AsyncOnlyStream(memoryStream)) + var archiveFactory = await ArchiveFactory.OpenAsyncArchive( + new AsyncOnlyStream(memoryStream) + ) ) { await foreach (var entry in archiveFactory.EntriesAsync) diff --git a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs index 4d607935..488e3914 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -24,16 +24,6 @@ public class TarArchiveTests : ArchiveTests [Fact] public void TarArchivePathRead() => ArchiveFileRead("Tar.tar"); - [Fact] - public void TarArchiveStreamRead_Autodetect_CompressedTar() - { - 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); - } - [Fact] public void TarArchiveStreamRead_Throws_On_NonSeekable_Stream() { @@ -329,4 +319,24 @@ public class TarArchiveTests : ArchiveTests Assert.False(isTar); } + + [Fact] + public void TarArchiveStreamRead_Autodetect_CompressedTar() + { + 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); + } + + [Fact] + public void TarReaderStreamRead_Autodetect_CompressedTar() + { + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + using var reader = ReaderFactory.OpenReader(stream); + + Assert.Equal(ArchiveType.Tar, reader.ArchiveType); + Assert.True(reader.MoveToNextEntry()); + } } From 5a319ffe2c51779aceaab25a22b84764213e11de Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 10:18:43 +0000 Subject: [PATCH 17/22] create/open always has to be async for detection --- .../Archives/GZip/GZipArchive.Factory.cs | 57 ++++++---- .../Archives/IArchiveOpenable.cs | 16 +-- .../Archives/IMultiArchiveOpenable.cs | 11 +- .../Archives/IWritableArchiveOpenable.cs | 3 +- .../Archives/Rar/RarArchive.Factory.cs | 40 ++++--- .../SevenZip/SevenZipArchive.Factory.cs | 44 +++++--- .../Archives/Tar/TarArchive.Factory.cs | 101 ++++++++++++++--- .../Archives/Zip/ZipArchive.Factory.cs | 58 +++++++--- src/SharpCompress/Factories/LzwFactory.cs | 2 +- .../Factories/SevenZipFactory.cs | 8 +- src/SharpCompress/Factories/TarFactory.cs | 22 ++++ .../Readers/Ace/AceReader.Factory.cs | 28 +++-- .../Readers/Arc/ArcReader.Factory.cs | 44 +++++--- .../Readers/Arj/ArjReader.Factory.cs | 44 +++++--- .../Readers/GZip/GZipReader.Factory.cs | 44 +++++--- src/SharpCompress/Readers/IReaderOpenable.cs | 16 +-- .../Readers/Lzw/LzwReader.Factory.cs | 44 +++++--- .../Readers/Rar/RarReader.Factory.cs | 44 +++++--- .../Readers/Tar/TarReader.Async.cs | 10 -- .../Readers/Tar/TarReader.Factory.cs | 102 +++++++++++++++--- .../Readers/Zip/ZipReader.Factory.cs | 44 +++++--- tests/SharpCompress.Test/GZip/AsyncTests.cs | 4 +- .../GZip/GZipArchiveAsyncTests.cs | 16 +-- .../GZip/GZipReaderAsyncTests.cs | 2 +- .../Rar/RarArchiveAsyncTests.cs | 4 +- tests/SharpCompress.Test/ReaderTests.cs | 3 +- .../SevenZip/SevenZipArchiveAsyncTests.cs | 4 +- .../Tar/TarArchiveAsyncTests.cs | 10 +- .../Tar/TarReaderAsyncTests.cs | 4 +- .../Zip/ZipArchiveAsyncTests.cs | 12 +-- 30 files changed, 592 insertions(+), 249 deletions(-) diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs index 1f90de62..6fe3fe60 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs @@ -20,14 +20,15 @@ public partial class GZipArchive > #endif { - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( string path, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { + cancellationToken.ThrowIfCancellationRequested(); path.NotNullOrEmpty(nameof(path)); - return (IWritableAsyncArchive) - OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions()); + return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken); } public static IWritableArchive OpenArchive( @@ -103,30 +104,50 @@ public partial class GZipArchive ); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( Stream stream, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(stream, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(streams, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions)); + } public static IWritableArchive CreateArchive() => new GZipArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => - new GZipArchive(); + public static ValueTask> CreateAsyncArchive() => + new(new GZipArchive()); public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/IArchiveOpenable.cs b/src/SharpCompress/Archives/IArchiveOpenable.cs index e36ea02b..5539e16e 100644 --- a/src/SharpCompress/Archives/IArchiveOpenable.cs +++ b/src/SharpCompress/Archives/IArchiveOpenable.cs @@ -1,6 +1,7 @@ #if NET8_0_OR_GREATER using System.IO; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Readers; namespace SharpCompress.Archives; @@ -18,19 +19,22 @@ public interface IArchiveOpenable public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null); - public static abstract TASync OpenAsyncArchive( + public static abstract ValueTask OpenAsyncArchive( string path, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); - public static abstract TASync OpenAsyncArchive( + public static abstract ValueTask OpenAsyncArchive( Stream stream, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); - public static abstract TASync OpenAsyncArchive( + public static abstract ValueTask OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); } diff --git a/src/SharpCompress/Archives/IMultiArchiveOpenable.cs b/src/SharpCompress/Archives/IMultiArchiveOpenable.cs index 0fed7adb..6a927297 100644 --- a/src/SharpCompress/Archives/IMultiArchiveOpenable.cs +++ b/src/SharpCompress/Archives/IMultiArchiveOpenable.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Readers; namespace SharpCompress.Archives; @@ -20,14 +21,16 @@ public interface IMultiArchiveOpenable ReaderOptions? readerOptions = null ); - public static abstract TASync OpenAsyncArchive( + public static abstract ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); - public static abstract TASync OpenAsyncArchive( + public static abstract ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); } #endif diff --git a/src/SharpCompress/Archives/IWritableArchiveOpenable.cs b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs index f523e1d2..2e5c6855 100644 --- a/src/SharpCompress/Archives/IWritableArchiveOpenable.cs +++ b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using SharpCompress.Common.Options; #if NET8_0_OR_GREATER @@ -8,6 +9,6 @@ public interface IWritableArchiveOpenable where TOptions : IWriterOptions { public static abstract IWritableArchive CreateArchive(); - public static abstract IWritableAsyncArchive CreateAsyncArchive(); + public static abstract ValueTask> CreateAsyncArchive(); } #endif diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs index 76154b7f..edf74590 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs @@ -20,13 +20,15 @@ public partial class RarArchive IMultiArchiveOpenable #endif { - public static IRarAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( string path, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { + cancellationToken.ThrowIfCancellationRequested(); path.NotNullOrEmpty(nameof(path)); - return (IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions); + return new((IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions)); } public static IRarArchive OpenArchive(string filePath, ReaderOptions? options = null) @@ -98,36 +100,44 @@ public partial class RarArchive ); } - public static IRarAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( Stream stream, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IRarAsyncArchive)OpenArchive(stream, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IRarAsyncArchive)OpenArchive(stream, readerOptions)); } - public static IRarAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IRarAsyncArchive)OpenArchive(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IRarAsyncArchive)OpenArchive(fileInfo, readerOptions)); } - public static IRarAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IRarAsyncArchive)OpenArchive(streams, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IRarAsyncArchive)OpenArchive(streams, readerOptions)); } - public static IRarAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IRarAsyncArchive)OpenArchive(fileInfos, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IRarAsyncArchive)OpenArchive(fileInfos, readerOptions)); } public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs index 76f177fc..db06ec9a 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs @@ -16,10 +16,17 @@ public partial class SevenZipArchive IMultiArchiveOpenable #endif { - public static IAsyncArchive OpenAsyncArchive(string path, ReaderOptions? readerOptions = null) + public static ValueTask OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); path.NotNullOrEmpty("path"); - return (IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions()); + return new( + (IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions()) + ); } public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null) @@ -86,33 +93,44 @@ public partial class SevenZipArchive ); } - public static IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) + public static ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) { - return (IAsyncArchive)OpenArchive(stream, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(stream, readerOptions)); } - public static IAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions)); } - public static IAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncArchive)OpenArchive(streams, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(streams, readerOptions)); } - public static IAsyncArchive OpenAsyncArchive( + public static ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfos, readerOptions)); } public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 0c851bc1..0c0e4d50 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -93,30 +93,98 @@ public partial class TarArchive return OpenArchive([stream], readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static async ValueTask> OpenAsyncArchive( Stream stream, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + stream.NotNull(nameof(stream)); + var sourceStream = new SourceStream( + stream, + i => null, + readerOptions ?? new ReaderOptions() + ); + var compressionType = await TarFactory.GetCompressionTypeAsync( + sourceStream, + cancellationToken + ); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( string path, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(new FileInfo(path), readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static async ValueTask> OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + fileInfo.NotNull(nameof(fileInfo)); + readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false }; + var sourceStream = new SourceStream(fileInfo, i => null, readerOptions); + var compressionType = await TarFactory.GetCompressionTypeAsync( + sourceStream, + cancellationToken + ); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static async ValueTask> OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + var sourceStream = new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ); + var compressionType = await TarFactory.GetCompressionTypeAsync( + sourceStream, + cancellationToken + ); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static async ValueTask> OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + var sourceStream = new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } + ); + var compressionType = await TarFactory.GetCompressionTypeAsync( + sourceStream, + cancellationToken + ); + sourceStream.Seek(0, SeekOrigin.Begin); + return new TarArchive(sourceStream, compressionType); + } public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); @@ -181,5 +249,6 @@ public partial class TarArchive public static IWritableArchive CreateArchive() => new TarArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => new TarArchive(); + public static ValueTask> CreateAsyncArchive() => + new(new TarArchive()); } diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs index fb9778a9..2635f42f 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -95,30 +95,55 @@ public partial class ZipArchive ); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( string path, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(path, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(path, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( Stream stream, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(stream, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( FileInfo fileInfo, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(streams, readerOptions)); + } - public static IWritableAsyncArchive OpenAsyncArchive( + public static ValueTask> OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions)); + } public static bool IsZipFile(string filePath, string? password = null) => IsZipFile(new FileInfo(filePath), password); @@ -223,7 +248,8 @@ public partial class ZipArchive public static IWritableArchive CreateArchive() => new ZipArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => new ZipArchive(); + public static ValueTask> CreateAsyncArchive() => + new(new ZipArchive()); public static async ValueTask IsZipMultiAsync( Stream stream, diff --git a/src/SharpCompress/Factories/LzwFactory.cs b/src/SharpCompress/Factories/LzwFactory.cs index 078fad37..7a0eeec0 100644 --- a/src/SharpCompress/Factories/LzwFactory.cs +++ b/src/SharpCompress/Factories/LzwFactory.cs @@ -87,7 +87,7 @@ public class LzwFactory : Factory, IReaderFactory ) { cancellationToken.ThrowIfCancellationRequested(); - return new(LzwReader.OpenAsyncReader(stream, options)); + return LzwReader.OpenAsyncReader(stream, options); } #endregion diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index 0a276223..a75b8448 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -50,7 +50,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory /// public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - SevenZipArchive.OpenAsyncArchive(stream, readerOptions); + (IAsyncArchive)OpenArchive(stream, readerOptions); /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => @@ -58,7 +58,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory /// public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - SevenZipArchive.OpenAsyncArchive(fileInfo, readerOptions); + (IAsyncArchive)OpenArchive(fileInfo, readerOptions); #endregion @@ -74,7 +74,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, ReaderOptions? readerOptions = null - ) => SevenZipArchive.OpenAsyncArchive(streams, readerOptions); + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// public IArchive OpenArchive( @@ -86,7 +86,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null - ) => SevenZipArchive.OpenAsyncArchive(fileInfos, readerOptions); + ) => (IAsyncArchive)OpenArchive(fileInfos, readerOptions); #endregion diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 55d3009b..f47e6de8 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -128,6 +128,28 @@ public class TarFactory throw new InvalidFormatException("Not a tar file."); } + public static async ValueTask GetCompressionTypeAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + stream.Seek(0, SeekOrigin.Begin); + foreach (var wrapper in TarWrapper.Wrappers) + { + stream.Seek(0, SeekOrigin.Begin); + if (wrapper.IsMatch(stream)) + { + stream.Seek(0, SeekOrigin.Begin); + var decompressedStream = wrapper.CreateStream(stream); + if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken)) + { + return wrapper.CompressionType; + } + } + } + throw new InvalidFormatException("Not a tar file."); + } + #region IArchiveFactory /// diff --git a/src/SharpCompress/Readers/Ace/AceReader.Factory.cs b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs index 9f873d77..a9e3e876 100644 --- a/src/SharpCompress/Readers/Ace/AceReader.Factory.cs +++ b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Readers.Ace; @@ -33,15 +35,25 @@ public partial class AceReader return new MultiVolumeAceReader(streams, options ?? new ReaderOptions()); } - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) { + cancellationToken.ThrowIfCancellationRequested(); path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); } - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) { - return (IAsyncReader)OpenReader(stream, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); } public static IAsyncReader OpenAsyncReader( @@ -53,12 +65,14 @@ public partial class AceReader return new MultiVolumeAceReader(streams, options ?? new ReaderOptions()); } - public static IAsyncReader OpenAsyncReader( + public static ValueTask OpenAsyncReader( FileInfo fileInfo, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs b/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs index abfad14a..0ca8478c 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs @@ -1,28 +1,42 @@ #if NET8_0_OR_GREATER using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Readers.Arc; public partial class ArcReader : IReaderOpenable { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs b/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs index 7a84f4c2..6072045d 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs @@ -1,28 +1,42 @@ #if NET8_0_OR_GREATER using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Readers.Arj; public partial class ArjReader : IReaderOpenable { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs index 55e96e73..5f1542e9 100644 --- a/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs +++ b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace SharpCompress.Readers.GZip; @@ -7,23 +9,35 @@ public partial class GZipReader : IReaderOpenable #endif { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/src/SharpCompress/Readers/IReaderOpenable.cs b/src/SharpCompress/Readers/IReaderOpenable.cs index ea42b827..32c7bdf3 100644 --- a/src/SharpCompress/Readers/IReaderOpenable.cs +++ b/src/SharpCompress/Readers/IReaderOpenable.cs @@ -1,6 +1,7 @@ #if NET8_0_OR_GREATER using System.IO; using System.Threading; +using System.Threading.Tasks; namespace SharpCompress.Readers; @@ -15,19 +16,22 @@ public interface IReaderOpenable public static abstract IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null); - public static abstract IAsyncReader OpenAsyncReader( + public static abstract ValueTask OpenAsyncReader( string path, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); - public static abstract IAsyncReader OpenAsyncReader( + public static abstract ValueTask OpenAsyncReader( Stream stream, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); - public static abstract IAsyncReader OpenAsyncReader( + public static abstract ValueTask OpenAsyncReader( FileInfo fileInfo, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); } #endif diff --git a/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs b/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs index 344a9572..008562c6 100644 --- a/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs +++ b/src/SharpCompress/Readers/Lzw/LzwReader.Factory.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace SharpCompress.Readers.Lzw; @@ -7,23 +9,35 @@ public partial class LzwReader : IReaderOpenable #endif { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/src/SharpCompress/Readers/Rar/RarReader.Factory.cs b/src/SharpCompress/Readers/Rar/RarReader.Factory.cs index 5fa1cba0..b18fd3c7 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.Factory.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.Factory.cs @@ -1,28 +1,42 @@ #if NET8_0_OR_GREATER using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Readers.Rar; public partial class RarReader : IReaderOpenable { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } } #endif diff --git a/src/SharpCompress/Readers/Tar/TarReader.Async.cs b/src/SharpCompress/Readers/Tar/TarReader.Async.cs index 6684bdab..45a9c85b 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Async.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Async.cs @@ -1,16 +1,6 @@ using System.Collections.Generic; using System.IO; -using SharpCompress.Archives.GZip; -using SharpCompress.Archives.Tar; -using SharpCompress.Common; using SharpCompress.Common.Tar; -using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; -using SharpCompress.Compressors.Deflate; -using SharpCompress.Compressors.LZMA; -using SharpCompress.Compressors.Lzw; -using SharpCompress.Compressors.Xz; -using SharpCompress.Compressors.ZStandard; using SharpCompress.IO; namespace SharpCompress.Readers.Tar; diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs index 6dc75437..8f506e7d 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Archives.GZip; using SharpCompress.Archives.Tar; using SharpCompress.Common; @@ -16,23 +18,92 @@ public partial class TarReader : IReaderOpenable #endif { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + path.NotNullOrEmpty(nameof(path)); + return OpenAsyncReader(new FileInfo(path), readerOptions, cancellationToken); + } + + public static async ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + stream.NotNull(nameof(stream)); + options ??= new ReaderOptions(); + var sharpCompressStream = SharpCompressStream.Create( + stream, + bufferSize: options.RewindableBufferSize + ); + long pos = sharpCompressStream.Position; + if (await GZipArchive.IsGZipFileAsync(sharpCompressStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); + if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.GZip); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (await BZip2Stream.IsBZip2Async(sharpCompressStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + var testStream = BZip2Stream.Create( + sharpCompressStream, + CompressionMode.Decompress, + false + ); + if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.BZip2); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (await ZStandardStream.IsZStandardAsync(sharpCompressStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + var testStream = new ZStandardStream(sharpCompressStream); + if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.ZStandard); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + if (await LZipStream.IsLZipFileAsync(sharpCompressStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + var testStream = new LZipStream(sharpCompressStream, CompressionMode.Decompress); + if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + { + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.LZip); + } + throw new InvalidFormatException("Not a tar file."); + } + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.None); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false }; + return OpenAsyncReader(fileInfo.OpenRead(), readerOptions, cancellationToken); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) @@ -44,6 +115,7 @@ public partial class TarReader public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) { fileInfo.NotNull(nameof(fileInfo)); + readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false }; return OpenReader(fileInfo.OpenRead(), readerOptions); } diff --git a/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs b/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs index f03ea4ab..65f179cf 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs @@ -1,28 +1,42 @@ #if NET8_0_OR_GREATER using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Readers.Zip; public partial class ZipReader : IReaderOpenable { - public static IAsyncReader OpenAsyncReader(string path, ReaderOptions? readerOptions = null) - { - path.NotNullOrEmpty(nameof(path)); - return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); - } - - public static IAsyncReader OpenAsyncReader(Stream stream, ReaderOptions? readerOptions = null) - { - return (IAsyncReader)OpenReader(stream, readerOptions); - } - - public static IAsyncReader OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? readerOptions = null + public static ValueTask OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncReader)OpenReader(fileInfo, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions)); + } + + public static ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(stream, readerOptions)); + } + + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)OpenReader(fileInfo, readerOptions)); } public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) diff --git a/tests/SharpCompress.Test/GZip/AsyncTests.cs b/tests/SharpCompress.Test/GZip/AsyncTests.cs index b525a15b..8c0d19e1 100644 --- a/tests/SharpCompress.Test/GZip/AsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/AsyncTests.cs @@ -72,7 +72,7 @@ public class AsyncTests : TestBase public async ValueTask Archive_Entry_Async_Open_Stream() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"); - await using var archive = GZipArchive.OpenAsyncArchive( + await using var archive = await GZipArchive.OpenAsyncArchive( new AsyncOnlyStream(File.OpenRead(testArchive)) ); @@ -123,7 +123,7 @@ public class AsyncTests : TestBase // Verify the archive was created and contains the entry Assert.True(File.Exists(outputPath)); - await using var archive = ZipArchive.OpenAsyncArchive(outputPath); + await using var archive = await ZipArchive.OpenAsyncArchive(outputPath); Assert.Single(await archive.EntriesAsync.Where(e => !e.IsDirectory).ToListAsync()); } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs index 3bcfb910..83043131 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs @@ -24,7 +24,7 @@ public class GZipArchiveAsyncTests : ArchiveTests #else await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) #endif - await using (var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) + await using (var archive = await GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) { var entry = await archive.EntriesAsync.FirstAsync(); await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -51,7 +51,9 @@ public class GZipArchiveAsyncTests : ArchiveTests await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) #endif { - await using (var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) + await using ( + var archive = await GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)) + ) { var entry = await archive.EntriesAsync.FirstAsync(); await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -79,7 +81,7 @@ public class GZipArchiveAsyncTests : ArchiveTests #else await using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); #endif - await using (var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) + await using (var archive = await GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) { await Assert.ThrowsAsync(async () => await archive.AddEntryAsync("jpg\\test.jpg", File.OpenRead(jpg), closeStream: true) @@ -105,7 +107,9 @@ public class GZipArchiveAsyncTests : ArchiveTests inputStream.Position = 0; } - await using var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(inputStream)); + await using var archive = await GZipArchive.OpenAsyncArchive( + new AsyncOnlyStream(inputStream) + ); var archiveEntry = await archive.EntriesAsync.FirstAsync(); MemoryStream tarStream; @@ -159,7 +163,7 @@ public class GZipArchiveAsyncTests : ArchiveTests #else await using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); #endif - await using var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + await using var archive = await GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { Assert.InRange(entry.Crc, 0L, 0xFFFFFFFFL); @@ -174,7 +178,7 @@ public class GZipArchiveAsyncTests : ArchiveTests #else await using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); #endif - await using var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + await using var archive = await GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); Assert.Equal(archive.Type, ArchiveType.GZip); } } diff --git a/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs index 8c172863..83d2b352 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs @@ -22,7 +22,7 @@ public class GZipReaderAsyncTests : ReaderTests { //read only as GZip item using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - await using var reader = GZipReader.OpenAsyncReader(new AsyncOnlyStream(stream)); + await using var reader = await GZipReader.OpenAsyncReader(new AsyncOnlyStream(stream)); while (await reader.MoveToNextEntryAsync()) { Assert.NotEqual(0, reader.Entry.Size); diff --git a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs index 95fef9a8..bed63133 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs @@ -69,7 +69,7 @@ public class RarArchiveAsyncTests : ArchiveTests { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) await using ( - var archive = RarArchive.OpenAsyncArchive( + var archive = await RarArchive.OpenAsyncArchive( stream, new ReaderOptions { Password = password, LeaveStreamOpen = true } ) @@ -691,7 +691,7 @@ public class RarArchiveAsyncTests : ArchiveTests { var testFile = "Rar.issue1050.rar"; using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile)); - await using var archive = RarArchive.OpenAsyncArchive(fileStream); + await using var archive = await RarArchive.OpenAsyncArchive(fileStream); // Extract using archive.WriteToDirectoryAsync without explicit options await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index 2cc7d86c..6e5b4b9e 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -178,7 +178,8 @@ public abstract class ReaderTests : TestBase await using ( var reader = await ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(testStream), - options + options, + cancellationToken ) ) { diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index 0cde6052..228b0111 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -233,7 +233,7 @@ public class SevenZipArchiveAsyncTests : ArchiveTests // This test verifies that solid archives iterate entries as contiguous streams // rather than recreating the decompression stream for each entry var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); - await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + await using var archive = await SevenZipArchive.OpenAsyncArchive(testArchive); Assert.True(((SevenZipArchive)archive).IsSolid); await using var reader = await archive.ExtractAllEntriesAsync(); @@ -254,7 +254,7 @@ public class SevenZipArchiveAsyncTests : ArchiveTests // This test verifies that the folder stream is reused within each folder // and not recreated for each entry in solid archives var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); - await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + await using var archive = await SevenZipArchive.OpenAsyncArchive(testArchive); Assert.True(((SevenZipArchive)archive).IsSolid); await using var reader = await archive.ExtractAllEntriesAsync(); diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index 4dde878f..194a07d8 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -55,7 +55,7 @@ public class TarArchiveAsyncTests : ArchiveTests // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); await using ( - var archive2 = TarArchive.OpenAsyncArchive( + var archive2 = await TarArchive.OpenAsyncArchive( new AsyncOnlyStream(File.OpenRead(unmodified)), new ReaderOptions() { LeaveStreamOpen = false } ) @@ -113,7 +113,7 @@ public class TarArchiveAsyncTests : ArchiveTests // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); await using ( - var archive2 = TarArchive.OpenAsyncArchive( + var archive2 = await TarArchive.OpenAsyncArchive( new AsyncOnlyStream(File.OpenRead(unmodified)), new ReaderOptions() { LeaveStreamOpen = false } ) @@ -145,7 +145,7 @@ public class TarArchiveAsyncTests : ArchiveTests var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - await using (var archive = TarArchive.CreateAsyncArchive()) + await using (var archive = await TarArchive.CreateAsyncArchive()) { await archive.AddAllFromDirectoryAsync(ORIGINAL_FILES_PATH); var twopt = new TarWriterOptions(CompressionType.None, true) @@ -165,7 +165,7 @@ public class TarArchiveAsyncTests : ArchiveTests var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - await using (var archive = TarArchive.OpenAsyncArchive(unmodified)) + await using (var archive = await TarArchive.OpenAsyncArchive(unmodified)) { await archive.AddEntryAsync("jpg\\test.jpg", jpg); await archive.SaveToAsync( @@ -183,7 +183,7 @@ public class TarArchiveAsyncTests : ArchiveTests var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - await using (var archive = TarArchive.OpenAsyncArchive(unmodified)) + await using (var archive = await TarArchive.OpenAsyncArchive(unmodified)) { var entry = await archive.EntriesAsync.SingleAsync(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs index 87196754..5aad3e8c 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs @@ -74,7 +74,7 @@ public class TarReaderAsyncTests : ReaderTests public async ValueTask Tar_BZip2_Entry_Stream_Async() { using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2")); - await using var reader = TarReader.OpenAsyncReader(stream); + await using var reader = await TarReader.OpenAsyncReader(stream); while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -135,7 +135,7 @@ public class TarReaderAsyncTests : ReaderTests public async ValueTask Tar_BZip2_Skip_Entry_Stream_Async() { using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2")); - await using var reader = TarReader.OpenAsyncReader(stream); + await using var reader = await TarReader.OpenAsyncReader(stream); var names = new List(); while (await reader.MoveToNextEntryAsync()) { diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs index 5894d065..c40365d2 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs @@ -126,7 +126,7 @@ public class ZipArchiveAsyncTests : ArchiveTests var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - await using (var archive = ZipArchive.OpenAsyncArchive(unmodified)) + await using (var archive = await ZipArchive.OpenAsyncArchive(unmodified)) { var entry = await archive.EntriesAsync.SingleAsync(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) @@ -151,7 +151,7 @@ public class ZipArchiveAsyncTests : ArchiveTests var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - await using (var archive = ZipArchive.OpenAsyncArchive(unmodified)) + await using (var archive = await ZipArchive.OpenAsyncArchive(unmodified)) { await archive.AddEntryAsync("jpg\\test.jpg", jpg); @@ -171,7 +171,7 @@ public class ZipArchiveAsyncTests : ArchiveTests var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - await using (var archive = (ZipArchive)ZipArchive.CreateAsyncArchive()) + await using (var archive = (ZipArchive)await ZipArchive.CreateAsyncArchive()) { archive.DeflateCompressionLevel = CompressionLevel.BestSpeed; archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); @@ -191,7 +191,7 @@ public class ZipArchiveAsyncTests : ArchiveTests { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) { - IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + IAsyncArchive archive = await ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); try { await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) @@ -212,7 +212,7 @@ public class ZipArchiveAsyncTests : ArchiveTests { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) { - IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + IAsyncArchive archive = await ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); try { await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); @@ -239,7 +239,7 @@ public class ZipArchiveAsyncTests : ArchiveTests ) #endif { - await using IAsyncArchive archive = ZipArchive.OpenAsyncArchive( + await using IAsyncArchive archive = await ZipArchive.OpenAsyncArchive( new AsyncOnlyStream(stream) ); await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH, progress); From 51c42b89b4617b0ed6344ba2ab447da0fa456e6a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 10:26:18 +0000 Subject: [PATCH 18/22] OpenAsyncArchive has to be async --- .../Archives/ArchiveFactory.Async.cs | 8 +++++-- src/SharpCompress/Archives/IArchiveFactory.cs | 13 +++++++++-- src/SharpCompress/Factories/GZipFactory.cs | 22 +++++++++++++++---- src/SharpCompress/Factories/RarFactory.cs | 22 +++++++++++++++---- .../Factories/SevenZipFactory.cs | 22 +++++++++++++++---- src/SharpCompress/Factories/TarFactory.cs | 14 ++++++++---- src/SharpCompress/Factories/ZipFactory.cs | 22 +++++++++++++++---- tests/SharpCompress.Test/ArchiveTests.cs | 2 +- 8 files changed, 100 insertions(+), 25 deletions(-) diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index 8c6d9e34..4af94a57 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -22,7 +22,9 @@ public static partial class ArchiveFactory readerOptions ??= ReaderOptions.ForExternalStream; var factory = await FindFactoryAsync(stream, cancellationToken) .ConfigureAwait(false); - return factory.OpenAsyncArchive(stream, readerOptions); + return await factory + .OpenAsyncArchive(stream, readerOptions, cancellationToken) + .ConfigureAwait(false); } public static ValueTask OpenAsyncArchive( @@ -45,7 +47,9 @@ public static partial class ArchiveFactory var factory = await FindFactoryAsync(fileInfo, cancellationToken) .ConfigureAwait(false); - return factory.OpenAsyncArchive(fileInfo, options); + return await factory + .OpenAsyncArchive(fileInfo, options, cancellationToken) + .ConfigureAwait(false); } public static async ValueTask OpenAsyncArchive( diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs index 12777a45..52320450 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Factories; using SharpCompress.Readers; @@ -32,7 +33,11 @@ public interface IArchiveFactory : IFactory /// /// An open, readable and seekable stream. /// reading options. - IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null); + ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); /// /// Constructor with a FileInfo object to an existing file. @@ -47,5 +52,9 @@ public interface IArchiveFactory : IFactory /// the file to open. /// reading options. /// Cancellation token. - IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null); + ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); } diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index 6d38d418..ab40438c 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -63,12 +63,26 @@ public class GZipFactory GZipArchive.OpenArchive(stream, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(stream, readerOptions); + public ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(stream, readerOptions)); + } /// - public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + public ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } #endregion diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index f3236d2e..c81e54a7 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -54,16 +54,30 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarArchive.OpenArchive(stream, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(stream, readerOptions); + public ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(stream, readerOptions)); + } /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => RarArchive.OpenArchive(fileInfo, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + public ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } #endregion diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index a75b8448..93826468 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -49,16 +49,30 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory SevenZipArchive.OpenArchive(stream, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(stream, readerOptions); + public ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(stream, readerOptions)); + } /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => SevenZipArchive.OpenArchive(fileInfo, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + public ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } #endregion diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index f47e6de8..88d6afa3 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -157,16 +157,22 @@ public class TarFactory TarArchive.OpenArchive(stream, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(stream, readerOptions); + public async ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) => await TarArchive.OpenAsyncArchive(stream, readerOptions, cancellationToken); /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => TarArchive.OpenArchive(fileInfo, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + public async ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) => await TarArchive.OpenAsyncArchive(fileInfo, readerOptions, cancellationToken); #endregion diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index 6b7df689..6c683a38 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -127,16 +127,30 @@ public class ZipFactory ZipArchive.OpenArchive(stream, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(stream, readerOptions); + public ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(stream, readerOptions)); + } /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => ZipArchive.OpenArchive(fileInfo, readerOptions); /// - public IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + public ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions)); + } #endregion diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 95e10214..0d98e805 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -614,7 +614,7 @@ public class ArchiveTests : ReaderTests { using (var stream = SharpCompressStream.CreateNonDisposing(File.OpenRead(path))) await using ( - var archive = archiveFactory.OpenAsyncArchive( + var archive = await archiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream), readerOptions ) From 89d948b4e1fda77d3bbc4de4778ad3c97695655c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 10:29:15 +0000 Subject: [PATCH 19/22] use configure await false --- .../Archives/Tar/TarArchive.Factory.cs | 28 ++++++------- src/SharpCompress/Factories/TarFactory.cs | 16 ++++++-- .../Readers/Tar/TarReader.Factory.cs | 41 +++++++++++++++---- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 0c0e4d50..99425528 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -105,10 +105,9 @@ public partial class TarArchive i => null, readerOptions ?? new ReaderOptions() ); - var compressionType = await TarFactory.GetCompressionTypeAsync( - sourceStream, - cancellationToken - ); + var compressionType = await TarFactory + .GetCompressionTypeAsync(sourceStream, cancellationToken) + .ConfigureAwait(false); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); } @@ -134,10 +133,9 @@ public partial class TarArchive fileInfo.NotNull(nameof(fileInfo)); readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false }; var sourceStream = new SourceStream(fileInfo, i => null, readerOptions); - var compressionType = await TarFactory.GetCompressionTypeAsync( - sourceStream, - cancellationToken - ); + var compressionType = await TarFactory + .GetCompressionTypeAsync(sourceStream, cancellationToken) + .ConfigureAwait(false); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); } @@ -156,10 +154,9 @@ public partial class TarArchive i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions() ); - var compressionType = await TarFactory.GetCompressionTypeAsync( - sourceStream, - cancellationToken - ); + var compressionType = await TarFactory + .GetCompressionTypeAsync(sourceStream, cancellationToken) + .ConfigureAwait(false); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); } @@ -178,10 +175,9 @@ public partial class TarArchive i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } ); - var compressionType = await TarFactory.GetCompressionTypeAsync( - sourceStream, - cancellationToken - ); + var compressionType = await TarFactory + .GetCompressionTypeAsync(sourceStream, cancellationToken) + .ConfigureAwait(false); sourceStream.Seek(0, SeekOrigin.Begin); return new TarArchive(sourceStream, compressionType); } diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 88d6afa3..fd2f528f 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -141,7 +141,11 @@ public class TarFactory { stream.Seek(0, SeekOrigin.Begin); var decompressedStream = wrapper.CreateStream(stream); - if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken)) + if ( + await TarArchive + .IsTarFileAsync(decompressedStream, cancellationToken) + .ConfigureAwait(false) + ) { return wrapper.CompressionType; } @@ -161,7 +165,10 @@ public class TarFactory Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => await TarArchive.OpenAsyncArchive(stream, readerOptions, cancellationToken); + ) => + await TarArchive + .OpenAsyncArchive(stream, readerOptions, cancellationToken) + .ConfigureAwait(false); /// public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => @@ -172,7 +179,10 @@ public class TarFactory FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => await TarArchive.OpenAsyncArchive(fileInfo, readerOptions, cancellationToken); + ) => + await TarArchive + .OpenAsyncArchive(fileInfo, readerOptions, cancellationToken) + .ConfigureAwait(false); #endregion diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs index 8f506e7d..fc775d71 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -34,6 +34,7 @@ public partial class TarReader CancellationToken cancellationToken = default ) { + cancellationToken.ThrowIfCancellationRequested(); stream.NotNull(nameof(stream)); options ??= new ReaderOptions(); var sharpCompressStream = SharpCompressStream.Create( @@ -41,11 +42,17 @@ public partial class TarReader bufferSize: options.RewindableBufferSize ); long pos = sharpCompressStream.Position; - if (await GZipArchive.IsGZipFileAsync(sharpCompressStream, cancellationToken)) + if ( + await GZipArchive + .IsGZipFileAsync(sharpCompressStream, cancellationToken) + .ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); - if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + if ( + await TarArchive.IsTarFileAsync(testStream, cancellationToken).ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; return new TarReader(sharpCompressStream, options, CompressionType.GZip); @@ -53,7 +60,11 @@ public partial class TarReader throw new InvalidFormatException("Not a tar file."); } sharpCompressStream.Position = pos; - if (await BZip2Stream.IsBZip2Async(sharpCompressStream, cancellationToken)) + if ( + await BZip2Stream + .IsBZip2Async(sharpCompressStream, cancellationToken) + .ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; var testStream = BZip2Stream.Create( @@ -61,7 +72,9 @@ public partial class TarReader CompressionMode.Decompress, false ); - if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + if ( + await TarArchive.IsTarFileAsync(testStream, cancellationToken).ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; return new TarReader(sharpCompressStream, options, CompressionType.BZip2); @@ -69,11 +82,17 @@ public partial class TarReader throw new InvalidFormatException("Not a tar file."); } sharpCompressStream.Position = pos; - if (await ZStandardStream.IsZStandardAsync(sharpCompressStream, cancellationToken)) + if ( + await ZStandardStream + .IsZStandardAsync(sharpCompressStream, cancellationToken) + .ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; var testStream = new ZStandardStream(sharpCompressStream); - if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + if ( + await TarArchive.IsTarFileAsync(testStream, cancellationToken).ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; return new TarReader(sharpCompressStream, options, CompressionType.ZStandard); @@ -81,11 +100,17 @@ public partial class TarReader throw new InvalidFormatException("Not a tar file."); } sharpCompressStream.Position = pos; - if (await LZipStream.IsLZipFileAsync(sharpCompressStream, cancellationToken)) + if ( + await LZipStream + .IsLZipFileAsync(sharpCompressStream, cancellationToken) + .ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; var testStream = new LZipStream(sharpCompressStream, CompressionMode.Decompress); - if (await TarArchive.IsTarFileAsync(testStream, cancellationToken)) + if ( + await TarArchive.IsTarFileAsync(testStream, cancellationToken).ConfigureAwait(false) + ) { sharpCompressStream.Position = pos; return new TarReader(sharpCompressStream, options, CompressionType.LZip); From 7f6272807d9a1b945d0570adedd0fb15302129cb Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 10:32:20 +0000 Subject: [PATCH 20/22] update docs --- docs/API.md | 6 +++--- src/SharpCompress/Archives/IArchiveFactory.cs | 3 +++ src/SharpCompress/Readers/IReaderFactory.cs | 14 +++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/API.md b/docs/API.md index d2a944ad..bc8e0541 100644 --- a/docs/API.md +++ b/docs/API.md @@ -95,7 +95,7 @@ using (var archive = ZipArchive.OpenArchive("file.zip")) } // Async extraction (requires IAsyncArchive) -using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip")) +await using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip")) { await asyncArchive.WriteToDirectoryAsync( @"C:\output", @@ -177,7 +177,7 @@ using (var reader = ReaderFactory.OpenReader(stream)) // Async variants (use OpenAsyncReader to get IAsyncReader) using (var stream = File.OpenRead("file.zip")) -using (var reader = await ReaderFactory.OpenAsyncReader(stream)) +await using (var reader = await ReaderFactory.OpenAsyncReader(stream)) { while (await reader.MoveToNextEntryAsync()) { @@ -409,7 +409,7 @@ cts.CancelAfter(TimeSpan.FromMinutes(5)); try { - using (var archive = await ZipArchive.OpenAsyncArchive("archive.zip")) + await using (var archive = await ZipArchive.OpenAsyncArchive("archive.zip")) { await archive.WriteToDirectoryAsync( @"C:\output", diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs index 52320450..03185424 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -33,6 +33,8 @@ public interface IArchiveFactory : IFactory /// /// An open, readable and seekable stream. /// reading options. + /// Cancellation token. + /// A containing the opened async archive. ValueTask OpenAsyncArchive( Stream stream, ReaderOptions? readerOptions = null, @@ -52,6 +54,7 @@ public interface IArchiveFactory : IFactory /// the file to open. /// reading options. /// Cancellation token. + /// A containing the opened async archive. ValueTask OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, diff --git a/src/SharpCompress/Readers/IReaderFactory.cs b/src/SharpCompress/Readers/IReaderFactory.cs index fb8c2e9f..538dacfb 100644 --- a/src/SharpCompress/Readers/IReaderFactory.cs +++ b/src/SharpCompress/Readers/IReaderFactory.cs @@ -9,18 +9,18 @@ public interface IReaderFactory : Factories.IFactory /// /// Opens a Reader for Non-seeking usage. /// - /// - /// - /// + /// An open, readable stream. + /// Reader options. + /// The opened reader. IReader OpenReader(Stream stream, ReaderOptions? options); /// /// Opens a Reader for Non-seeking usage asynchronously. /// - /// - /// - /// - /// + /// An open, readable stream. + /// Reader options. + /// Cancellation token. + /// A containing the opened async reader. ValueTask OpenAsyncReader( Stream stream, ReaderOptions? options, From fb707aa6764afc1c94263dd60c7b1171487791c3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 10:56:26 +0000 Subject: [PATCH 21/22] push to nuget only on tags --- .github/workflows/nuget-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nuget-release.yml b/.github/workflows/nuget-release.yml index 253a93ed..548ca8eb 100644 --- a/.github/workflows/nuget-release.yml +++ b/.github/workflows/nuget-release.yml @@ -53,9 +53,9 @@ jobs: name: ${{ matrix.os }}-nuget-package path: artifacts/*.nupkg - # Push to NuGet.org using C# build target (Windows only, not on PRs) + # Push to NuGet.org only for version tag pushes (Windows only) - name: Push to NuGet - if: success() && matrix.os == 'windows-latest' && github.event_name != 'pull_request' + if: success() && matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/') run: dotnet run --project build/build.csproj -- push-to-nuget env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} From c81e78b5bb22d9b10af29f428d3330bdaf322152 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 12 Feb 2026 11:07:11 +0000 Subject: [PATCH 22/22] fix async benchmarks --- .../Benchmarks/RarBenchmarks.cs | 2 +- .../Benchmarks/SevenZipBenchmarks.cs | 12 +++++++++--- .../Benchmarks/TarBenchmarks.cs | 4 ++-- .../Benchmarks/ZipBenchmarks.cs | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs index 4667694b..ffe207cd 100644 --- a/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs @@ -35,7 +35,7 @@ public class RarBenchmarks : ArchiveBenchmarkBase public async Task RarExtractArchiveApiAsync() { using var stream = new MemoryStream(_rarBytes); - await using var archive = RarArchive.OpenAsyncArchive(stream); + await using var archive = await RarArchive.OpenAsyncArchive(stream).ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); diff --git a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs index 8a249b00..38435ef2 100644 --- a/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs @@ -36,7 +36,9 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase public async Task SevenZipLzmaExtractAsync() { using var stream = new MemoryStream(_lzmaBytes); - await using var archive = SevenZipArchive.OpenAsyncArchive(stream); + await using var archive = await SevenZipArchive + .OpenAsyncArchive(stream) + .ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); @@ -60,7 +62,9 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase public async Task SevenZipLzma2ExtractAsync() { using var stream = new MemoryStream(_lzma2Bytes); - await using var archive = SevenZipArchive.OpenAsyncArchive(stream); + await using var archive = await SevenZipArchive + .OpenAsyncArchive(stream) + .ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); @@ -85,7 +89,9 @@ public class SevenZipBenchmarks : ArchiveBenchmarkBase public async Task SevenZipLzma2ExtractAsync_Reader() { using var stream = new MemoryStream(_lzma2Bytes); - await using var archive = SevenZipArchive.OpenAsyncArchive(stream); + await using var archive = await SevenZipArchive + .OpenAsyncArchive(stream) + .ConfigureAwait(false); await using var reader = await archive.ExtractAllEntriesAsync(); while (await reader.MoveToNextEntryAsync().ConfigureAwait(false)) { diff --git a/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs index f7ad19e9..521e7438 100644 --- a/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs @@ -39,7 +39,7 @@ public class TarBenchmarks : ArchiveBenchmarkBase public async Task TarExtractArchiveApiAsync() { using var stream = new MemoryStream(_tarBytes); - await using var archive = TarArchive.OpenAsyncArchive(stream); + await using var archive = await TarArchive.OpenAsyncArchive(stream).ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); @@ -91,7 +91,7 @@ public class TarBenchmarks : ArchiveBenchmarkBase public async Task TarGzipExtractAsync() { using var stream = new MemoryStream(_tarGzBytes); - await using var archive = TarArchive.OpenAsyncArchive(stream); + await using var archive = await TarArchive.OpenAsyncArchive(stream).ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false); diff --git a/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs b/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs index fb95d018..375c5690 100644 --- a/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs +++ b/tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs @@ -39,7 +39,7 @@ public class ZipBenchmarks : ArchiveBenchmarkBase public async Task ZipExtractArchiveApiAsync() { using var stream = new MemoryStream(_archiveBytes); - await using var archive = ZipArchive.OpenAsyncArchive(stream); + await using var archive = await ZipArchive.OpenAsyncArchive(stream).ConfigureAwait(false); await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory)) { await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);