mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-08 13:34:57 +00:00
Compare commits
145 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd242cb1c5 | ||
|
|
0ecbe02ede | ||
|
|
bcf9a6bdf1 | ||
|
|
e3a25ecdc0 | ||
|
|
783521928d | ||
|
|
9a876abd31 | ||
|
|
97f58b412e | ||
|
|
99a8b0f750 | ||
|
|
a9017d7c25 | ||
|
|
d9e4b26648 | ||
|
|
0d03bafe49 | ||
|
|
fee15a31f9 | ||
|
|
997d3910d4 | ||
|
|
a3918cc0d7 | ||
|
|
f056986b07 | ||
|
|
59c1f02f98 | ||
|
|
3a71a2b1f8 | ||
|
|
2ef1215b49 | ||
|
|
130ac83076 | ||
|
|
dd606a0702 | ||
|
|
84cd772f50 | ||
|
|
8b612c658d | ||
|
|
f7b3525c4e | ||
|
|
de83bdae48 | ||
|
|
d90b610767 | ||
|
|
2d41de6b72 | ||
|
|
f391c3caf3 | ||
|
|
9bdf150676 | ||
|
|
0c199609eb | ||
|
|
6eff9d3753 | ||
|
|
7ab16457c7 | ||
|
|
e7ad8132b5 | ||
|
|
55cb350d2c | ||
|
|
7fa271a1b4 | ||
|
|
c53ca372f2 | ||
|
|
75bc8501f4 | ||
|
|
1e22b47fe1 | ||
|
|
74e2dca207 | ||
|
|
a669de24b7 | ||
|
|
e1e9c449e9 | ||
|
|
60e1dc0239 | ||
|
|
10eb94fd82 | ||
|
|
ccc8587e5f | ||
|
|
53c96193c1 | ||
|
|
d4f11e00b1 | ||
|
|
321233b82c | ||
|
|
eb188051d4 | ||
|
|
a136084e11 | ||
|
|
bc06f3179d | ||
|
|
ee84d971b2 | ||
|
|
264d80ef4c | ||
|
|
dba68187ac | ||
|
|
ca4a1936b3 | ||
|
|
77c8d31a90 | ||
|
|
ab7196f86c | ||
|
|
88b3a66bf9 | ||
|
|
ea77666b4a | ||
|
|
db98e5f39b | ||
|
|
df59c5cb9d | ||
|
|
e786e95358 | ||
|
|
75ada5623c | ||
|
|
ad5c655c45 | ||
|
|
65e607454e | ||
|
|
f238be6003 | ||
|
|
dc31e4c5fa | ||
|
|
665d8cd266 | ||
|
|
8324114e84 | ||
|
|
b83e6ee4ce | ||
|
|
58bab0d310 | ||
|
|
1af51aaaba | ||
|
|
a09327b831 | ||
|
|
16543bf74c | ||
|
|
aa4cd373ac | ||
|
|
351e294362 | ||
|
|
a0c5b1cd9d | ||
|
|
df2ed1e584 | ||
|
|
b354f7a3a5 | ||
|
|
bb53d1e1c6 | ||
|
|
2aabd8d0e1 | ||
|
|
aca97c2c6c | ||
|
|
8e7d959cf4 | ||
|
|
b23f031db9 | ||
|
|
1ba529a9d5 | ||
|
|
3d29c183ef | ||
|
|
8a108b590d | ||
|
|
bca0f67344 | ||
|
|
f3dad51134 | ||
|
|
f51840829c | ||
|
|
aa1c0d0870 | ||
|
|
dee5ee6589 | ||
|
|
b799f479c4 | ||
|
|
b4352fefa5 | ||
|
|
77d06fb60e | ||
|
|
00b647457c | ||
|
|
153d10a35c | ||
|
|
06713c641e | ||
|
|
210978ec2d | ||
|
|
42f7d43139 | ||
|
|
19967f5ad7 | ||
|
|
a1de3eb47d | ||
|
|
e88841bdec | ||
|
|
c8e4915f8e | ||
|
|
a93a3f0598 | ||
|
|
084f81fc8d | ||
|
|
d148f36e87 | ||
|
|
150d9c35b7 | ||
|
|
e11198616e | ||
|
|
2f27f1e6f9 | ||
|
|
5392ca9794 | ||
|
|
46672eb583 | ||
|
|
79653eee80 | ||
|
|
16ad86c52a | ||
|
|
6b7c6be5f5 | ||
|
|
fda1c2cc79 | ||
|
|
ef2fee0ee3 | ||
|
|
e287d0811d | ||
|
|
a7164f3c9f | ||
|
|
c55060039a | ||
|
|
c68d8deddd | ||
|
|
f6eabc5db1 | ||
|
|
72d5884db6 | ||
|
|
3595c89c79 | ||
|
|
9ebbc718c5 | ||
|
|
e862480b86 | ||
|
|
1f3d8fe6f1 | ||
|
|
41ae036ab4 | ||
|
|
588d176b96 | ||
|
|
f8697120a0 | ||
|
|
1a767105e6 | ||
|
|
4067b6ed2c | ||
|
|
b272dbfd1f | ||
|
|
48be7bbf86 | ||
|
|
51e22cea71 | ||
|
|
2241e27e68 | ||
|
|
11c90ae879 | ||
|
|
cf55125202 | ||
|
|
9cefb85905 | ||
|
|
fc672da0e0 | ||
|
|
25b297b142 | ||
|
|
ab03c12fa8 | ||
|
|
3095c805ad | ||
|
|
9c18daafb8 | ||
|
|
16182417fb | ||
|
|
9af35201e4 | ||
|
|
f21b982955 |
8
.github/COPILOT_AGENT_README.md
vendored
8
.github/COPILOT_AGENT_README.md
vendored
@@ -3,11 +3,13 @@
|
||||
This repository includes a minimal opt-in configuration and CI workflow to allow the GitHub Copilot coding agent to open and validate PRs.
|
||||
|
||||
- .copilot-agent.yml: opt-in config for automated agents
|
||||
- .github/agents/copilot-agent.yml: detailed agent policy configuration
|
||||
- .github/workflows/dotnetcore.yml: CI runs on PRs touching the solution, source, or tests to validate changes
|
||||
- AGENTS.yml: general information for this project
|
||||
- AGENTS.md: general instructions for Copilot coding agent with project-specific guidelines
|
||||
|
||||
Maintainers can adjust the allowed paths or disable the agent by editing or removing .copilot-agent.yml.
|
||||
|
||||
Notes:
|
||||
- Do not change any other files in the repository.
|
||||
- If build/test paths are different, update the workflow accordingly; this workflow targets SharpCompress.sln and the SharpCompress.Tests test project.
|
||||
- The agent can create, modify, and delete files within the allowed paths (src, tests, README.md, AGENTS.md)
|
||||
- All changes require review before merge
|
||||
- If build/test paths are different, update the workflow accordingly; this workflow targets SharpCompress.sln and the SharpCompress.Test test project.
|
||||
|
||||
107
AGENTS.md
107
AGENTS.md
@@ -1,19 +1,24 @@
|
||||
---
|
||||
description: 'Guidelines for building C# applications'
|
||||
description: 'Guidelines for building SharpCompress - A C# compression library'
|
||||
applyTo: '**/*.cs'
|
||||
---
|
||||
|
||||
# C# Development
|
||||
# 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.
|
||||
|
||||
## C# Instructions
|
||||
- Always use the latest version C#, currently C# 13 features.
|
||||
- Write clear and concise comments for each function.
|
||||
- Follow the existing code style and patterns in the codebase.
|
||||
|
||||
## General Instructions
|
||||
- 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.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
@@ -23,21 +28,26 @@ applyTo: '**/*.cs'
|
||||
|
||||
## Code Formatting
|
||||
|
||||
- Use CSharpier for all code formatting to ensure consistent style across the project.
|
||||
- Install CSharpier globally: `dotnet tool install -g csharpier`
|
||||
- Format files with: `dotnet csharpier format .`
|
||||
- **ALWAYS run `dotnet csharpier format .` after making code changes before committing.**
|
||||
- Configure your IDE to format on save using CSharpier.
|
||||
- CSharpier configuration can be customized via `.csharpierrc` file in the project root.
|
||||
- Trust CSharpier's opinionated formatting decisions to maintain consistency.
|
||||
- Use CSharpier for code formatting to ensure consistent style across the project
|
||||
- CSharpier is configured as a local tool in `.config/dotnet-tools.json`
|
||||
- Restore tools with: `dotnet tool restore`
|
||||
- Format files from the project root with: `dotnet csharpier .`
|
||||
- **Run `dotnet csharpier .` from the project root after making code changes before committing**
|
||||
- Configure your IDE to format on save using CSharpier for the best experience
|
||||
- The project also uses `.editorconfig` for editor settings (indentation, encoding, etc.)
|
||||
- Let CSharpier handle code style while `.editorconfig` handles editor behavior
|
||||
|
||||
## Project Setup and Structure
|
||||
|
||||
- Guide users through creating a new .NET project with the appropriate templates.
|
||||
- Explain the purpose of each generated file and folder to build understanding of the project structure.
|
||||
- Demonstrate how to organize code using feature folders or domain-driven design principles.
|
||||
- Show proper separation of concerns with models, services, and data access layers.
|
||||
- Explain the Program.cs and configuration system in ASP.NET Core 9 including environment-specific settings.
|
||||
- The project targets multiple frameworks: .NET Framework 4.62, .NET Standard 2.1, .NET 6.0, and .NET 8.0
|
||||
- Main library is in `src/SharpCompress/`
|
||||
- Tests are in `tests/SharpCompress.Test/`
|
||||
- Performance tests are in `tests/SharpCompress.Performance/`
|
||||
- Test archives are in `tests/TestArchives/`
|
||||
- Build project is in `build/`
|
||||
- Use `dotnet build` to build the solution
|
||||
- Use `dotnet test` to run tests
|
||||
- Solution file: `SharpCompress.sln`
|
||||
|
||||
## Nullable Reference Types
|
||||
|
||||
@@ -45,21 +55,64 @@ applyTo: '**/*.cs'
|
||||
- Always use `is null` or `is not null` instead of `== null` or `!= null`.
|
||||
- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null.
|
||||
|
||||
## SharpCompress-Specific Guidelines
|
||||
|
||||
### Supported Formats
|
||||
SharpCompress supports multiple archive and compression formats:
|
||||
- **Archive Formats**: Zip, Tar, 7Zip, Rar (read-only)
|
||||
- **Compression**: DEFLATE, BZip2, LZMA/LZMA2, PPMd, ZStandard (decompress only), Deflate64 (decompress only)
|
||||
- **Combined Formats**: Tar.GZip, Tar.BZip2, Tar.LZip, Tar.XZ, Tar.ZStandard
|
||||
- See FORMATS.md for complete format support matrix
|
||||
|
||||
### Stream Handling Rules
|
||||
- **Disposal**: As of version 0.21, SharpCompress closes wrapped streams by default
|
||||
- Use `ReaderOptions` or `WriterOptions` with `LeaveStreamOpen = true` to control stream disposal
|
||||
- Use `NonDisposingStream` wrapper when working with compression streams directly to prevent disposal
|
||||
- Always dispose of readers, writers, and archives in `using` blocks
|
||||
- For forward-only operations, use Reader/Writer APIs; for random access, use Archive APIs
|
||||
|
||||
### Async/Await Patterns
|
||||
- All I/O operations support async/await with `CancellationToken`
|
||||
- Async methods follow the naming convention: `MethodNameAsync`
|
||||
- Key async methods:
|
||||
- `WriteEntryToAsync` - Extract entry asynchronously
|
||||
- `WriteAllToDirectoryAsync` - Extract all entries asynchronously
|
||||
- `WriteAsync` - Write entry asynchronously
|
||||
- `WriteAllAsync` - Write directory asynchronously
|
||||
- `OpenEntryStreamAsync` - Open entry stream asynchronously
|
||||
- Always provide `CancellationToken` parameter in async methods
|
||||
|
||||
### Archive APIs vs Reader/Writer APIs
|
||||
- **Archive API**: Use for random access with seekable streams (e.g., `ZipArchive`, `TarArchive`)
|
||||
- **Reader API**: Use for forward-only reading on non-seekable streams (e.g., `ZipReader`, `TarReader`)
|
||||
- **Writer API**: Use for forward-only writing on streams (e.g., `ZipWriter`, `TarWriter`)
|
||||
- 7Zip only supports Archive API due to format limitations
|
||||
|
||||
### Tar-Specific Considerations
|
||||
- Tar format requires file size in the header
|
||||
- If no size is specified to TarWriter and the stream is not seekable, an exception will be thrown
|
||||
- Tar combined with compression (GZip, BZip2, LZip, XZ) is supported
|
||||
|
||||
### Zip-Specific Considerations
|
||||
- Supports Zip64 for large files (seekable streams only)
|
||||
- Supports PKWare and WinZip AES encryption
|
||||
- Multiple compression methods: None, Shrink, Reduce, Implode, DEFLATE, Deflate64, BZip2, LZMA, PPMd
|
||||
- Encrypted LZMA is not supported
|
||||
|
||||
### Performance Considerations
|
||||
- For large files, use Reader/Writer APIs with non-seekable streams to avoid loading entire file in memory
|
||||
- Leverage async I/O for better scalability
|
||||
- Consider compression level trade-offs (speed vs. size)
|
||||
- Use appropriate buffer sizes for stream operations
|
||||
|
||||
## Testing
|
||||
|
||||
- Always include test cases for critical paths of the application.
|
||||
- Guide users through creating unit tests.
|
||||
- Test with multiple archive formats when making changes to core functionality.
|
||||
- Include tests for both Archive and Reader/Writer APIs when applicable.
|
||||
- Test async operations with cancellation tokens.
|
||||
- Do not emit "Act", "Arrange" or "Assert" comments.
|
||||
- Copy existing style in nearby files for test method names and capitalization.
|
||||
- Explain integration testing approaches for API endpoints.
|
||||
- Demonstrate how to mock dependencies for effective testing.
|
||||
- Show how to test authentication and authorization logic.
|
||||
- Explain test-driven development principles as applied to API development.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- Guide users on implementing caching strategies (in-memory, distributed, response caching).
|
||||
- Explain asynchronous programming patterns and why they matter for API performance.
|
||||
- Demonstrate pagination, filtering, and sorting for large data sets.
|
||||
- Show how to implement compression and other performance optimizations.
|
||||
- Explain how to measure and benchmark API performance.
|
||||
- Use test archives from `tests/TestArchives` directory for consistency.
|
||||
- Test stream disposal and `LeaveStreamOpen` behavior.
|
||||
- Test edge cases: empty archives, large files, corrupted archives, encrypted archives.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
|
||||
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.21" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Writers;
|
||||
@@ -94,6 +96,9 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
DateTime? modified
|
||||
) => AddEntry(key, source, closeStream, size, modified);
|
||||
|
||||
IArchiveEntry IWritableArchive.AddDirectoryEntry(string key, DateTime? modified) =>
|
||||
AddDirectoryEntry(key, modified);
|
||||
|
||||
public TEntry AddEntry(
|
||||
string key,
|
||||
Stream source,
|
||||
@@ -134,6 +139,22 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
return false;
|
||||
}
|
||||
|
||||
public TEntry AddDirectoryEntry(string key, DateTime? modified = null)
|
||||
{
|
||||
if (key.Length > 0 && key[0] is '/' or '\\')
|
||||
{
|
||||
key = key.Substring(1);
|
||||
}
|
||||
if (DoesKeyMatchExisting(key))
|
||||
{
|
||||
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
|
||||
}
|
||||
var entry = CreateDirectoryEntry(key, modified);
|
||||
newEntries.Add(entry);
|
||||
RebuildModifiedCollection();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void SaveTo(Stream stream, WriterOptions options)
|
||||
{
|
||||
//reset streams of new entries
|
||||
@@ -141,6 +162,18 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
SaveTo(stream, options, OldEntries, newEntries);
|
||||
}
|
||||
|
||||
public async Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//reset streams of new entries
|
||||
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
|
||||
await SaveToAsync(stream, options, OldEntries, newEntries, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected TEntry CreateEntry(
|
||||
string key,
|
||||
Stream source,
|
||||
@@ -166,6 +199,8 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
bool closeStream
|
||||
);
|
||||
|
||||
protected abstract TEntry CreateDirectoryEntry(string key, DateTime? modified);
|
||||
|
||||
protected abstract void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
@@ -173,6 +208,14 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
IEnumerable<TEntry> newEntries
|
||||
);
|
||||
|
||||
protected abstract Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
IEnumerable<TEntry> oldEntries,
|
||||
IEnumerable<TEntry> newEntries,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
||||
@@ -20,7 +20,7 @@ public static class ArchiveFactory
|
||||
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions();
|
||||
stream = new SharpCompressStream(stream, bufferSize: readerOptions.BufferSize);
|
||||
stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize);
|
||||
return FindFactory<IArchiveFactory>(stream).Open(stream, readerOptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ 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.GZip;
|
||||
using SharpCompress.IO;
|
||||
@@ -136,6 +138,16 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
SaveTo(stream, new WriterOptions(CompressionType.GZip));
|
||||
}
|
||||
|
||||
public Task SaveToAsync(string filePath, CancellationToken cancellationToken = default) =>
|
||||
SaveToAsync(new FileInfo(filePath), cancellationToken);
|
||||
|
||||
public async Task SaveToAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
|
||||
await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static bool IsGZipFile(Stream stream)
|
||||
{
|
||||
// read the header on the first read
|
||||
@@ -173,6 +185,11 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
}
|
||||
|
||||
protected override GZipArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
|
||||
protected override void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
@@ -196,6 +213,28 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
IEnumerable<GZipArchiveEntry> oldEntries,
|
||||
IEnumerable<GZipArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (Entries.Count > 1)
|
||||
{
|
||||
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(entry.Key.NotNull("Entry Key is null"), entryStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
|
||||
{
|
||||
var stream = volumes.Single().Stream;
|
||||
|
||||
@@ -15,9 +15,10 @@ public class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
{
|
||||
//this is to reset the stream to be read multiple times
|
||||
var part = (GZipFilePart)Parts.Single();
|
||||
if (part.GetRawStream().Position != part.EntryStartPosition)
|
||||
var rawStream = part.GetRawStream();
|
||||
if (rawStream.CanSeek && rawStream.Position != part.EntryStartPosition)
|
||||
{
|
||||
part.GetRawStream().Position = part.EntryStartPosition;
|
||||
rawStream.Position = part.EntryStartPosition;
|
||||
}
|
||||
return Parts.Single().GetCompressedStream().NotNull();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -30,6 +32,34 @@ public static class IArchiveEntryExtensions
|
||||
streamListener.FireEntryExtractionEnd(archiveEntry);
|
||||
}
|
||||
|
||||
public static async Task WriteToAsync(
|
||||
this IArchiveEntry archiveEntry,
|
||||
Stream streamToWriteTo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (archiveEntry.IsDirectory)
|
||||
{
|
||||
throw new ExtractionException("Entry is a file directory and cannot be extracted.");
|
||||
}
|
||||
|
||||
var streamListener = (IArchiveExtractionListener)archiveEntry.Archive;
|
||||
streamListener.EnsureEntriesLoaded();
|
||||
streamListener.FireEntryExtractionBegin(archiveEntry);
|
||||
streamListener.FireFilePartExtractionBegin(
|
||||
archiveEntry.Key ?? "Key",
|
||||
archiveEntry.Size,
|
||||
archiveEntry.CompressedSize
|
||||
);
|
||||
var entryStream = archiveEntry.OpenEntryStream();
|
||||
using (entryStream)
|
||||
{
|
||||
using Stream s = new ListeningStream(streamListener, entryStream);
|
||||
await s.CopyToAsync(streamToWriteTo, 81920, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
streamListener.FireEntryExtractionEnd(archiveEntry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
@@ -45,6 +75,23 @@ public static class IArchiveEntryExtensions
|
||||
entry.WriteToFile
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory asynchronously, retaining filename
|
||||
/// </summary>
|
||||
public static Task WriteToDirectoryAsync(
|
||||
this IArchiveEntry entry,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
ExtractionMethods.WriteEntryToDirectoryAsync(
|
||||
entry,
|
||||
destinationDirectory,
|
||||
options,
|
||||
(x, opt) => entry.WriteToFileAsync(x, opt, cancellationToken),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific file
|
||||
/// </summary>
|
||||
@@ -63,4 +110,24 @@ public static class IArchiveEntryExtensions
|
||||
entry.WriteTo(fs);
|
||||
}
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific file asynchronously
|
||||
/// </summary>
|
||||
public static Task WriteToFileAsync(
|
||||
this IArchiveEntry entry,
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
ExtractionMethods.WriteEntryToFileAsync(
|
||||
entry,
|
||||
destinationFileName,
|
||||
options,
|
||||
async (x, fm) =>
|
||||
{
|
||||
using var fs = File.Open(destinationFileName, fm);
|
||||
await entry.WriteToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public static class IArchiveExtensions
|
||||
}
|
||||
|
||||
// Write file
|
||||
using var fs = File.OpenWrite(path);
|
||||
using var fs = File.Create(path);
|
||||
entry.WriteTo(fs);
|
||||
|
||||
// Update progress
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -16,8 +18,16 @@ public interface IWritableArchive : IArchive
|
||||
DateTime? modified = null
|
||||
);
|
||||
|
||||
IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null);
|
||||
|
||||
void SaveTo(Stream stream, WriterOptions options);
|
||||
|
||||
Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -42,6 +44,24 @@ public static class IWritableArchiveExtensions
|
||||
writableArchive.SaveTo(stream, options);
|
||||
}
|
||||
|
||||
public static Task SaveToAsync(
|
||||
this IWritableArchive writableArchive,
|
||||
string filePath,
|
||||
WriterOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken);
|
||||
|
||||
public static async Task SaveToAsync(
|
||||
this IWritableArchive writableArchive,
|
||||
FileInfo fileInfo,
|
||||
WriterOptions options,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
|
||||
await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static void AddAllFromDirectory(
|
||||
this IWritableArchive writableArchive,
|
||||
string filePath,
|
||||
|
||||
@@ -70,24 +70,51 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
|
||||
public Stream OpenEntryStream()
|
||||
{
|
||||
RarStream stream;
|
||||
if (IsRarV3)
|
||||
{
|
||||
return new RarStream(
|
||||
stream = new RarStream(
|
||||
archive.UnpackV1.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = new RarStream(
|
||||
archive.UnpackV2017.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
|
||||
);
|
||||
}
|
||||
|
||||
return new RarStream(
|
||||
archive.UnpackV2017.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
|
||||
);
|
||||
stream.Initialize();
|
||||
return stream;
|
||||
}
|
||||
|
||||
public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(OpenEntryStream());
|
||||
public async Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
RarStream stream;
|
||||
if (IsRarV3)
|
||||
{
|
||||
stream = new RarStream(
|
||||
archive.UnpackV1.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = new RarStream(
|
||||
archive.UnpackV2017.Value,
|
||||
FileHeader,
|
||||
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
|
||||
);
|
||||
}
|
||||
|
||||
await stream.InitializeAsync(cancellationToken);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public bool IsComplete
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
@@ -222,6 +224,11 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
closeStream
|
||||
);
|
||||
|
||||
protected override TarArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => new TarWritableArchiveEntry(this, directoryPath, modified);
|
||||
|
||||
protected override void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
@@ -230,15 +237,62 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new TarWriter(stream, new TarWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size
|
||||
);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
writer.WriteDirectory(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
IEnumerable<TarArchiveEntry> oldEntries,
|
||||
IEnumerable<TarArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var writer = new TarWriter(stream, new TarWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
await writer
|
||||
.WriteDirectoryAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace SharpCompress.Archives.Tar;
|
||||
internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry
|
||||
{
|
||||
private readonly bool closeStream;
|
||||
private readonly Stream stream;
|
||||
private readonly Stream? stream;
|
||||
private readonly bool isDirectory;
|
||||
|
||||
internal TarWritableArchiveEntry(
|
||||
TarArchive archive,
|
||||
@@ -27,6 +28,22 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
Size = size;
|
||||
LastModifiedTime = lastModified;
|
||||
this.closeStream = closeStream;
|
||||
isDirectory = false;
|
||||
}
|
||||
|
||||
internal TarWritableArchiveEntry(
|
||||
TarArchive archive,
|
||||
string directoryPath,
|
||||
DateTime? lastModified
|
||||
)
|
||||
: base(archive, null, CompressionType.None)
|
||||
{
|
||||
stream = null;
|
||||
Key = directoryPath;
|
||||
Size = 0;
|
||||
LastModifiedTime = lastModified;
|
||||
closeStream = false;
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
public override long Crc => 0;
|
||||
@@ -47,15 +64,19 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
public override bool IsDirectory => isDirectory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
Stream IWritableArchiveEntry.Stream => stream ?? Stream.Null;
|
||||
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return SharpCompressStream.Create(stream, leaveOpen: true);
|
||||
@@ -63,7 +84,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream)
|
||||
if (closeStream && stream is not null)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ 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.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
@@ -306,14 +308,59 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
writer.WriteDirectory(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task SaveToAsync(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
IEnumerable<ZipArchiveEntry> oldEntries,
|
||||
IEnumerable<ZipArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
await writer
|
||||
.WriteDirectoryAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +372,11 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
bool closeStream
|
||||
) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
|
||||
protected override ZipArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => new ZipWritableArchiveEntry(this, directoryPath, modified);
|
||||
|
||||
public static ZipArchive Create() => new();
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace SharpCompress.Archives.Zip;
|
||||
internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
{
|
||||
private readonly bool closeStream;
|
||||
private readonly Stream stream;
|
||||
private readonly Stream? stream;
|
||||
private readonly bool isDirectory;
|
||||
private bool isDisposed;
|
||||
|
||||
internal ZipWritableArchiveEntry(
|
||||
@@ -27,6 +28,22 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
Size = size;
|
||||
LastModifiedTime = lastModified;
|
||||
this.closeStream = closeStream;
|
||||
isDirectory = false;
|
||||
}
|
||||
|
||||
internal ZipWritableArchiveEntry(
|
||||
ZipArchive archive,
|
||||
string directoryPath,
|
||||
DateTime? lastModified
|
||||
)
|
||||
: base(archive, null)
|
||||
{
|
||||
stream = null;
|
||||
Key = directoryPath;
|
||||
Size = 0;
|
||||
LastModifiedTime = lastModified;
|
||||
closeStream = false;
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
public override long Crc => 0;
|
||||
@@ -47,16 +64,20 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
public override bool IsDirectory => isDirectory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
|
||||
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
Stream IWritableArchiveEntry.Stream => stream ?? Stream.Null;
|
||||
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return SharpCompressStream.Create(stream, leaveOpen: true);
|
||||
@@ -64,7 +85,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream && !isDisposed)
|
||||
if (closeStream && !isDisposed && stream is not null)
|
||||
{
|
||||
stream.Dispose();
|
||||
isDisposed = true;
|
||||
|
||||
@@ -8,4 +8,5 @@ public enum ArchiveType
|
||||
SevenZip,
|
||||
GZip,
|
||||
Arc,
|
||||
Arj,
|
||||
}
|
||||
|
||||
58
src/SharpCompress/Common/Arj/ArjEntry.cs
Normal file
58
src/SharpCompress/Common/Arj/ArjEntry.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Arc;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Arj
|
||||
{
|
||||
public class ArjEntry : Entry
|
||||
{
|
||||
private readonly ArjFilePart _filePart;
|
||||
|
||||
internal ArjEntry(ArjFilePart filePart)
|
||||
{
|
||||
_filePart = filePart;
|
||||
}
|
||||
|
||||
public override long Crc => _filePart.Header.OriginalCrc32;
|
||||
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
|
||||
|
||||
public override CompressionType CompressionType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePart.Header.CompressionMethod == CompressionMethod.Stored)
|
||||
{
|
||||
return CompressionType.None;
|
||||
}
|
||||
return CompressionType.ArjLZ77;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Size => _filePart?.Header.OriginalSize ?? 0;
|
||||
|
||||
public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime;
|
||||
|
||||
public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime;
|
||||
|
||||
public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime;
|
||||
|
||||
public override DateTime? ArchivedTime => null;
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
}
|
||||
}
|
||||
56
src/SharpCompress/Common/Arj/ArjFilePart.cs
Normal file
56
src/SharpCompress/Common/Arj/ArjFilePart.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Compressors.Arj;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Arj
|
||||
{
|
||||
public class ArjFilePart : FilePart
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
internal ArjLocalHeader Header { get; set; }
|
||||
|
||||
internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream)
|
||||
: base(localArjHeader.ArchiveEncoding)
|
||||
{
|
||||
_stream = seekableStream;
|
||||
Header = localArjHeader;
|
||||
}
|
||||
|
||||
internal override string? FilePartName => Header.Name;
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
Stream compressedStream;
|
||||
switch (Header.CompressionMethod)
|
||||
{
|
||||
case CompressionMethod.Stored:
|
||||
compressedStream = new ReadOnlySubStream(
|
||||
_stream,
|
||||
Header.DataStartPosition,
|
||||
Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionMethod.CompressedFastest:
|
||||
compressedStream = new LHDecoderStream(_stream, (int)Header.OriginalSize);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"CompressionMethod: " + Header.CompressionMethod
|
||||
);
|
||||
}
|
||||
return compressedStream;
|
||||
}
|
||||
return _stream.NotNull();
|
||||
}
|
||||
|
||||
internal override Stream GetRawStream() => _stream;
|
||||
}
|
||||
}
|
||||
36
src/SharpCompress/Common/Arj/ArjVolume.cs
Normal file
36
src/SharpCompress/Common/Arj/ArjVolume.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Rar;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Common.Arj
|
||||
{
|
||||
public class ArjVolume : Volume
|
||||
{
|
||||
public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
: base(stream, readerOptions, index) { }
|
||||
|
||||
public override bool IsFirstVolume
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ArjArchive is part of a multi-part archive.
|
||||
/// </summary>
|
||||
public override bool IsMultiVolume
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
internal IEnumerable<ArjFilePart> GetVolumeFileParts()
|
||||
{
|
||||
return new List<ArjFilePart>();
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
Normal file
142
src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public enum ArjHeaderType
|
||||
{
|
||||
MainHeader,
|
||||
LocalHeader,
|
||||
}
|
||||
|
||||
public abstract class ArjHeader
|
||||
{
|
||||
private const int FIRST_HDR_SIZE = 34;
|
||||
private const ushort ARJ_MAGIC = 0xEA60;
|
||||
|
||||
public ArjHeader(ArjHeaderType type)
|
||||
{
|
||||
ArjHeaderType = type;
|
||||
}
|
||||
|
||||
public ArjHeaderType ArjHeaderType { get; }
|
||||
public byte Flags { get; set; }
|
||||
public FileType FileType { get; set; }
|
||||
|
||||
public abstract ArjHeader? Read(Stream reader);
|
||||
|
||||
public byte[] ReadHeader(Stream stream)
|
||||
{
|
||||
// check for magic bytes
|
||||
Span<byte> magic = stackalloc byte[2];
|
||||
if (stream.Read(magic) != 2)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var magicValue = (ushort)(magic[0] | magic[1] << 8);
|
||||
if (magicValue != ARJ_MAGIC)
|
||||
{
|
||||
throw new InvalidDataException("Not an ARJ file (wrong magic bytes)");
|
||||
}
|
||||
|
||||
// read header_size
|
||||
byte[] headerBytes = new byte[2];
|
||||
stream.Read(headerBytes, 0, 2);
|
||||
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
|
||||
if (headerSize < 1)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var body = new byte[headerSize];
|
||||
var read = stream.Read(body, 0, headerSize);
|
||||
if (read < headerSize)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
read = stream.Read(crc, 0, 4);
|
||||
var checksum = Crc32Stream.Compute(body);
|
||||
// Compute the hash value
|
||||
if (checksum != BitConverter.ToUInt32(crc, 0))
|
||||
{
|
||||
throw new InvalidDataException("Header checksum is invalid");
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
protected List<byte[]> ReadExtendedHeaders(Stream reader)
|
||||
{
|
||||
List<byte[]> extendedHeader = new List<byte[]>();
|
||||
byte[] buffer = new byte[2];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int bytesRead = reader.Read(buffer, 0, 2);
|
||||
if (bytesRead < 2)
|
||||
{
|
||||
throw new EndOfStreamException(
|
||||
"Unexpected end of stream while reading extended header size."
|
||||
);
|
||||
}
|
||||
|
||||
var extHeaderSize = (ushort)(buffer[0] | (buffer[1] << 8));
|
||||
if (extHeaderSize == 0)
|
||||
{
|
||||
return extendedHeader;
|
||||
}
|
||||
|
||||
byte[] header = new byte[extHeaderSize];
|
||||
bytesRead = reader.Read(header, 0, extHeaderSize);
|
||||
if (bytesRead < extHeaderSize)
|
||||
{
|
||||
throw new EndOfStreamException(
|
||||
"Unexpected end of stream while reading extended header data."
|
||||
);
|
||||
}
|
||||
|
||||
byte[] crc = new byte[4];
|
||||
bytesRead = reader.Read(crc, 0, 4);
|
||||
if (bytesRead < 4)
|
||||
{
|
||||
throw new EndOfStreamException(
|
||||
"Unexpected end of stream while reading extended header CRC."
|
||||
);
|
||||
}
|
||||
|
||||
var checksum = Crc32Stream.Compute(header);
|
||||
if (checksum != BitConverter.ToUInt32(crc, 0))
|
||||
{
|
||||
throw new InvalidDataException("Extended header checksum is invalid");
|
||||
}
|
||||
|
||||
extendedHeader.Add(header);
|
||||
}
|
||||
}
|
||||
|
||||
// Flag helpers
|
||||
public bool IsGabled => (Flags & 0x01) != 0;
|
||||
public bool IsAnsiPage => (Flags & 0x02) != 0;
|
||||
public bool IsVolume => (Flags & 0x04) != 0;
|
||||
public bool IsArjProtected => (Flags & 0x08) != 0;
|
||||
public bool IsPathSym => (Flags & 0x10) != 0;
|
||||
public bool IsBackup => (Flags & 0x20) != 0;
|
||||
public bool IsSecured => (Flags & 0x40) != 0;
|
||||
public bool IsAltName => (Flags & 0x80) != 0;
|
||||
|
||||
public static FileType FileTypeFromByte(byte value)
|
||||
{
|
||||
return Enum.IsDefined(typeof(FileType), value)
|
||||
? (FileType)value
|
||||
: Headers.FileType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
Normal file
161
src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public class ArjLocalHeader : ArjHeader
|
||||
{
|
||||
public ArchiveEncoding ArchiveEncoding { get; }
|
||||
public long DataStartPosition { get; protected set; }
|
||||
|
||||
public byte ArchiverVersionNumber { get; set; }
|
||||
public byte MinVersionToExtract { get; set; }
|
||||
public HostOS HostOS { get; set; }
|
||||
public CompressionMethod CompressionMethod { get; set; }
|
||||
public DosDateTime DateTimeModified { get; set; } = new DosDateTime(0);
|
||||
public long CompressedSize { get; set; }
|
||||
public long OriginalSize { get; set; }
|
||||
public long OriginalCrc32 { get; set; }
|
||||
public int FileSpecPosition { get; set; }
|
||||
public int FileAccessMode { get; set; }
|
||||
public byte FirstChapter { get; set; }
|
||||
public byte LastChapter { get; set; }
|
||||
public long ExtendedFilePosition { get; set; }
|
||||
public DosDateTime DateTimeAccessed { get; set; } = new DosDateTime(0);
|
||||
public DosDateTime DateTimeCreated { get; set; } = new DosDateTime(0);
|
||||
public long OriginalSizeEvenForVolumes { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Comment { get; set; } = string.Empty;
|
||||
|
||||
private const byte StdHdrSize = 30;
|
||||
private const byte R9HdrSize = 46;
|
||||
|
||||
public ArjLocalHeader(ArchiveEncoding archiveEncoding)
|
||||
: base(ArjHeaderType.LocalHeader)
|
||||
{
|
||||
ArchiveEncoding =
|
||||
archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
|
||||
}
|
||||
|
||||
public override ArjHeader? Read(Stream stream)
|
||||
{
|
||||
var body = ReadHeader(stream);
|
||||
if (body.Length > 0)
|
||||
{
|
||||
ReadExtendedHeaders(stream);
|
||||
var header = LoadFrom(body);
|
||||
header.DataStartPosition = stream.Position;
|
||||
return header;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArjLocalHeader LoadFrom(byte[] headerBytes)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
int ReadInt16()
|
||||
{
|
||||
if (offset + 1 >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8;
|
||||
offset += 2;
|
||||
return v;
|
||||
}
|
||||
long ReadInt32()
|
||||
{
|
||||
if (offset + 3 >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
long v =
|
||||
headerBytes[offset] & 0xFF
|
||||
| (headerBytes[offset + 1] & 0xFF) << 8
|
||||
| (headerBytes[offset + 2] & 0xFF) << 16
|
||||
| (headerBytes[offset + 3] & 0xFF) << 24;
|
||||
offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
byte headerSize = headerBytes[offset++];
|
||||
ArchiverVersionNumber = headerBytes[offset++];
|
||||
MinVersionToExtract = headerBytes[offset++];
|
||||
HostOS hostOS = (HostOS)headerBytes[offset++];
|
||||
Flags = headerBytes[offset++];
|
||||
CompressionMethod = CompressionMethodFromByte(headerBytes[offset++]);
|
||||
FileType = FileTypeFromByte(headerBytes[offset++]);
|
||||
|
||||
offset++; // Skip 1 byte
|
||||
|
||||
var rawTimestamp = ReadInt32();
|
||||
DateTimeModified =
|
||||
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
|
||||
|
||||
CompressedSize = ReadInt32();
|
||||
OriginalSize = ReadInt32();
|
||||
OriginalCrc32 = ReadInt32();
|
||||
FileSpecPosition = ReadInt16();
|
||||
FileAccessMode = ReadInt16();
|
||||
|
||||
FirstChapter = headerBytes[offset++];
|
||||
LastChapter = headerBytes[offset++];
|
||||
|
||||
ExtendedFilePosition = 0;
|
||||
OriginalSizeEvenForVolumes = 0;
|
||||
|
||||
if (headerSize > StdHdrSize)
|
||||
{
|
||||
ExtendedFilePosition = ReadInt32();
|
||||
|
||||
if (headerSize >= R9HdrSize)
|
||||
{
|
||||
rawTimestamp = ReadInt32();
|
||||
DateTimeAccessed =
|
||||
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
|
||||
rawTimestamp = ReadInt32();
|
||||
DateTimeCreated =
|
||||
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
|
||||
OriginalSizeEvenForVolumes = ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
Name = Encoding.ASCII.GetString(
|
||||
headerBytes,
|
||||
offset,
|
||||
Array.IndexOf(headerBytes, (byte)0, offset) - offset
|
||||
);
|
||||
offset += Name.Length + 1;
|
||||
|
||||
Comment = Encoding.ASCII.GetString(
|
||||
headerBytes,
|
||||
offset,
|
||||
Array.IndexOf(headerBytes, (byte)0, offset) - offset
|
||||
);
|
||||
offset += Comment.Length + 1;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static CompressionMethod CompressionMethodFromByte(byte value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
0 => CompressionMethod.Stored,
|
||||
1 => CompressionMethod.CompressedMost,
|
||||
2 => CompressionMethod.Compressed,
|
||||
3 => CompressionMethod.CompressedFaster,
|
||||
4 => CompressionMethod.CompressedFastest,
|
||||
8 => CompressionMethod.NoDataNoCrc,
|
||||
9 => CompressionMethod.NoData,
|
||||
_ => CompressionMethod.Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
Normal file
138
src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public class ArjMainHeader : ArjHeader
|
||||
{
|
||||
private const int FIRST_HDR_SIZE = 34;
|
||||
private const ushort ARJ_MAGIC = 0xEA60;
|
||||
|
||||
public ArchiveEncoding ArchiveEncoding { get; }
|
||||
|
||||
public int ArchiverVersionNumber { get; private set; }
|
||||
public int MinVersionToExtract { get; private set; }
|
||||
public HostOS HostOs { get; private set; }
|
||||
public int SecurityVersion { get; private set; }
|
||||
public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0);
|
||||
public long CompressedSize { get; private set; }
|
||||
public long ArchiveSize { get; private set; }
|
||||
public long SecurityEnvelope { get; private set; }
|
||||
public int FileSpecPosition { get; private set; }
|
||||
public int SecurityEnvelopeLength { get; private set; }
|
||||
public int EncryptionVersion { get; private set; }
|
||||
public int LastChapter { get; private set; }
|
||||
|
||||
public int ArjProtectionFactor { get; private set; }
|
||||
public int Flags2 { get; private set; }
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public string Comment { get; private set; } = string.Empty;
|
||||
|
||||
public ArjMainHeader(ArchiveEncoding archiveEncoding)
|
||||
: base(ArjHeaderType.MainHeader)
|
||||
{
|
||||
ArchiveEncoding =
|
||||
archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
|
||||
}
|
||||
|
||||
public override ArjHeader? Read(Stream stream)
|
||||
{
|
||||
var body = ReadHeader(stream);
|
||||
ReadExtendedHeaders(stream);
|
||||
return LoadFrom(body);
|
||||
}
|
||||
|
||||
public ArjMainHeader LoadFrom(byte[] headerBytes)
|
||||
{
|
||||
var offset = 1;
|
||||
|
||||
byte ReadByte()
|
||||
{
|
||||
if (offset >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return (byte)(headerBytes[offset++] & 0xFF);
|
||||
}
|
||||
|
||||
int ReadInt16()
|
||||
{
|
||||
if (offset + 1 >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8;
|
||||
offset += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
long ReadInt32()
|
||||
{
|
||||
if (offset + 3 >= headerBytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
long v =
|
||||
headerBytes[offset] & 0xFF
|
||||
| (headerBytes[offset + 1] & 0xFF) << 8
|
||||
| (headerBytes[offset + 2] & 0xFF) << 16
|
||||
| (headerBytes[offset + 3] & 0xFF) << 24;
|
||||
offset += 4;
|
||||
return v;
|
||||
}
|
||||
string ReadNullTerminatedString(byte[] x, int startIndex)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
int i = startIndex;
|
||||
|
||||
while (i < x.Length && x[i] != 0)
|
||||
{
|
||||
result.Append((char)x[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Skip the null terminator
|
||||
i++;
|
||||
if (i < x.Length)
|
||||
{
|
||||
byte[] remainder = new byte[x.Length - i];
|
||||
Array.Copy(x, i, remainder, 0, remainder.Length);
|
||||
x = remainder;
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
ArchiverVersionNumber = ReadByte();
|
||||
MinVersionToExtract = ReadByte();
|
||||
|
||||
var hostOsByte = ReadByte();
|
||||
HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown;
|
||||
|
||||
Flags = ReadByte();
|
||||
SecurityVersion = ReadByte();
|
||||
FileType = FileTypeFromByte(ReadByte());
|
||||
|
||||
offset++; // skip reserved
|
||||
|
||||
CreationDateTime = new DosDateTime((int)ReadInt32());
|
||||
CompressedSize = ReadInt32();
|
||||
ArchiveSize = ReadInt32();
|
||||
|
||||
SecurityEnvelope = ReadInt32();
|
||||
FileSpecPosition = ReadInt16();
|
||||
SecurityEnvelopeLength = ReadInt16();
|
||||
|
||||
EncryptionVersion = ReadByte();
|
||||
LastChapter = ReadByte();
|
||||
|
||||
Name = ReadNullTerminatedString(headerBytes, offset);
|
||||
Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs
Normal file
20
src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public enum CompressionMethod
|
||||
{
|
||||
Stored = 0,
|
||||
CompressedMost = 1,
|
||||
Compressed = 2,
|
||||
CompressedFaster = 3,
|
||||
CompressedFastest = 4,
|
||||
NoDataNoCrc = 8,
|
||||
NoData = 9,
|
||||
Unknown,
|
||||
}
|
||||
}
|
||||
37
src/SharpCompress/Common/Arj/Headers/DosDateTime.cs
Normal file
37
src/SharpCompress/Common/Arj/Headers/DosDateTime.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public class DosDateTime
|
||||
{
|
||||
public DateTime DateTime { get; }
|
||||
|
||||
public DosDateTime(long dosValue)
|
||||
{
|
||||
// Ensure only the lower 32 bits are used
|
||||
int value = unchecked((int)(dosValue & 0xFFFFFFFF));
|
||||
|
||||
var date = (value >> 16) & 0xFFFF;
|
||||
var time = value & 0xFFFF;
|
||||
|
||||
var day = date & 0x1F;
|
||||
var month = (date >> 5) & 0x0F;
|
||||
var year = ((date >> 9) & 0x7F) + 1980;
|
||||
|
||||
var second = (time & 0x1F) * 2;
|
||||
var minute = (time >> 5) & 0x3F;
|
||||
var hour = (time >> 11) & 0x1F;
|
||||
|
||||
try
|
||||
{
|
||||
DateTime = new DateTime(year, month, day, hour, minute, second);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DateTime = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => DateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
}
|
||||
13
src/SharpCompress/Common/Arj/Headers/FileType.cs
Normal file
13
src/SharpCompress/Common/Arj/Headers/FileType.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public enum FileType : byte
|
||||
{
|
||||
Binary = 0,
|
||||
Text7Bit = 1,
|
||||
CommentHeader = 2,
|
||||
Directory = 3,
|
||||
VolumeLabel = 4,
|
||||
ChapterLabel = 5,
|
||||
Unknown = 255,
|
||||
}
|
||||
}
|
||||
19
src/SharpCompress/Common/Arj/Headers/HostOS.cs
Normal file
19
src/SharpCompress/Common/Arj/Headers/HostOS.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SharpCompress.Common.Arj.Headers
|
||||
{
|
||||
public enum HostOS
|
||||
{
|
||||
MsDos = 0,
|
||||
PrimOS = 1,
|
||||
Unix = 2,
|
||||
Amiga = 3,
|
||||
MacOs = 4,
|
||||
OS2 = 5,
|
||||
AppleGS = 6,
|
||||
AtariST = 7,
|
||||
NeXT = 8,
|
||||
VaxVMS = 9,
|
||||
Win95 = 10,
|
||||
Win32 = 11,
|
||||
Unknown = 255,
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,5 @@ public enum CompressionType
|
||||
Crushed,
|
||||
Distilled,
|
||||
ZStandard,
|
||||
ArjLZ77,
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ public class EntryStream : Stream, IStreamStack
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
if (!(_completed || _reader.Cancelled))
|
||||
{
|
||||
SkipEntry();
|
||||
@@ -81,12 +86,6 @@ public class EntryStream : Stream, IStreamStack
|
||||
lzmaStream.Flush(); //Lzma over reads. Knock it back
|
||||
}
|
||||
}
|
||||
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugDispose(typeof(EntryStream));
|
||||
#endif
|
||||
@@ -97,6 +96,11 @@ public class EntryStream : Stream, IStreamStack
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
if (!(_completed || _reader.Cancelled))
|
||||
{
|
||||
await SkipEntryAsync().ConfigureAwait(false);
|
||||
@@ -114,12 +118,6 @@ public class EntryStream : Stream, IStreamStack
|
||||
await lzmaStream.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugDispose(typeof(EntryStream));
|
||||
#endif
|
||||
@@ -204,4 +202,11 @@ public class EntryStream : Stream, IStreamStack
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -7,6 +8,15 @@ namespace SharpCompress.Common;
|
||||
|
||||
internal static class ExtractionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the appropriate StringComparison for path checks based on the file system.
|
||||
/// Windows uses case-insensitive file systems, while Unix-like systems use case-sensitive file systems.
|
||||
/// </summary>
|
||||
private static StringComparison PathComparison =>
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
@@ -48,7 +58,7 @@ internal static class ExtractionMethods
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to create a directory outside of the destination directory."
|
||||
@@ -68,12 +78,7 @@ internal static class ExtractionMethods
|
||||
{
|
||||
destinationFileName = Path.GetFullPath(destinationFileName);
|
||||
|
||||
if (
|
||||
!destinationFileName.StartsWith(
|
||||
fullDestinationDirectoryPath,
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to write a file outside of the destination directory."
|
||||
@@ -158,7 +163,7 @@ internal static class ExtractionMethods
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to create a directory outside of the destination directory."
|
||||
@@ -178,12 +183,7 @@ internal static class ExtractionMethods
|
||||
{
|
||||
destinationFileName = Path.GetFullPath(destinationFileName);
|
||||
|
||||
if (
|
||||
!destinationFileName.StartsWith(
|
||||
fullDestinationDirectoryPath,
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to write a file outside of the destination directory."
|
||||
|
||||
@@ -24,8 +24,14 @@ internal sealed class GZipFilePart : FilePart
|
||||
stream.Position = stream.Length - 8;
|
||||
ReadTrailer();
|
||||
stream.Position = position;
|
||||
EntryStartPosition = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-seekable streams, we can't read the trailer or track position.
|
||||
// Set to 0 since the stream will be read sequentially from its current position.
|
||||
EntryStartPosition = 0;
|
||||
}
|
||||
EntryStartPosition = stream.Position;
|
||||
}
|
||||
|
||||
internal long EntryStartPosition { get; }
|
||||
|
||||
@@ -67,6 +67,7 @@ internal class SevenZipFilePart : FilePart
|
||||
}
|
||||
}
|
||||
|
||||
private const uint K_COPY = 0x0;
|
||||
private const uint K_LZMA2 = 0x21;
|
||||
private const uint K_LZMA = 0x030101;
|
||||
private const uint K_PPMD = 0x030401;
|
||||
@@ -82,6 +83,7 @@ internal class SevenZipFilePart : FilePart
|
||||
var coder = Folder.NotNull()._coders.First();
|
||||
return coder._methodId._id switch
|
||||
{
|
||||
K_COPY => CompressionType.None,
|
||||
K_LZMA or K_LZMA2 => CompressionType.LZMA,
|
||||
K_PPMD => CompressionType.PPMd,
|
||||
K_B_ZIP2 => CompressionType.BZip2,
|
||||
|
||||
@@ -36,11 +36,10 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
|
||||
throw new ArgumentException("Stream must be a SharpCompressStream", nameof(stream));
|
||||
}
|
||||
}
|
||||
SharpCompressStream rewindableStream = (SharpCompressStream)stream;
|
||||
var rewindableStream = (SharpCompressStream)stream;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ZipHeader? header;
|
||||
var reader = new BinaryReader(rewindableStream);
|
||||
uint headerBytes = 0;
|
||||
if (
|
||||
@@ -155,7 +154,7 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
|
||||
}
|
||||
|
||||
_lastEntryHeader = null;
|
||||
header = ReadHeader(headerBytes, reader);
|
||||
var header = ReadHeader(headerBytes, reader);
|
||||
if (header is null)
|
||||
{
|
||||
yield break;
|
||||
|
||||
@@ -24,10 +24,29 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.ADC;
|
||||
|
||||
/// <summary>
|
||||
/// Result of an ADC decompression operation
|
||||
/// </summary>
|
||||
public class AdcDecompressResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of bytes read from input
|
||||
/// </summary>
|
||||
public int BytesRead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompressed output buffer
|
||||
/// </summary>
|
||||
public byte[]? Output { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides static methods for decompressing Apple Data Compression data
|
||||
/// </summary>
|
||||
@@ -78,6 +97,173 @@ public static class ADCBase
|
||||
public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) =>
|
||||
Decompress(new MemoryStream(input), out output, bufferSize);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a byte buffer asynchronously that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Compressed buffer</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Result containing bytes read and decompressed data</returns>
|
||||
public static async Task<AdcDecompressResult> DecompressAsync(
|
||||
byte[] input,
|
||||
int bufferSize = 262144,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a stream asynchronously that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Stream containing compressed data</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Result containing bytes read and decompressed data</returns>
|
||||
public static async Task<AdcDecompressResult> DecompressAsync(
|
||||
Stream input,
|
||||
int bufferSize = 262144,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var result = new AdcDecompressResult();
|
||||
|
||||
if (input is null || input.Length == 0)
|
||||
{
|
||||
result.BytesRead = 0;
|
||||
result.Output = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
var start = (int)input.Position;
|
||||
var position = (int)input.Position;
|
||||
int chunkSize;
|
||||
int offset;
|
||||
int chunkType;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
var outPosition = 0;
|
||||
var full = false;
|
||||
byte[] temp = ArrayPool<byte>.Shared.Rent(3);
|
||||
|
||||
try
|
||||
{
|
||||
while (position < input.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var readByte = input.ReadByte();
|
||||
if (readByte == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
chunkType = GetChunkType((byte)readByte);
|
||||
|
||||
switch (chunkType)
|
||||
{
|
||||
case PLAIN:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var readCount = await input.ReadAsync(
|
||||
buffer,
|
||||
outPosition,
|
||||
chunkSize,
|
||||
cancellationToken
|
||||
);
|
||||
outPosition += readCount;
|
||||
position += readCount + 1;
|
||||
break;
|
||||
case TWO_BYTE:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
temp[0] = (byte)readByte;
|
||||
temp[1] = (byte)input.ReadByte();
|
||||
offset = GetOffset(temp.AsSpan(0, 2));
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
var lastByte = buffer[outPosition - 1];
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 2;
|
||||
}
|
||||
|
||||
break;
|
||||
case THREE_BYTE:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
temp[0] = (byte)readByte;
|
||||
temp[1] = (byte)input.ReadByte();
|
||||
temp[2] = (byte)input.ReadByte();
|
||||
offset = GetOffset(temp.AsSpan(0, 3));
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
var lastByte = buffer[outPosition - 1];
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
|
||||
position += 3;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (full)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var output = new byte[outPosition];
|
||||
Array.Copy(buffer, output, outPosition);
|
||||
result.BytesRead = position - start;
|
||||
result.Output = output;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
ArrayPool<byte>.Shared.Return(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a stream that's compressed with ADC
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.ADC;
|
||||
@@ -187,6 +189,76 @@ public sealed class ADCStream : Stream, IStreamStack
|
||||
return copied;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (buffer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
if (offset < buffer.GetLowerBound(0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
if ((offset + count) > buffer.GetLength(0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
|
||||
if (_outBuffer is null)
|
||||
{
|
||||
var result = await ADCBase.DecompressAsync(
|
||||
_stream,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
_outBuffer = result.Output;
|
||||
_outPosition = 0;
|
||||
}
|
||||
|
||||
var inPosition = offset;
|
||||
var toCopy = count;
|
||||
var copied = 0;
|
||||
|
||||
while (_outPosition + toCopy >= _outBuffer.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var piece = _outBuffer.Length - _outPosition;
|
||||
Array.Copy(_outBuffer, _outPosition, buffer, inPosition, piece);
|
||||
inPosition += piece;
|
||||
copied += piece;
|
||||
_position += piece;
|
||||
toCopy -= piece;
|
||||
var result = await ADCBase.DecompressAsync(
|
||||
_stream,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
_outBuffer = result.Output;
|
||||
_outPosition = 0;
|
||||
if (result.BytesRead == 0 || _outBuffer is null || _outBuffer.Length == 0)
|
||||
{
|
||||
return copied;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Copy(_outBuffer, _outPosition, buffer, inPosition, toCopy);
|
||||
_outPosition += toCopy;
|
||||
_position += toCopy;
|
||||
copied += toCopy;
|
||||
return copied;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
60
src/SharpCompress/Compressors/Arj/BitReader.cs
Normal file
60
src/SharpCompress/Compressors/Arj/BitReader.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Arj
|
||||
{
|
||||
[CLSCompliant(true)]
|
||||
public sealed class BitReader
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private int _bitBuffer;
|
||||
private int _bitsRemaining;
|
||||
private bool _disposed;
|
||||
|
||||
public BitReader(Stream input)
|
||||
{
|
||||
_stream = input ?? throw new ArgumentNullException(nameof(input));
|
||||
if (!input.CanRead)
|
||||
throw new ArgumentException("Stream must be readable.", nameof(input));
|
||||
}
|
||||
|
||||
public int ReadBits(int count)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(BitReader));
|
||||
|
||||
if (count <= 0 || count > 32)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(count),
|
||||
"Bit count must be between 1 and 32."
|
||||
);
|
||||
|
||||
int result = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (_bitsRemaining == 0)
|
||||
{
|
||||
int nextByte = _stream.ReadByte();
|
||||
if (nextByte == -1)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
_bitBuffer = nextByte;
|
||||
_bitsRemaining = 8;
|
||||
}
|
||||
|
||||
// hoogste bit eerst
|
||||
result = (result << 1) | ((_bitBuffer >> 7) & 1);
|
||||
_bitBuffer <<= 1;
|
||||
_bitsRemaining--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void AlignToByte()
|
||||
{
|
||||
_bitsRemaining = 0;
|
||||
_bitBuffer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
191
src/SharpCompress/Compressors/Arj/LHDecoderStream.cs
Normal file
191
src/SharpCompress/Compressors/Arj/LHDecoderStream.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Arj
|
||||
{
|
||||
[CLSCompliant(true)]
|
||||
public sealed class LHDecoderStream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => _stream;
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly BitReader _bitReader;
|
||||
private readonly Stream _stream;
|
||||
|
||||
// Buffer containing *all* bytes decoded so far.
|
||||
private readonly List<byte> _buffer = new();
|
||||
|
||||
private long _readPosition;
|
||||
private readonly int _originalSize;
|
||||
private bool _finishedDecoding;
|
||||
private bool _disposed;
|
||||
|
||||
private const int THRESHOLD = 3;
|
||||
|
||||
public LHDecoderStream(Stream compressedStream, int originalSize)
|
||||
{
|
||||
_stream = compressedStream ?? throw new ArgumentNullException(nameof(compressedStream));
|
||||
if (!compressedStream.CanRead)
|
||||
throw new ArgumentException(
|
||||
"compressedStream must be readable.",
|
||||
nameof(compressedStream)
|
||||
);
|
||||
|
||||
_bitReader = new BitReader(compressedStream);
|
||||
_originalSize = originalSize;
|
||||
_readPosition = 0;
|
||||
_finishedDecoding = (originalSize == 0);
|
||||
}
|
||||
|
||||
public Stream BaseStream => _stream;
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _originalSize;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _readPosition;
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a single element (literal or back-reference) and appends it to _buffer.
|
||||
/// Returns true if data was added, or false if all input has already been decoded.
|
||||
/// </summary>
|
||||
private bool DecodeNext()
|
||||
{
|
||||
if (_buffer.Count >= _originalSize)
|
||||
{
|
||||
_finishedDecoding = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = DecodeVal(0, 7);
|
||||
if (len == 0)
|
||||
{
|
||||
byte nextChar = (byte)_bitReader.ReadBits(8);
|
||||
_buffer.Add(nextChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
int repCount = len + THRESHOLD - 1;
|
||||
int backPtr = DecodeVal(9, 13);
|
||||
|
||||
if (backPtr >= _buffer.Count)
|
||||
throw new InvalidDataException("Invalid back_ptr in LH stream");
|
||||
|
||||
int srcIndex = _buffer.Count - 1 - backPtr;
|
||||
for (int j = 0; j < repCount && _buffer.Count < _originalSize; j++)
|
||||
{
|
||||
byte b = _buffer[srcIndex];
|
||||
_buffer.Add(b);
|
||||
srcIndex++;
|
||||
// srcIndex may grow; it's allowed (source region can overlap destination)
|
||||
}
|
||||
}
|
||||
|
||||
if (_buffer.Count >= _originalSize)
|
||||
{
|
||||
_finishedDecoding = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int DecodeVal(int from, int to)
|
||||
{
|
||||
int add = 0;
|
||||
int bit = from;
|
||||
|
||||
while (bit < to && _bitReader.ReadBits(1) == 1)
|
||||
{
|
||||
add |= 1 << bit;
|
||||
bit++;
|
||||
}
|
||||
|
||||
int res = bit > 0 ? _bitReader.ReadBits(bit) : 0;
|
||||
return res + add;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads decompressed bytes into buffer[offset..offset+count].
|
||||
/// The method decodes additional data on demand when needed.
|
||||
/// </summary>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(LHDecoderStream));
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
if (offset < 0 || count < 0 || offset + count > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException("offset/count");
|
||||
|
||||
if (_readPosition >= _originalSize)
|
||||
return 0; // EOF
|
||||
|
||||
int totalRead = 0;
|
||||
|
||||
while (totalRead < count && _readPosition < _originalSize)
|
||||
{
|
||||
if (_readPosition >= _buffer.Count)
|
||||
{
|
||||
bool had = DecodeNext();
|
||||
if (!had)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int available = _buffer.Count - (int)_readPosition;
|
||||
if (available <= 0)
|
||||
{
|
||||
if (!_finishedDecoding)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(available, count - totalRead);
|
||||
_buffer.CopyTo((int)_readPosition, buffer, offset + totalRead, toCopy);
|
||||
|
||||
_readPosition += toCopy;
|
||||
totalRead += toCopy;
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.BZip2;
|
||||
@@ -96,13 +98,37 @@ public sealed class BZip2Stream : Stream, IStreamStack
|
||||
|
||||
public override void SetLength(long value) => stream.SetLength(value);
|
||||
|
||||
#if !NETFRAMEWORK&& !NETSTANDARD2_0
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
|
||||
public override int Read(Span<byte> buffer) => stream.Read(buffer);
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer) => stream.Write(buffer);
|
||||
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override async ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
stream.Write(buffer, offset, count);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
/*
|
||||
@@ -1127,6 +1129,28 @@ internal class CBZip2InputStream : Stream, IStreamStack
|
||||
return k;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var c = -1;
|
||||
int k;
|
||||
for (k = 0; k < count; ++k)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
c = ReadByte();
|
||||
if (c == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buffer[k + offset] = (byte)c;
|
||||
}
|
||||
return Task.FromResult(k);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => 0;
|
||||
|
||||
public override void SetLength(long value) { }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
/*
|
||||
@@ -2022,6 +2024,21 @@ internal sealed class CBZip2OutputStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
for (var k = 0; k < count; ++k)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
WriteByte(buffer[k + offset]);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.IO;
|
||||
@@ -39,7 +39,6 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
private const int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
private Stream _stream;
|
||||
private CompressionMode _mode;
|
||||
private InflaterManaged _inflater;
|
||||
private byte[] _buffer;
|
||||
|
||||
@@ -62,61 +61,23 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream));
|
||||
}
|
||||
|
||||
InitializeInflater(stream, ZipCompressionMethod.Deflate64);
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(Deflate64Stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up this DeflateManagedStream to be used for Inflation/Decompression
|
||||
/// </summary>
|
||||
private void InitializeInflater(
|
||||
Stream stream,
|
||||
ZipCompressionMethod method = ZipCompressionMethod.Deflate
|
||||
)
|
||||
{
|
||||
Debug.Assert(stream != null);
|
||||
Debug.Assert(
|
||||
method == ZipCompressionMethod.Deflate || method == ZipCompressionMethod.Deflate64
|
||||
);
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream));
|
||||
}
|
||||
|
||||
_inflater = new InflaterManaged(method == ZipCompressionMethod.Deflate64);
|
||||
_inflater = new InflaterManaged(true);
|
||||
|
||||
_stream = stream;
|
||||
_mode = CompressionMode.Decompress;
|
||||
_buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(Deflate64Stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
|
||||
return (_mode == CompressionMode.Decompress && _stream.CanRead);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (_mode == CompressionMode.Compress && _stream.CanWrite);
|
||||
}
|
||||
}
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
@@ -138,7 +99,6 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
|
||||
public override int Read(byte[] array, int offset, int count)
|
||||
{
|
||||
EnsureDecompressionMode();
|
||||
ValidateParameters(array, offset, count);
|
||||
EnsureNotDisposed();
|
||||
|
||||
@@ -185,6 +145,106 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
return count - remainingCount;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] array,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
ValidateParameters(array, offset, count);
|
||||
EnsureNotDisposed();
|
||||
|
||||
int bytesRead;
|
||||
var currentOffset = offset;
|
||||
var remainingCount = count;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bytesRead = _inflater.Inflate(array, currentOffset, remainingCount);
|
||||
currentOffset += bytesRead;
|
||||
remainingCount -= bytesRead;
|
||||
|
||||
if (remainingCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_inflater.Finished())
|
||||
{
|
||||
// if we finished decompressing, we can't have anything left in the outputwindow.
|
||||
Debug.Assert(
|
||||
_inflater.AvailableOutput == 0,
|
||||
"We should have copied all stuff out!"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
var bytes = await _stream
|
||||
.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytes <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (bytes > _buffer.Length)
|
||||
{
|
||||
// The stream is either malicious or poorly implemented and returned a number of
|
||||
// bytes larger than the buffer supplied to it.
|
||||
throw new InvalidFormatException("Deflate64: invalid data");
|
||||
}
|
||||
|
||||
_inflater.SetInput(_buffer, 0, bytes);
|
||||
}
|
||||
|
||||
return count - remainingCount;
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
|
||||
// InflaterManaged doesn't have a Span-based Inflate method, so we need to work with arrays
|
||||
// For large buffers, we could rent from ArrayPool, but for simplicity we'll use the buffer's array if available
|
||||
if (
|
||||
System.Runtime.InteropServices.MemoryMarshal.TryGetArray<byte>(
|
||||
buffer,
|
||||
out var arraySegment
|
||||
)
|
||||
)
|
||||
{
|
||||
// Fast path: the Memory<byte> is backed by an array
|
||||
return await ReadAsync(
|
||||
arraySegment.Array!,
|
||||
arraySegment.Offset,
|
||||
arraySegment.Count,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow path: rent a temporary array
|
||||
var tempBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
try
|
||||
{
|
||||
var bytesRead = await ReadAsync(tempBuffer, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
tempBuffer.AsMemory(0, bytesRead).CopyTo(buffer);
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Buffers.ArrayPool<byte>.Shared.Return(tempBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void ValidateParameters(byte[] array, int offset, int count)
|
||||
{
|
||||
if (array is null)
|
||||
@@ -220,26 +280,6 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
private static void ThrowStreamClosedException() =>
|
||||
throw new ObjectDisposedException(null, "Deflate64: stream has been disposed");
|
||||
|
||||
private void EnsureDecompressionMode()
|
||||
{
|
||||
if (_mode != CompressionMode.Decompress)
|
||||
{
|
||||
ThrowCannotReadFromDeflateManagedStreamException();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowCannotReadFromDeflateManagedStreamException() =>
|
||||
throw new InvalidOperationException("Deflate64: cannot read from this stream");
|
||||
|
||||
private void EnsureCompressionMode()
|
||||
{
|
||||
if (_mode != CompressionMode.Compress)
|
||||
{
|
||||
ThrowCannotWriteToDeflateManagedStreamException();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowCannotWriteToDeflateManagedStreamException() =>
|
||||
throw new InvalidOperationException("Deflate64: cannot write to this stream");
|
||||
@@ -281,20 +321,17 @@ public sealed class Deflate64Stream : Stream, IStreamStack
|
||||
#endif
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stream = null;
|
||||
|
||||
try
|
||||
{
|
||||
_inflater?.Dispose();
|
||||
_inflater.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_inflater = null;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.Utilites;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -283,5 +285,70 @@ internal sealed class AesDecoderStream : DecoderStream2, IStreamStack
|
||||
return count;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (count == 0 || mWritten == mLimit)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mUnderflow > 0)
|
||||
{
|
||||
return HandleUnderflow(buffer, offset, count);
|
||||
}
|
||||
|
||||
// Need at least 16 bytes to proceed.
|
||||
if (mEnding - mOffset < 16)
|
||||
{
|
||||
Buffer.BlockCopy(mBuffer, mOffset, mBuffer, 0, mEnding - mOffset);
|
||||
mEnding -= mOffset;
|
||||
mOffset = 0;
|
||||
|
||||
do
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var read = await mStream
|
||||
.ReadAsync(mBuffer, mEnding, mBuffer.Length - mEnding, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read == 0)
|
||||
{
|
||||
// We are not done decoding and have less than 16 bytes.
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
mEnding += read;
|
||||
} while (mEnding - mOffset < 16);
|
||||
}
|
||||
|
||||
// We shouldn't return more data than we are limited to.
|
||||
if (count > mLimit - mWritten)
|
||||
{
|
||||
count = (int)(mLimit - mWritten);
|
||||
}
|
||||
|
||||
// We cannot transform less than 16 bytes into the target buffer,
|
||||
// but we also cannot return zero, so we need to handle this.
|
||||
if (count < 16)
|
||||
{
|
||||
return HandleUnderflow(buffer, offset, count);
|
||||
}
|
||||
|
||||
if (count > mEnding - mOffset)
|
||||
{
|
||||
count = mEnding - mOffset;
|
||||
}
|
||||
|
||||
// Otherwise we transform directly into the target buffer.
|
||||
var processed = mDecoder.TransformBlock(mBuffer, mOffset, count & ~15, buffer, offset);
|
||||
mOffset += processed;
|
||||
mWritten += processed;
|
||||
return processed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA;
|
||||
@@ -191,6 +193,18 @@ internal class Bcj2DecoderStream : DecoderStream2, IStreamStack
|
||||
return count;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
// Bcj2DecoderStream uses complex state machine with multiple streams
|
||||
return Task.FromResult(Read(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_mFinished)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an error in input stream occurs during decoding.
|
||||
/// </summary>
|
||||
internal class DataErrorException : Exception
|
||||
internal class DataErrorException : SharpCompressException
|
||||
{
|
||||
public DataErrorException()
|
||||
: base("Data Error") { }
|
||||
@@ -15,7 +16,7 @@ internal class DataErrorException : Exception
|
||||
/// <summary>
|
||||
/// The exception that is thrown when the value of an argument is outside the allowable range.
|
||||
/// </summary>
|
||||
internal class InvalidParamException : Exception
|
||||
internal class InvalidParamException : SharpCompressException
|
||||
{
|
||||
public InvalidParamException()
|
||||
: base("Invalid Parameter") { }
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA.LZ;
|
||||
|
||||
@@ -85,6 +87,12 @@ internal class OutWindow : IDisposable
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
public async Task ReleaseStreamAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
private void Flush()
|
||||
{
|
||||
if (_stream is null)
|
||||
@@ -104,6 +112,27 @@ internal class OutWindow : IDisposable
|
||||
_streamPos = _pos;
|
||||
}
|
||||
|
||||
private async Task FlushAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_stream is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var size = _pos - _streamPos;
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await _stream
|
||||
.WriteAsync(_buffer, _streamPos, size, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (_pos >= _windowSize)
|
||||
{
|
||||
_pos = 0;
|
||||
}
|
||||
_streamPos = _pos;
|
||||
}
|
||||
|
||||
public void CopyPending()
|
||||
{
|
||||
if (_pendingLen < 1)
|
||||
@@ -124,6 +153,26 @@ internal class OutWindow : IDisposable
|
||||
_pendingLen = rem;
|
||||
}
|
||||
|
||||
public async Task CopyPendingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_pendingLen < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var rem = _pendingLen;
|
||||
var pos = (_pendingDist < _pos ? _pos : _pos + _windowSize) - _pendingDist - 1;
|
||||
while (rem > 0 && HasSpace)
|
||||
{
|
||||
if (pos >= _windowSize)
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false);
|
||||
rem--;
|
||||
}
|
||||
_pendingLen = rem;
|
||||
}
|
||||
|
||||
public void CopyBlock(int distance, int len)
|
||||
{
|
||||
var rem = len;
|
||||
@@ -157,6 +206,43 @@ internal class OutWindow : IDisposable
|
||||
_pendingDist = distance;
|
||||
}
|
||||
|
||||
public async Task CopyBlockAsync(
|
||||
int distance,
|
||||
int len,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var rem = len;
|
||||
var pos = (distance < _pos ? _pos : _pos + _windowSize) - distance - 1;
|
||||
var targetSize = HasSpace ? (int)Math.Min(rem, _limit - _total) : 0;
|
||||
var sizeUntilWindowEnd = Math.Min(_windowSize - _pos, _windowSize - pos);
|
||||
var sizeUntilOverlap = Math.Abs(pos - _pos);
|
||||
var fastSize = Math.Min(Math.Min(sizeUntilWindowEnd, sizeUntilOverlap), targetSize);
|
||||
if (fastSize >= 2)
|
||||
{
|
||||
_buffer.AsSpan(pos, fastSize).CopyTo(_buffer.AsSpan(_pos, fastSize));
|
||||
_pos += fastSize;
|
||||
pos += fastSize;
|
||||
_total += fastSize;
|
||||
if (_pos >= _windowSize)
|
||||
{
|
||||
await FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
rem -= fastSize;
|
||||
}
|
||||
while (rem > 0 && HasSpace)
|
||||
{
|
||||
if (pos >= _windowSize)
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false);
|
||||
rem--;
|
||||
}
|
||||
_pendingLen = rem;
|
||||
_pendingDist = distance;
|
||||
}
|
||||
|
||||
public void PutByte(byte b)
|
||||
{
|
||||
_buffer[_pos++] = b;
|
||||
@@ -167,6 +253,16 @@ internal class OutWindow : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PutByteAsync(byte b, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_buffer[_pos++] = b;
|
||||
_total++;
|
||||
if (_pos >= _windowSize)
|
||||
{
|
||||
await FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public byte GetByte(int distance)
|
||||
{
|
||||
var pos = _pos - distance - 1;
|
||||
@@ -207,6 +303,44 @@ internal class OutWindow : IDisposable
|
||||
return len - size;
|
||||
}
|
||||
|
||||
public async Task<int> CopyStreamAsync(
|
||||
Stream stream,
|
||||
int len,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var size = len;
|
||||
while (size > 0 && _pos < _windowSize && _total < _limit)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var curSize = _windowSize - _pos;
|
||||
if (curSize > _limit - _total)
|
||||
{
|
||||
curSize = (int)(_limit - _total);
|
||||
}
|
||||
if (curSize > size)
|
||||
{
|
||||
curSize = size;
|
||||
}
|
||||
var numReadBytes = await stream
|
||||
.ReadAsync(_buffer, _pos, curSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (numReadBytes == 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
size -= numReadBytes;
|
||||
_pos += numReadBytes;
|
||||
_total += numReadBytes;
|
||||
if (_pos >= _windowSize)
|
||||
{
|
||||
await FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return len - size;
|
||||
}
|
||||
|
||||
public void SetLimit(long size) => _limit = _total + size;
|
||||
|
||||
public bool HasSpace => _pos < _windowSize && _total < _limit;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Crypto;
|
||||
using SharpCompress.IO;
|
||||
@@ -157,6 +159,11 @@ public sealed class LZipStream : Stream, IStreamStack
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
|
||||
public override ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => _stream.ReadAsync(buffer, cancellationToken);
|
||||
|
||||
public override int Read(Span<byte> buffer) => _stream.Read(buffer);
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
@@ -179,6 +186,25 @@ public sealed class LZipStream : Stream, IStreamStack
|
||||
++_writeCount;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
) => _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
_writeCount += count;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using SharpCompress.Compressors.LZMA.LZ;
|
||||
using SharpCompress.Compressors.LZMA.RangeCoder;
|
||||
@@ -199,6 +200,9 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
[MemberNotNull(nameof(_outWindow))]
|
||||
#endif
|
||||
private void CreateDictionary()
|
||||
{
|
||||
if (_dictionarySize < 0)
|
||||
@@ -309,6 +313,42 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
|
||||
_outWindow = null;
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task CodeAsync(
|
||||
Stream inStream,
|
||||
Stream outStream,
|
||||
long inSize,
|
||||
long outSize,
|
||||
ICodeProgress progress,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (_outWindow is null)
|
||||
{
|
||||
CreateDictionary();
|
||||
}
|
||||
_outWindow.Init(outStream);
|
||||
if (outSize > 0)
|
||||
{
|
||||
_outWindow.SetLimit(outSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
_outWindow.SetLimit(long.MaxValue - _outWindow.Total);
|
||||
}
|
||||
|
||||
var rangeDecoder = new RangeCoder.Decoder();
|
||||
rangeDecoder.Init(inStream);
|
||||
|
||||
await CodeAsync(_dictionarySize, _outWindow, rangeDecoder, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await _outWindow.ReleaseStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
rangeDecoder.ReleaseStream();
|
||||
|
||||
_outWindow.Dispose();
|
||||
_outWindow = null;
|
||||
}
|
||||
|
||||
internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder)
|
||||
{
|
||||
var dictionarySizeCheck = Math.Max(dictionarySize, 1);
|
||||
@@ -435,6 +475,143 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async System.Threading.Tasks.Task<bool> CodeAsync(
|
||||
int dictionarySize,
|
||||
OutWindow outWindow,
|
||||
RangeCoder.Decoder rangeDecoder,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var dictionarySizeCheck = Math.Max(dictionarySize, 1);
|
||||
|
||||
await outWindow.CopyPendingAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
while (outWindow.HasSpace)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var posState = (uint)outWindow.Total & _posStateMask;
|
||||
if (
|
||||
_isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState]
|
||||
.Decode(rangeDecoder) == 0
|
||||
)
|
||||
{
|
||||
byte b;
|
||||
var prevByte = outWindow.GetByte(0);
|
||||
if (!_state.IsCharState())
|
||||
{
|
||||
b = _literalDecoder.DecodeWithMatchByte(
|
||||
rangeDecoder,
|
||||
(uint)outWindow.Total,
|
||||
prevByte,
|
||||
outWindow.GetByte((int)_rep0)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow.Total, prevByte);
|
||||
}
|
||||
await outWindow.PutByteAsync(b, cancellationToken).ConfigureAwait(false);
|
||||
_state.UpdateChar();
|
||||
}
|
||||
else
|
||||
{
|
||||
uint len;
|
||||
if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1)
|
||||
{
|
||||
if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
if (
|
||||
_isRep0LongDecoders[
|
||||
(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState
|
||||
]
|
||||
.Decode(rangeDecoder) == 0
|
||||
)
|
||||
{
|
||||
_state.UpdateShortRep();
|
||||
await outWindow
|
||||
.PutByteAsync(outWindow.GetByte((int)_rep0), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint distance;
|
||||
if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
distance = _rep1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
distance = _rep2;
|
||||
}
|
||||
else
|
||||
{
|
||||
distance = _rep3;
|
||||
_rep3 = _rep2;
|
||||
}
|
||||
_rep2 = _rep1;
|
||||
}
|
||||
_rep1 = _rep0;
|
||||
_rep0 = distance;
|
||||
}
|
||||
len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN;
|
||||
_state.UpdateRep();
|
||||
}
|
||||
else
|
||||
{
|
||||
_rep3 = _rep2;
|
||||
_rep2 = _rep1;
|
||||
_rep1 = _rep0;
|
||||
len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState);
|
||||
_state.UpdateMatch();
|
||||
var posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder);
|
||||
if (posSlot >= Base.K_START_POS_MODEL_INDEX)
|
||||
{
|
||||
var numDirectBits = (int)((posSlot >> 1) - 1);
|
||||
_rep0 = ((2 | (posSlot & 1)) << numDirectBits);
|
||||
if (posSlot < Base.K_END_POS_MODEL_INDEX)
|
||||
{
|
||||
_rep0 += BitTreeDecoder.ReverseDecode(
|
||||
_posDecoders,
|
||||
_rep0 - posSlot - 1,
|
||||
rangeDecoder,
|
||||
numDirectBits
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rep0 += (
|
||||
rangeDecoder.DecodeDirectBits(numDirectBits - Base.K_NUM_ALIGN_BITS)
|
||||
<< Base.K_NUM_ALIGN_BITS
|
||||
);
|
||||
_rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rep0 = posSlot;
|
||||
}
|
||||
}
|
||||
if (_rep0 >= outWindow.Total || _rep0 >= dictionarySizeCheck)
|
||||
{
|
||||
if (_rep0 == 0xFFFFFFFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
throw new DataErrorException();
|
||||
}
|
||||
await outWindow
|
||||
.CopyBlockAsync((int)_rep0, (int)len, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetDecoderProperties(byte[] properties)
|
||||
{
|
||||
if (properties.Length < 1)
|
||||
@@ -470,29 +647,4 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
|
||||
}
|
||||
_outWindow.Train(stream);
|
||||
}
|
||||
|
||||
/*
|
||||
public override bool CanRead { get { return true; }}
|
||||
public override bool CanWrite { get { return true; }}
|
||||
public override bool CanSeek { get { return true; }}
|
||||
public override long Length { get { return 0; }}
|
||||
public override long Position
|
||||
{
|
||||
get { return 0; }
|
||||
set { }
|
||||
}
|
||||
public override void Flush() { }
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
}
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
public override void SetLength(long value) {}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.LZ;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -423,6 +425,82 @@ public class LzmaStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DecodeChunkHeaderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var controlBuffer = new byte[1];
|
||||
await _inputStream.ReadAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
var control = controlBuffer[0];
|
||||
_inputPosition++;
|
||||
|
||||
if (control == 0x00)
|
||||
{
|
||||
_endReached = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (control >= 0xE0 || control == 0x01)
|
||||
{
|
||||
_needProps = true;
|
||||
_needDictReset = false;
|
||||
_outWindow.Reset();
|
||||
}
|
||||
else if (_needDictReset)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
|
||||
if (control >= 0x80)
|
||||
{
|
||||
_uncompressedChunk = false;
|
||||
|
||||
_availableBytes = (control & 0x1F) << 16;
|
||||
var buffer = new byte[2];
|
||||
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
_availableBytes += (buffer[0] << 8) + buffer[1] + 1;
|
||||
_inputPosition += 2;
|
||||
|
||||
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
_rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1;
|
||||
_inputPosition += 2;
|
||||
|
||||
if (control >= 0xC0)
|
||||
{
|
||||
_needProps = false;
|
||||
await _inputStream
|
||||
.ReadAsync(controlBuffer, 0, 1, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
Properties[0] = controlBuffer[0];
|
||||
_inputPosition++;
|
||||
|
||||
_decoder = new Decoder();
|
||||
_decoder.SetDecoderProperties(Properties);
|
||||
}
|
||||
else if (_needProps)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
else if (control >= 0xA0)
|
||||
{
|
||||
_decoder = new Decoder();
|
||||
_decoder.SetDecoderProperties(Properties);
|
||||
}
|
||||
|
||||
_rangeDecoder.Init(_inputStream);
|
||||
}
|
||||
else if (control > 0x02)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
else
|
||||
{
|
||||
_uncompressedChunk = true;
|
||||
var buffer = new byte[2];
|
||||
await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
|
||||
_availableBytes = (buffer[0] << 8) + buffer[1] + 1;
|
||||
_inputPosition += 2;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
@@ -435,5 +513,128 @@ public class LzmaStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (_endReached)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var total = 0;
|
||||
while (total < count)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_availableBytes == 0)
|
||||
{
|
||||
if (_isLzma2)
|
||||
{
|
||||
await DecodeChunkHeaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_endReached = true;
|
||||
}
|
||||
if (_endReached)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var toProcess = count - total;
|
||||
if (toProcess > _availableBytes)
|
||||
{
|
||||
toProcess = (int)_availableBytes;
|
||||
}
|
||||
|
||||
_outWindow.SetLimit(toProcess);
|
||||
if (_uncompressedChunk)
|
||||
{
|
||||
_inputPosition += await _outWindow
|
||||
.CopyStreamAsync(_inputStream, toProcess, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else if (
|
||||
await _decoder
|
||||
.CodeAsync(_dictionarySize, _outWindow, _rangeDecoder, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
&& _outputSize < 0
|
||||
)
|
||||
{
|
||||
_availableBytes = _outWindow.AvailableBytes;
|
||||
}
|
||||
|
||||
var read = _outWindow.Read(buffer, offset, toProcess);
|
||||
total += read;
|
||||
offset += read;
|
||||
_position += read;
|
||||
_availableBytes -= read;
|
||||
|
||||
if (_availableBytes == 0 && !_uncompressedChunk)
|
||||
{
|
||||
if (
|
||||
!_rangeDecoder.IsFinished
|
||||
|| (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit)
|
||||
)
|
||||
{
|
||||
_outWindow.SetLimit(toProcess + 1);
|
||||
if (
|
||||
!await _decoder
|
||||
.CodeAsync(
|
||||
_dictionarySize,
|
||||
_outWindow,
|
||||
_rangeDecoder,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
_rangeDecoder.ReleaseStream();
|
||||
throw new DataErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
_rangeDecoder.ReleaseStream();
|
||||
|
||||
_inputPosition += _rangeDecoder._total;
|
||||
if (_outWindow.HasPending)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_endReached)
|
||||
{
|
||||
if (_inputSize >= 0 && _inputPosition != _inputSize)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
if (_outputSize >= 0 && _position != _outputSize)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Write(buffer, offset, count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public byte[] Properties { get; } = new byte[5];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA.Utilites;
|
||||
@@ -101,4 +103,22 @@ internal class CrcBuilderStream : Stream, IStreamStack
|
||||
_mCrc = Crc.Update(_mCrc, buffer, offset, count);
|
||||
_mTarget.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_mFinished)
|
||||
{
|
||||
throw new InvalidOperationException("CRC calculation has been finished.");
|
||||
}
|
||||
|
||||
Processed += count;
|
||||
_mCrc = Crc.Update(_mCrc, buffer, offset, count);
|
||||
await _mTarget.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA.Utilites;
|
||||
|
||||
@@ -11,7 +14,7 @@ public class CrcCheckStream : Stream
|
||||
private uint _mCurrentCrc;
|
||||
private bool _mClosed;
|
||||
|
||||
private readonly long[] _mBytes = new long[256];
|
||||
private readonly long[] _mBytes = ArrayPool<long>.Shared.Rent(256);
|
||||
private long _mLength;
|
||||
|
||||
public CrcCheckStream(uint crc)
|
||||
@@ -65,6 +68,7 @@ public class CrcCheckStream : Stream
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
ArrayPool<long>.Shared.Return(_mBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,4 +105,16 @@ public class CrcCheckStream : Stream
|
||||
|
||||
_mCurrentCrc = Crc.Update(_mCurrentCrc, buffer, offset, count);
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Write(buffer, offset, count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.RLE90
|
||||
{
|
||||
/// <summary>
|
||||
/// Real-time streaming RLE90 decompression stream.
|
||||
/// Decompresses bytes on demand without buffering the entire file in memory.
|
||||
/// </summary>
|
||||
public class RunLength90Stream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
@@ -31,13 +31,19 @@ namespace SharpCompress.Compressors.RLE90
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly int _compressedSize;
|
||||
private int _bytesReadFromSource;
|
||||
|
||||
private const byte DLE = 0x90;
|
||||
private int _compressedSize;
|
||||
private bool _processed = false;
|
||||
private bool _inDleMode;
|
||||
private byte _lastByte;
|
||||
private int _repeatCount;
|
||||
|
||||
private bool _endOfCompressedData;
|
||||
|
||||
public RunLength90Stream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
_compressedSize = compressedSize;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(RunLength90Stream));
|
||||
@@ -53,44 +59,93 @@ namespace SharpCompress.Compressors.RLE90
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
if (offset < 0 || count < 0 || offset + count > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
int bytesWritten = 0;
|
||||
|
||||
while (bytesWritten < count && !_endOfCompressedData)
|
||||
{
|
||||
return 0;
|
||||
// Handle pending repeat bytes first
|
||||
if (_repeatCount > 0)
|
||||
{
|
||||
int toWrite = Math.Min(_repeatCount, count - bytesWritten);
|
||||
for (int i = 0; i < toWrite; i++)
|
||||
{
|
||||
buffer[offset + bytesWritten++] = _lastByte;
|
||||
}
|
||||
_repeatCount -= toWrite;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to read the next byte from compressed data
|
||||
if (_bytesReadFromSource >= _compressedSize)
|
||||
{
|
||||
_endOfCompressedData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int next = _stream.ReadByte();
|
||||
if (next == -1)
|
||||
{
|
||||
_endOfCompressedData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
_bytesReadFromSource++;
|
||||
byte c = (byte)next;
|
||||
|
||||
if (_inDleMode)
|
||||
{
|
||||
_inDleMode = false;
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
buffer[offset + bytesWritten++] = DLE;
|
||||
_lastByte = DLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_repeatCount = c - 1;
|
||||
// We’ll handle these repeats in next loop iteration.
|
||||
}
|
||||
}
|
||||
else if (c == DLE)
|
||||
{
|
||||
_inDleMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[offset + bytesWritten++] = c;
|
||||
_lastByte = c;
|
||||
}
|
||||
}
|
||||
_processed = true;
|
||||
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
byte[] compressedBuffer = binaryReader.ReadBytes(_compressedSize);
|
||||
|
||||
var unpacked = RLE.UnpackRLE(compressedBuffer);
|
||||
unpacked.CopyTo(buffer);
|
||||
|
||||
return unpacked.Count;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Compressors.Rar;
|
||||
@@ -8,6 +10,14 @@ internal interface IRarUnpack
|
||||
void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream);
|
||||
void DoUnpack();
|
||||
|
||||
Task DoUnpackAsync(
|
||||
FileHeader fileHeader,
|
||||
Stream readStream,
|
||||
Stream writeStream,
|
||||
CancellationToken cancellationToken
|
||||
);
|
||||
Task DoUnpackAsync(CancellationToken cancellationToken);
|
||||
|
||||
// eg u/i pause/resume button
|
||||
bool Suspended { get; set; }
|
||||
|
||||
|
||||
@@ -150,6 +150,136 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
public override async System.Threading.Tasks.Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var totalRead = 0;
|
||||
var currentOffset = offset;
|
||||
var currentCount = count;
|
||||
while (currentCount > 0)
|
||||
{
|
||||
var readSize = currentCount;
|
||||
if (currentCount > maxPosition - currentPosition)
|
||||
{
|
||||
readSize = (int)(maxPosition - currentPosition);
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.ReadAsync(buffer, currentOffset, readSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
currentPosition += read;
|
||||
currentOffset += read;
|
||||
currentCount -= read;
|
||||
totalRead += read;
|
||||
if (
|
||||
((maxPosition - currentPosition) == 0)
|
||||
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
|
||||
)
|
||||
{
|
||||
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!filePartEnumerator.MoveNext())
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Multi-part rar file is incomplete. Entry expects a new volume: "
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentPartTotalReadBytes += totalRead;
|
||||
currentEntryTotalReadBytes += totalRead;
|
||||
streamListener.FireCompressedBytesRead(
|
||||
currentPartTotalReadBytes,
|
||||
currentEntryTotalReadBytes
|
||||
);
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var totalRead = 0;
|
||||
var currentOffset = 0;
|
||||
var currentCount = buffer.Length;
|
||||
while (currentCount > 0)
|
||||
{
|
||||
var readSize = currentCount;
|
||||
if (currentCount > maxPosition - currentPosition)
|
||||
{
|
||||
readSize = (int)(maxPosition - currentPosition);
|
||||
}
|
||||
|
||||
var read = await currentStream
|
||||
.ReadAsync(buffer.Slice(currentOffset, readSize), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read < 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
currentPosition += read;
|
||||
currentOffset += read;
|
||||
currentCount -= read;
|
||||
totalRead += read;
|
||||
if (
|
||||
((maxPosition - currentPosition) == 0)
|
||||
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
|
||||
)
|
||||
{
|
||||
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Sharpcompress currently does not support multi-volume decryption."
|
||||
);
|
||||
}
|
||||
var fileName = filePartEnumerator.Current.FileHeader.FileName;
|
||||
if (!filePartEnumerator.MoveNext())
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"Multi-part rar file is incomplete. Entry expects a new volume: "
|
||||
+ fileName
|
||||
);
|
||||
}
|
||||
InitializeNextFilePart();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentPartTotalReadBytes += totalRead;
|
||||
currentEntryTotalReadBytes += totalRead;
|
||||
streamListener.FireCompressedBytesRead(
|
||||
currentPartTotalReadBytes,
|
||||
currentEntryTotalReadBytes
|
||||
);
|
||||
return totalRead;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
@@ -103,7 +105,7 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
|
||||
byte[] _hash = { };
|
||||
|
||||
public RarBLAKE2spStream(
|
||||
private RarBLAKE2spStream(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
@@ -121,6 +123,29 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
ResetCrc();
|
||||
}
|
||||
|
||||
public static RarBLAKE2spStream Create(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
)
|
||||
{
|
||||
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
|
||||
stream.Initialize();
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<RarBLAKE2spStream> CreateAsync(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
|
||||
await stream.InitializeAsync(cancellationToken);
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
@@ -333,4 +358,59 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async System.Threading.Tasks.Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var result = await base.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, buffer.Span.Slice(0, result), result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (
|
||||
!disableCRCCheck
|
||||
&& !(GetCrc().SequenceEqual(readStream.CurrentCrc))
|
||||
&& buffer.Length != 0
|
||||
)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
@@ -31,7 +33,7 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
private uint currentCrc;
|
||||
private readonly bool disableCRC;
|
||||
|
||||
public RarCrcStream(
|
||||
private RarCrcStream(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
@@ -46,6 +48,29 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
ResetCrc();
|
||||
}
|
||||
|
||||
public static RarCrcStream Create(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
)
|
||||
{
|
||||
var stream = new RarCrcStream(unpack, fileHeader, readStream);
|
||||
stream.Initialize();
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<RarCrcStream> CreateAsync(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var stream = new RarCrcStream(unpack, fileHeader, readStream);
|
||||
await stream.InitializeAsync(cancellationToken);
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
@@ -77,4 +102,56 @@ internal class RarCrcStream : RarStream, IStreamStack
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async System.Threading.Tasks.Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var result = await base.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result);
|
||||
}
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& count != 0
|
||||
)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
currentCrc = RarCRC.CheckCrc(currentCrc, buffer.Span, 0, result);
|
||||
}
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& buffer.Length != 0
|
||||
)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -56,13 +58,24 @@ internal class RarStream : Stream, IStreamStack
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(RarStream));
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
fetch = true;
|
||||
unpack.DoUnpack(fileHeader, readStream, this);
|
||||
fetch = false;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
fetch = true;
|
||||
await unpack.DoUnpackAsync(fileHeader, readStream, this, cancellationToken);
|
||||
fetch = false;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!isDisposed)
|
||||
@@ -131,6 +144,73 @@ internal class RarStream : Stream, IStreamStack
|
||||
return outTotal;
|
||||
}
|
||||
|
||||
public override async System.Threading.Tasks.Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
) => await ReadImplAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
private async System.Threading.Tasks.Task<int> ReadImplAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
outTotal = 0;
|
||||
if (tmpCount > 0)
|
||||
{
|
||||
var toCopy = tmpCount < count ? tmpCount : count;
|
||||
Buffer.BlockCopy(tmpBuffer, tmpOffset, buffer, offset, toCopy);
|
||||
tmpOffset += toCopy;
|
||||
tmpCount -= toCopy;
|
||||
offset += toCopy;
|
||||
count -= toCopy;
|
||||
outTotal += toCopy;
|
||||
}
|
||||
if (count > 0 && unpack.DestSize > 0)
|
||||
{
|
||||
outBuffer = buffer;
|
||||
outOffset = offset;
|
||||
outCount = count;
|
||||
fetch = true;
|
||||
await unpack.DoUnpackAsync(cancellationToken).ConfigureAwait(false);
|
||||
fetch = false;
|
||||
}
|
||||
_position += outTotal;
|
||||
if (count > 0 && outTotal == 0 && _position != Length)
|
||||
{
|
||||
// sanity check, eg if we try to decompress a redir entry
|
||||
throw new InvalidOperationException(
|
||||
$"unpacked file size does not match header: expected {Length} found {_position}"
|
||||
);
|
||||
}
|
||||
return outTotal;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
||||
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var array = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
try
|
||||
{
|
||||
var bytesRead = await ReadImplAsync(array, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
new ReadOnlySpan<byte>(array, 0, bytesRead).CopyTo(buffer.Span);
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Buffers.ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
@@ -165,6 +245,18 @@ internal class RarStream : Stream, IStreamStack
|
||||
}
|
||||
}
|
||||
|
||||
public override System.Threading.Tasks.Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Write(buffer, offset, count);
|
||||
return System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void EnsureBufferCapacity(int count)
|
||||
{
|
||||
if (this.tmpBuffer.Length < this.tmpCount + count)
|
||||
|
||||
@@ -155,6 +155,25 @@ 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(null);
|
||||
}
|
||||
suspended = false;
|
||||
await DoUnpackAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void DoUnpack()
|
||||
{
|
||||
if (fileHeader.CompressionMethod == 0)
|
||||
@@ -189,6 +208,42 @@ 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<byte> buffer = stackalloc byte[(int)Math.Min(0x10000, destUnpSize)];
|
||||
@@ -205,6 +260,26 @@ 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<int> DDecode = stackalloc int[PackDef.DC];
|
||||
@@ -483,6 +558,281 @@ 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;
|
||||
@@ -1339,6 +1689,256 @@ 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<byte>.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<byte>.Shared.Return(FilteredData);
|
||||
FilteredData = ArrayPool<byte>.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<byte>.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)
|
||||
|
||||
@@ -316,6 +316,110 @@ 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;
|
||||
@@ -351,6 +455,40 @@ internal partial class Unpack
|
||||
return (readCode != -1);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> 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];
|
||||
@@ -814,4 +952,26 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,163 @@ 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;
|
||||
@@ -534,6 +691,120 @@ internal partial class Unpack
|
||||
return (true);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> 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<byte>(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)
|
||||
|
||||
@@ -479,6 +479,354 @@ internal partial class Unpack
|
||||
return ReadCode != -1;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> 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<bool> 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<ByteCount;I++)
|
||||
for (var I = 0; I < ByteCount; I++)
|
||||
{
|
||||
//BlockSize+=(Inp.fgetbits()>>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<bool> 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()
|
||||
// {
|
||||
@@ -814,116 +1162,5 @@ internal partial class Unpack
|
||||
Header.TablePresent = (BlockFlags & 0x80) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//?
|
||||
// bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables)
|
||||
// {
|
||||
// if (!Header.TablePresent)
|
||||
// return true;
|
||||
//
|
||||
// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25)
|
||||
// if (!UnpReadBuf())
|
||||
// return false;
|
||||
//
|
||||
// byte BitLength[BC];
|
||||
// for (uint 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<ASIZE(BitLength))
|
||||
// BitLength[I++]=0;
|
||||
// I--;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// BitLength[I]=Length;
|
||||
// }
|
||||
//
|
||||
// MakeDecodeTables(BitLength,&Tables.BD,BC);
|
||||
//
|
||||
// byte Table[HUFF_TABLE_SIZE];
|
||||
// const uint TableSize=HUFF_TABLE_SIZE;
|
||||
// for (uint I=0;I<TableSize;)
|
||||
// {
|
||||
// if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-5)
|
||||
// if (!UnpReadBuf())
|
||||
// return false;
|
||||
// uint Number=DecodeNumber(Inp,&Tables.BD);
|
||||
// if (Number<16)
|
||||
// {
|
||||
// Table[I]=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],&Tables.LD,NC);
|
||||
// MakeDecodeTables(&Table[NC],&Tables.DD,DC);
|
||||
// MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC);
|
||||
// MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
//?
|
||||
// void InitFilters()
|
||||
// {
|
||||
// Filters.SoftReset();
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -29,9 +29,28 @@ 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<int> UnpIO_UnpReadAsync(
|
||||
byte[] buf,
|
||||
int offset,
|
||||
int count,
|
||||
System.Threading.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);
|
||||
|
||||
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(
|
||||
byte[] buf,
|
||||
size_t offset,
|
||||
uint count,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await writeStream
|
||||
.WriteAsync(buf, checked((int)offset), checked((int)count), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream)
|
||||
{
|
||||
// as of 12/2017 .NET limits array indexing to using a signed integer
|
||||
@@ -53,6 +72,25 @@ internal partial class Unpack : 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.IsStored)
|
||||
{
|
||||
Init(fileHeader.WindowSize, fileHeader.IsSolid);
|
||||
}
|
||||
Suspended = false;
|
||||
await DoUnpackAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void DoUnpack()
|
||||
{
|
||||
if (fileHeader.IsStored)
|
||||
@@ -65,6 +103,27 @@ internal partial class Unpack : IRarUnpack
|
||||
}
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task DoUnpackAsync(
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (fileHeader.IsStored)
|
||||
{
|
||||
await UnstoreFileAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: When compression methods are converted to async, call them here
|
||||
// For now, fall back to synchronous version
|
||||
await DoUnpackAsync(
|
||||
fileHeader.CompressionAlgorithm,
|
||||
fileHeader.IsSolid,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnstoreFile()
|
||||
{
|
||||
Span<byte> b = stackalloc byte[(int)Math.Min(0x10000, DestUnpSize)];
|
||||
@@ -80,6 +139,25 @@ internal partial class Unpack : IRarUnpack
|
||||
} while (!Suspended);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task UnstoreFileAsync(
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var buffer = new byte[(int)Math.Min(0x10000, DestUnpSize)];
|
||||
do
|
||||
{
|
||||
var n = await readStream
|
||||
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (n == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
await writeStream.WriteAsync(buffer, 0, n, cancellationToken).ConfigureAwait(false);
|
||||
DestUnpSize -= n;
|
||||
} while (!Suspended);
|
||||
}
|
||||
|
||||
public bool Suspended { get; set; }
|
||||
|
||||
public long DestSize => DestUnpSize;
|
||||
|
||||
@@ -200,6 +200,102 @@ 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]);
|
||||
|
||||
|
||||
@@ -349,6 +349,170 @@ 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)
|
||||
@@ -370,6 +534,36 @@ 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<byte> BitLength = stackalloc byte[checked((int)BC20)];
|
||||
@@ -490,6 +684,130 @@ internal partial class Unpack
|
||||
return true;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> 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)
|
||||
|
||||
@@ -1,793 +0,0 @@
|
||||
#if !Rar2017_64bit
|
||||
#else
|
||||
using nint = System.Int64;
|
||||
using nuint = System.UInt64;
|
||||
using size_t = System.UInt64;
|
||||
#endif
|
||||
|
||||
//using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local;
|
||||
/*
|
||||
namespace SharpCompress.Compressors.Rar.UnpackV2017
|
||||
{
|
||||
internal partial class Unpack
|
||||
{
|
||||
|
||||
#if !RarV2017_RAR5ONLY
|
||||
// We use it instead of direct PPM.DecodeChar call to be sure that
|
||||
// we reset PPM structures in case of corrupt data. It is important,
|
||||
// because these structures can be invalid after PPM.DecodeChar returned -1.
|
||||
int SafePPMDecodeChar()
|
||||
{
|
||||
int Ch=PPM.DecodeChar();
|
||||
if (Ch==-1) // Corrupt PPM data found.
|
||||
{
|
||||
PPM.CleanUp(); // Reset possibly corrupt PPM data structures.
|
||||
UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode.
|
||||
}
|
||||
return(Ch);
|
||||
}
|
||||
|
||||
internal static class Unpack30Local {
|
||||
public static readonly byte[] LDecode={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224};
|
||||
public static readonly byte[] LBits= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5};
|
||||
public static readonly int[] DDecode = new int[DC];
|
||||
public static readonly byte[] DBits = new byte[DC];
|
||||
public static readonly int[] DBitLengthCounts= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12};
|
||||
public static readonly byte[] SDDecode={0,4,8,16,32,64,128,192};
|
||||
public static readonly byte[] SDBits= {2,2,3, 4, 5, 6, 6, 6};
|
||||
}
|
||||
void Unpack29(bool Solid)
|
||||
{
|
||||
uint Bits;
|
||||
|
||||
if (DDecode[1]==0)
|
||||
{
|
||||
int Dist=0,BitLength=0,Slot=0;
|
||||
for (int I=0;I<DBitLengthCounts.Length;I++,BitLength++)
|
||||
for (int J=0;J<DBitLengthCounts[I];J++,Slot++,Dist+=(1<<BitLength))
|
||||
{
|
||||
DDecode[Slot]=Dist;
|
||||
DBits[Slot]=(byte)BitLength;
|
||||
}
|
||||
}
|
||||
|
||||
FileExtracted=true;
|
||||
|
||||
if (!Suspended)
|
||||
{
|
||||
UnpInitData(Solid);
|
||||
if (!UnpReadBuf30())
|
||||
return;
|
||||
if ((!Solid || !TablesRead3) && !ReadTables30())
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
UnpPtr&=MaxWinMask;
|
||||
|
||||
if (Inp.InAddr>ReadBorder)
|
||||
{
|
||||
if (!UnpReadBuf30())
|
||||
break;
|
||||
}
|
||||
if (((WrPtr-UnpPtr) & MaxWinMask)<260 && WrPtr!=UnpPtr)
|
||||
{
|
||||
UnpWriteBuf30();
|
||||
if (WrittenFileSize>DestUnpSize)
|
||||
return;
|
||||
if (Suspended)
|
||||
{
|
||||
FileExtracted=false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (UnpBlockType==BLOCK_PPM)
|
||||
{
|
||||
// Here speed is critical, so we do not use SafePPMDecodeChar,
|
||||
// because sometimes even the inline function can introduce
|
||||
// some additional penalty.
|
||||
int Ch=PPM.DecodeChar();
|
||||
if (Ch==-1) // Corrupt PPM data found.
|
||||
{
|
||||
PPM.CleanUp(); // Reset possibly corrupt PPM data structures.
|
||||
UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode.
|
||||
break;
|
||||
}
|
||||
if (Ch==PPMEscChar)
|
||||
{
|
||||
int NextCh=SafePPMDecodeChar();
|
||||
if (NextCh==0) // End of PPM encoding.
|
||||
{
|
||||
if (!ReadTables30())
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
if (NextCh==-1) // Corrupt PPM data found.
|
||||
break;
|
||||
if (NextCh==2) // End of file in PPM mode.
|
||||
break;
|
||||
if (NextCh==3) // Read VM code.
|
||||
{
|
||||
if (!ReadVMCodePPM())
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
if (NextCh==4) // LZ inside of PPM.
|
||||
{
|
||||
uint Distance=0,Length;
|
||||
bool Failed=false;
|
||||
for (int I=0;I<4 && !Failed;I++)
|
||||
{
|
||||
int _Ch=SafePPMDecodeChar();
|
||||
if (_Ch==-1)
|
||||
Failed=true;
|
||||
else
|
||||
if (I==3)
|
||||
Length=(byte)_Ch;
|
||||
else
|
||||
Distance=(Distance<<8)+(byte)_Ch;
|
||||
}
|
||||
if (Failed)
|
||||
break;
|
||||
|
||||
CopyString(Length+32,Distance+2);
|
||||
continue;
|
||||
}
|
||||
if (NextCh==5) // One byte distance match (RLE) inside of PPM.
|
||||
{
|
||||
int Length=SafePPMDecodeChar();
|
||||
if (Length==-1)
|
||||
break;
|
||||
CopyString((uint)(Length+4),1);
|
||||
continue;
|
||||
}
|
||||
// If we are here, NextCh must be 1, what means that current byte
|
||||
// is equal to our 'escape' byte, so we just store it to Window.
|
||||
}
|
||||
Window[UnpPtr++]=(byte)Ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint Number=DecodeNumber(Inp,BlockTables.LD);
|
||||
if (Number<256)
|
||||
{
|
||||
Window[UnpPtr++]=(byte)Number;
|
||||
continue;
|
||||
}
|
||||
if (Number>=271)
|
||||
{
|
||||
uint Length=(uint)(LDecode[Number-=271]+3);
|
||||
if ((Bits=LBits[Number])>0)
|
||||
{
|
||||
Length+=Inp.getbits()>>(int)(16-Bits);
|
||||
Inp.addbits(Bits);
|
||||
}
|
||||
|
||||
uint DistNumber=DecodeNumber(Inp,BlockTables.DD);
|
||||
uint Distance=(uint)(DDecode[DistNumber]+1);
|
||||
if ((Bits=DBits[DistNumber])>0)
|
||||
{
|
||||
if (DistNumber>9)
|
||||
{
|
||||
if (Bits>4)
|
||||
{
|
||||
Distance+=((Inp.getbits()>>(int)(20-Bits))<<4);
|
||||
Inp.addbits(Bits-4);
|
||||
}
|
||||
if (LowDistRepCount>0)
|
||||
{
|
||||
LowDistRepCount--;
|
||||
Distance+=(uint)PrevLowDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint LowDist=DecodeNumber(Inp,BlockTables.LDD);
|
||||
if (LowDist==16)
|
||||
{
|
||||
LowDistRepCount=(int)(LOW_DIST_REP_COUNT-1);
|
||||
Distance+=(uint)PrevLowDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
Distance+=LowDist;
|
||||
PrevLowDist=(int)LowDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Distance+=Inp.getbits()>>(int)(16-Bits);
|
||||
Inp.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)
|
||||
{
|
||||
uint DistNum=Number-259;
|
||||
uint Distance=OldDist[DistNum];
|
||||
for (uint I=DistNum;I>0;I--)
|
||||
OldDist[I]=OldDist[I-1];
|
||||
OldDist[0]=Distance;
|
||||
|
||||
uint LengthNumber=DecodeNumber(Inp,BlockTables.RD);
|
||||
int Length=LDecode[LengthNumber]+2;
|
||||
if ((Bits=LBits[LengthNumber])>0)
|
||||
{
|
||||
Length+=(int)(Inp.getbits()>>(int)(16-Bits));
|
||||
Inp.addbits(Bits);
|
||||
}
|
||||
LastLength=(uint)Length;
|
||||
CopyString((uint)Length,Distance);
|
||||
continue;
|
||||
}
|
||||
if (Number<272)
|
||||
{
|
||||
uint Distance=(uint)(SDDecode[Number-=263]+1);
|
||||
if ((Bits=SDBits[Number])>0)
|
||||
{
|
||||
Distance+=Inp.getbits()>>(int)(16-Bits);
|
||||
Inp.addbits(Bits);
|
||||
}
|
||||
InsertOldDist(Distance);
|
||||
LastLength=2;
|
||||
CopyString(2,Distance);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
UnpWriteBuf30();
|
||||
}
|
||||
|
||||
|
||||
// Return 'false' to quit unpacking the current file or 'true' to continue.
|
||||
bool ReadEndOfBlock()
|
||||
{
|
||||
uint BitField=Inp.getbits();
|
||||
bool NewTable,NewFile=false;
|
||||
|
||||
// "1" - no new file, new table just here.
|
||||
// "00" - new file, no new table.
|
||||
// "01" - new file, new table (in beginning of next file).
|
||||
|
||||
if ((BitField & 0x8000)!=0)
|
||||
{
|
||||
NewTable=true;
|
||||
Inp.addbits(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
NewFile=true;
|
||||
NewTable=(BitField & 0x4000)!=0;
|
||||
Inp.addbits(2);
|
||||
}
|
||||
TablesRead3=!NewTable;
|
||||
|
||||
// Quit immediately if "new file" flag is set. If "new table" flag
|
||||
// is present, we'll read the table in beginning of next file
|
||||
// based on 'TablesRead3' 'false' value.
|
||||
if (NewFile)
|
||||
return false;
|
||||
return ReadTables30(); // Quit only if we failed to read tables.
|
||||
}
|
||||
|
||||
|
||||
bool ReadVMCode()
|
||||
{
|
||||
// Entire VM code is guaranteed to fully present in block defined
|
||||
// by current Huffman table. Compressor checks that VM code does not cross
|
||||
// Huffman block boundaries.
|
||||
uint FirstByte=Inp.getbits()>>8;
|
||||
Inp.addbits(8);
|
||||
uint Length=(FirstByte & 7)+1;
|
||||
if (Length==7)
|
||||
{
|
||||
Length=(Inp.getbits()>>8)+7;
|
||||
Inp.addbits(8);
|
||||
}
|
||||
else
|
||||
if (Length==8)
|
||||
{
|
||||
Length=Inp.getbits();
|
||||
Inp.addbits(16);
|
||||
}
|
||||
if (Length==0)
|
||||
return false;
|
||||
Array<byte> VMCode(Length);
|
||||
for (uint I=0;I<Length;I++)
|
||||
{
|
||||
// Try to read the new buffer if only one byte is left.
|
||||
// But if we read all bytes except the last, one byte is enough.
|
||||
if (Inp.InAddr>=ReadTop-1 && !UnpReadBuf30() && I<Length-1)
|
||||
return false;
|
||||
VMCode[I]=Inp.getbits()>>8;
|
||||
Inp.addbits(8);
|
||||
}
|
||||
return AddVMCode(FirstByte,&VMCode[0],Length);
|
||||
}
|
||||
|
||||
|
||||
bool ReadVMCodePPM()
|
||||
{
|
||||
uint FirstByte=(uint)SafePPMDecodeChar();
|
||||
if ((int)FirstByte==-1)
|
||||
return false;
|
||||
uint Length=(FirstByte & 7)+1;
|
||||
if (Length==7)
|
||||
{
|
||||
int B1=SafePPMDecodeChar();
|
||||
if (B1==-1)
|
||||
return false;
|
||||
Length=B1+7;
|
||||
}
|
||||
else
|
||||
if (Length==8)
|
||||
{
|
||||
int B1=SafePPMDecodeChar();
|
||||
if (B1==-1)
|
||||
return false;
|
||||
int B2=SafePPMDecodeChar();
|
||||
if (B2==-1)
|
||||
return false;
|
||||
Length=B1*256+B2;
|
||||
}
|
||||
if (Length==0)
|
||||
return false;
|
||||
Array<byte> VMCode(Length);
|
||||
for (uint I=0;I<Length;I++)
|
||||
{
|
||||
int Ch=SafePPMDecodeChar();
|
||||
if (Ch==-1)
|
||||
return false;
|
||||
VMCode[I]=Ch;
|
||||
}
|
||||
return AddVMCode(FirstByte,&VMCode[0],Length);
|
||||
}
|
||||
|
||||
|
||||
bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize)
|
||||
{
|
||||
VMCodeInp.InitBitInput();
|
||||
//x memcpy(VMCodeInp.InBuf,Code,Min(BitInput.MAX_SIZE,CodeSize));
|
||||
Array.Copy(Code, 0, VMCodeInp.InBuf, 0, Math.Min(BitInput.MAX_SIZE,CodeSize));
|
||||
VM.Init();
|
||||
|
||||
uint FiltPos;
|
||||
if ((FirstByte & 0x80)!=0)
|
||||
{
|
||||
FiltPos=RarVM.ReadData(VMCodeInp);
|
||||
if (FiltPos==0)
|
||||
InitFilters30(false);
|
||||
else
|
||||
FiltPos--;
|
||||
}
|
||||
else
|
||||
FiltPos=(uint)this.LastFilter; // Use the same filter as last time.
|
||||
|
||||
if (FiltPos>Filters30.Count || FiltPos>OldFilterLengths.Count)
|
||||
return false;
|
||||
LastFilter=(int)FiltPos;
|
||||
bool NewFilter=(FiltPos==Filters30.Count);
|
||||
|
||||
UnpackFilter30 StackFilter=new UnpackFilter30(); // New filter for PrgStack.
|
||||
|
||||
UnpackFilter30 Filter;
|
||||
if (NewFilter) // New filter code, never used before since VM reset.
|
||||
{
|
||||
if (FiltPos>MAX3_UNPACK_FILTERS)
|
||||
{
|
||||
// Too many different filters, corrupt archive.
|
||||
//delete StackFilter;
|
||||
return false;
|
||||
}
|
||||
|
||||
Filters30.Add(1);
|
||||
Filters30[Filters30.Count-1]=Filter=new UnpackFilter30();
|
||||
StackFilter.ParentFilter=(uint)(Filters30.Count-1);
|
||||
|
||||
// Reserve one item to store the data block length of our new filter
|
||||
// entry. We'll set it to real block length below, after reading it.
|
||||
// But we need to initialize it now, because when processing corrupt
|
||||
// data, we can access this item even before we set it to real value.
|
||||
OldFilterLengths.Add(0);
|
||||
}
|
||||
else // Filter was used in the past.
|
||||
{
|
||||
Filter=Filters30[(int)FiltPos];
|
||||
StackFilter.ParentFilter=FiltPos;
|
||||
}
|
||||
|
||||
int EmptyCount=0;
|
||||
for (int I=0;I<PrgStack.Count;I++)
|
||||
{
|
||||
PrgStack[I-EmptyCount]=PrgStack[I];
|
||||
if (PrgStack[I]==null)
|
||||
EmptyCount++;
|
||||
if (EmptyCount>0)
|
||||
PrgStack[I]=null;
|
||||
}
|
||||
if (EmptyCount==0)
|
||||
{
|
||||
if (PrgStack.Count>MAX3_UNPACK_FILTERS)
|
||||
{
|
||||
//delete StackFilter;
|
||||
return false;
|
||||
}
|
||||
PrgStack.Add(1);
|
||||
EmptyCount=1;
|
||||
}
|
||||
size_t StackPos=(uint)(this.PrgStack.Count-EmptyCount);
|
||||
PrgStack[(int)StackPos]=StackFilter;
|
||||
|
||||
uint BlockStart=RarVM.ReadData(VMCodeInp);
|
||||
if ((FirstByte & 0x40)!=0)
|
||||
BlockStart+=258;
|
||||
StackFilter.BlockStart=(uint)((BlockStart+UnpPtr)&MaxWinMask);
|
||||
if ((FirstByte & 0x20)!=0)
|
||||
{
|
||||
StackFilter.BlockLength=RarVM.ReadData(VMCodeInp);
|
||||
|
||||
// Store the last data block length for current filter.
|
||||
OldFilterLengths[(int)FiltPos]=(int)StackFilter.BlockLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the data block size to same value as the previous block size
|
||||
// for same filter. It is possible for corrupt data to access a new
|
||||
// and not filled yet item of OldFilterLengths array here. This is why
|
||||
// we set new OldFilterLengths items to zero above.
|
||||
StackFilter.BlockLength=FiltPos<OldFilterLengths.Count ? OldFilterLengths[(int)FiltPos]:0;
|
||||
}
|
||||
|
||||
StackFilter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=BlockStart;
|
||||
|
||||
// DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x BlockStart=%08x",UnpPtr,WrPtr,BlockStart);
|
||||
|
||||
memset(StackFilter.Prg.InitR,0,sizeof(StackFilter.Prg.InitR));
|
||||
StackFilter.Prg.InitR[4]=StackFilter.BlockLength;
|
||||
|
||||
if ((FirstByte & 0x10)!=0) // Set registers to optional parameters if any.
|
||||
{
|
||||
uint InitMask=VMCodeInp.fgetbits()>>9;
|
||||
VMCodeInp.faddbits(7);
|
||||
for (int I=0;I<7;I++)
|
||||
if ((InitMask & (1<<I)) != 0)
|
||||
StackFilter.Prg.InitR[I]=RarVM.ReadData(VMCodeInp);
|
||||
}
|
||||
|
||||
if (NewFilter)
|
||||
{
|
||||
uint VMCodeSize=RarVM.ReadData(VMCodeInp);
|
||||
if (VMCodeSize>=0x10000 || VMCodeSize==0)
|
||||
return false;
|
||||
Array<byte> VMCode(VMCodeSize);
|
||||
for (uint I=0;I<VMCodeSize;I++)
|
||||
{
|
||||
if (VMCodeInp.Overflow(3))
|
||||
return false;
|
||||
VMCode[I]=VMCodeInp.fgetbits()>>8;
|
||||
VMCodeInp.faddbits(8);
|
||||
}
|
||||
VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
|
||||
}
|
||||
StackFilter.Prg.Type=Filter.Prg.Type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool UnpReadBuf30()
|
||||
{
|
||||
int DataSize=ReadTop-Inp.InAddr; // Data left to process.
|
||||
if (DataSize<0)
|
||||
return false;
|
||||
if (Inp.InAddr>BitInput.MAX_SIZE/2)
|
||||
{
|
||||
// If we already processed more than half of buffer, let's move
|
||||
// remaining data into beginning to free more space for new data
|
||||
// and ensure that calling function does not cross the buffer border
|
||||
// even if we did not read anything here. Also it ensures that read size
|
||||
// is not less than CRYPT_BLOCK_SIZE, so we can align it without risk
|
||||
// to make it zero.
|
||||
if (DataSize>0)
|
||||
//x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize);
|
||||
Array.Copy(Inp.InBuf,Inp.InAddr,Inp.InBuf,0,DataSize);
|
||||
Inp.InAddr=0;
|
||||
ReadTop=DataSize;
|
||||
}
|
||||
else
|
||||
DataSize=ReadTop;
|
||||
int ReadCode=UnpIO_UnpRead(Inp.InBuf,DataSize,BitInput.MAX_SIZE-DataSize);
|
||||
if (ReadCode>0)
|
||||
ReadTop+=ReadCode;
|
||||
ReadBorder=ReadTop-30;
|
||||
return ReadCode!=-1;
|
||||
}
|
||||
|
||||
|
||||
void UnpWriteBuf30()
|
||||
{
|
||||
uint WrittenBorder=(uint)WrPtr;
|
||||
uint WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask);
|
||||
for (int I=0;I<PrgStack.Count;I++)
|
||||
{
|
||||
// Here we apply filters to data which we need to write.
|
||||
// We always copy data to virtual machine memory before processing.
|
||||
// We cannot process them just in place in Window buffer, because
|
||||
// these data can be used for future string matches, so we must
|
||||
// preserve them in original form.
|
||||
|
||||
UnpackFilter30 flt=PrgStack[I];
|
||||
if (flt==null)
|
||||
continue;
|
||||
if (flt.NextWindow)
|
||||
{
|
||||
flt.NextWindow=false;
|
||||
continue;
|
||||
}
|
||||
uint BlockStart=flt.BlockStart;
|
||||
uint BlockLength=flt.BlockLength;
|
||||
if (((BlockStart-WrittenBorder)&MaxWinMask)<WriteSize)
|
||||
{
|
||||
if (WrittenBorder!=BlockStart)
|
||||
{
|
||||
UnpWriteArea(WrittenBorder,BlockStart);
|
||||
WrittenBorder=BlockStart;
|
||||
WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask);
|
||||
}
|
||||
if (BlockLength<=WriteSize)
|
||||
{
|
||||
uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask;
|
||||
if (BlockStart<BlockEnd || BlockEnd==0)
|
||||
VM.SetMemory(0,Window+BlockStart,BlockLength);
|
||||
else
|
||||
{
|
||||
uint FirstPartLength=uint(MaxWinSize-BlockStart);
|
||||
VM.SetMemory(0,Window+BlockStart,FirstPartLength);
|
||||
VM.SetMemory(FirstPartLength,Window,BlockEnd);
|
||||
}
|
||||
|
||||
VM_PreparedProgram *ParentPrg=&Filters30[flt->ParentFilter]->Prg;
|
||||
VM_PreparedProgram *Prg=&flt->Prg;
|
||||
|
||||
ExecuteCode(Prg);
|
||||
|
||||
byte[] FilteredData=Prg.FilteredData;
|
||||
uint FilteredDataSize=Prg.FilteredDataSize;
|
||||
|
||||
delete PrgStack[I];
|
||||
PrgStack[I]=null;
|
||||
while (I+1<PrgStack.Count)
|
||||
{
|
||||
UnpackFilter30 NextFilter=PrgStack[I+1];
|
||||
// It is required to check NextWindow here.
|
||||
if (NextFilter==null || NextFilter.BlockStart!=BlockStart ||
|
||||
NextFilter.BlockLength!=FilteredDataSize || NextFilter.NextWindow)
|
||||
break;
|
||||
|
||||
// Apply several filters to same data block.
|
||||
|
||||
VM.SetMemory(0,FilteredData,FilteredDataSize);
|
||||
|
||||
VM_PreparedProgram *ParentPrg=&Filters30[NextFilter.ParentFilter]->Prg;
|
||||
VM_PreparedProgram *NextPrg=&NextFilter->Prg;
|
||||
|
||||
ExecuteCode(NextPrg);
|
||||
|
||||
FilteredData=NextPrg.FilteredData;
|
||||
FilteredDataSize=NextPrg.FilteredDataSize;
|
||||
I++;
|
||||
delete PrgStack[I];
|
||||
PrgStack[I]=null;
|
||||
}
|
||||
UnpIO_UnpWrite(FilteredData,0,FilteredDataSize);
|
||||
UnpSomeRead=true;
|
||||
WrittenFileSize+=FilteredDataSize;
|
||||
WrittenBorder=BlockEnd;
|
||||
WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Current filter intersects the window write border, so we adjust
|
||||
// the window border to process this filter next time, not now.
|
||||
for (size_t J=I;J<PrgStack.Count;J++)
|
||||
{
|
||||
UnpackFilter30 flt=PrgStack[J];
|
||||
if (flt!=null && flt.NextWindow)
|
||||
flt.NextWindow=false;
|
||||
}
|
||||
WrPtr=WrittenBorder;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnpWriteArea(WrittenBorder,UnpPtr);
|
||||
WrPtr=UnpPtr;
|
||||
}
|
||||
|
||||
|
||||
void ExecuteCode(VM_PreparedProgram *Prg)
|
||||
{
|
||||
Prg->InitR[6]=(uint)WrittenFileSize;
|
||||
VM.Execute(Prg);
|
||||
}
|
||||
|
||||
|
||||
bool ReadTables30()
|
||||
{
|
||||
byte[] BitLength = new byte[BC];
|
||||
byte[] Table = new byte[HUFF_TABLE_SIZE30];
|
||||
if (Inp.InAddr>ReadTop-25)
|
||||
if (!UnpReadBuf30())
|
||||
return(false);
|
||||
Inp.faddbits((uint)((8-Inp.InBit)&7));
|
||||
uint BitField=Inp.fgetbits();
|
||||
if ((BitField & 0x8000) != 0)
|
||||
{
|
||||
UnpBlockType=BLOCK_PPM;
|
||||
return(PPM.DecodeInit(this,PPMEscChar));
|
||||
}
|
||||
UnpBlockType=BLOCK_LZ;
|
||||
|
||||
PrevLowDist=0;
|
||||
LowDistRepCount=0;
|
||||
|
||||
if ((BitField & 0x4000) == 0)
|
||||
Utility.Memset(UnpOldTable,0,UnpOldTable.Length);
|
||||
Inp.faddbits(2);
|
||||
|
||||
for (uint 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,BC30);
|
||||
|
||||
const uint TableSize=HUFF_TABLE_SIZE30;
|
||||
for (uint I=0;I<TableSize;)
|
||||
{
|
||||
if (Inp.InAddr>ReadTop-5)
|
||||
if (!UnpReadBuf30())
|
||||
return(false);
|
||||
uint Number=DecodeNumber(Inp,BlockTables.BD);
|
||||
if (Number<16)
|
||||
{
|
||||
Table[I]=(byte)((Number+this.UnpOldTable[I]) & 0xf);
|
||||
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)
|
||||
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==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;
|
||||
}
|
||||
}
|
||||
TablesRead3=true;
|
||||
if (Inp.InAddr>ReadTop)
|
||||
return false;
|
||||
MakeDecodeTables(Table,0,BlockTables.LD,NC30);
|
||||
MakeDecodeTables(Table,(int)NC30,BlockTables.DD,DC30);
|
||||
MakeDecodeTables(Table,(int)(NC30+DC30),BlockTables.LDD,LDC30);
|
||||
MakeDecodeTables(Table,(int)(NC30+DC30+LDC30),BlockTables.RD,RC30);
|
||||
//x memcpy(UnpOldTable,Table,sizeof(UnpOldTable));
|
||||
Array.Copy(Table,0,UnpOldTable,0,UnpOldTable.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void UnpInitData30(bool Solid)
|
||||
{
|
||||
if (!Solid)
|
||||
{
|
||||
TablesRead3=false;
|
||||
Utility.Memset(UnpOldTable, 0, UnpOldTable.Length);
|
||||
PPMEscChar=2;
|
||||
UnpBlockType=BLOCK_LZ;
|
||||
}
|
||||
InitFilters30(Solid);
|
||||
}
|
||||
|
||||
|
||||
void InitFilters30(bool Solid)
|
||||
{
|
||||
if (!Solid)
|
||||
{
|
||||
//OldFilterLengths.SoftReset();
|
||||
OldFilterLengths.Clear();
|
||||
LastFilter=0;
|
||||
|
||||
//for (size_t I=0;I<Filters30.Count;I++)
|
||||
// delete Filters30[I];
|
||||
//Filters30.SoftReset();
|
||||
Filters30.Clear();
|
||||
}
|
||||
//for (size_t I=0;I<PrgStack.Count;I++)
|
||||
// delete PrgStack[I];
|
||||
//PrgStack.SoftReset();
|
||||
PrgStack.Clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -222,6 +222,180 @@ 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;
|
||||
}
|
||||
|
||||
if (
|
||||
!ReadBlockHeader(Inp, ref BlockHeader)
|
||||
|| !ReadTables(Inp, ref BlockHeader, ref BlockTables)
|
||||
|| !TablesRead5
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
UnpPtr &= MaxWinMask;
|
||||
|
||||
if (Inp.InAddr >= ReadBorder)
|
||||
{
|
||||
var FileDone = false;
|
||||
|
||||
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 (
|
||||
!ReadBlockHeader(Inp, ref BlockHeader)
|
||||
|| !ReadTables(Inp, ref BlockHeader, ref BlockTables)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (FileDone || !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr)
|
||||
{
|
||||
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (WrittenFileSize > DestUnpSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint MainSlot = DecodeNumber(Inp, BlockTables.LD);
|
||||
if (MainSlot < 256)
|
||||
{
|
||||
if (Fragmented)
|
||||
{
|
||||
FragWindow[UnpPtr++] = (byte)MainSlot;
|
||||
}
|
||||
else
|
||||
{
|
||||
Window[UnpPtr++] = (byte)MainSlot;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (MainSlot >= 262)
|
||||
{
|
||||
uint 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.getbits() >> (int)(20 - DBits)) << 4);
|
||||
Inp.addbits(DBits - 4);
|
||||
}
|
||||
|
||||
uint LowDist = DecodeNumber(Inp, BlockTables.LDD);
|
||||
Distance += LowDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
Distance += Inp.getbits() >> (int)(16 - 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 (!ReadFilter(Inp, Filter) || !AddFilter(Filter))
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (MainSlot == 257)
|
||||
{
|
||||
if (LastLength != 0)
|
||||
{
|
||||
CopyString((uint)LastLength, (uint)OldDist[0]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (MainSlot < 262)
|
||||
{
|
||||
uint DistNum = MainSlot - 258;
|
||||
uint Distance = (uint)OldDist[(int)DistNum];
|
||||
for (var I = (int)DistNum; I > 0; I--)
|
||||
{
|
||||
OldDist[I] = OldDist[I - 1];
|
||||
}
|
||||
OldDist[0] = Distance;
|
||||
|
||||
uint LengthSlot = DecodeNumber(Inp, BlockTables.RD);
|
||||
uint Length = SlotToLength(Inp, LengthSlot);
|
||||
LastLength = Length;
|
||||
CopyString(Length, Distance);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private uint ReadFilterData(BitInput Inp)
|
||||
{
|
||||
var ByteCount = (Inp.fgetbits() >> 14) + 1;
|
||||
@@ -339,6 +513,58 @@ internal partial class Unpack
|
||||
return ReadCode != -1;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> 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;
|
||||
@@ -533,6 +759,163 @@ 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;
|
||||
@@ -664,6 +1047,48 @@ 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)
|
||||
@@ -682,6 +1107,29 @@ 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)
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using SharpCompress.Common;
|
||||
using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef;
|
||||
using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal;
|
||||
#if !Rar2017_64bit
|
||||
using size_t = System.UInt32;
|
||||
#else
|
||||
using nint = System.Int64;
|
||||
using nuint = System.UInt64;
|
||||
using size_t = System.UInt64;
|
||||
#endif
|
||||
|
||||
namespace SharpCompress.Compressors.Rar.UnpackV2017;
|
||||
|
||||
@@ -42,11 +37,9 @@ internal sealed partial class Unpack : BitInput
|
||||
// It prevents crash if first DoUnpack call is later made with wrong
|
||||
// (true) 'Solid' value.
|
||||
UnpInitData(false);
|
||||
#if !RarV2017_SFX_MODULE
|
||||
// RAR 1.5 decompression initialization
|
||||
UnpInitData15(false);
|
||||
InitHuff();
|
||||
#endif
|
||||
}
|
||||
|
||||
// later: may need Dispose() if we support thread pool
|
||||
@@ -110,7 +103,7 @@ internal sealed partial class Unpack : BitInput
|
||||
throw new InvalidFormatException("Grow && Fragmented");
|
||||
}
|
||||
|
||||
var NewWindow = Fragmented ? null : new byte[WinSize];
|
||||
var NewWindow = Fragmented ? null : ArrayPool<byte>.Shared.Rent((int)WinSize);
|
||||
|
||||
if (NewWindow == null)
|
||||
{
|
||||
@@ -126,6 +119,7 @@ internal sealed partial class Unpack : BitInput
|
||||
if (Window != null) // If allocated by preceding files.
|
||||
{
|
||||
//free(Window);
|
||||
ArrayPool<byte>.Shared.Return(Window);
|
||||
Window = null;
|
||||
}
|
||||
FragWindow.Init(WinSize);
|
||||
@@ -170,7 +164,6 @@ internal sealed partial class Unpack : BitInput
|
||||
// just for extra safety.
|
||||
switch (Method)
|
||||
{
|
||||
#if !RarV2017_SFX_MODULE
|
||||
case 15: // rar 1.5 compression
|
||||
if (!Fragmented)
|
||||
{
|
||||
@@ -186,33 +179,64 @@ internal sealed partial class Unpack : BitInput
|
||||
}
|
||||
|
||||
break;
|
||||
#endif
|
||||
#if !RarV2017_RAR5ONLY
|
||||
case 29: // rar 3.x compression
|
||||
if (!Fragmented)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
break;
|
||||
case 50: // RAR 5.0 compression algorithm.
|
||||
Unpack5(Solid);
|
||||
break;
|
||||
#if !Rar2017_NOSTRICT
|
||||
default:
|
||||
throw new InvalidFormatException("unknown compression method " + Method);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task DoUnpackAsync(
|
||||
uint Method,
|
||||
bool Solid,
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Methods <50 will crash in Fragmented mode when accessing NULL Window.
|
||||
// They cannot be called in such mode now, but we check it below anyway
|
||||
// just for extra safety.
|
||||
switch (Method)
|
||||
{
|
||||
#if !RarV2017_SFX_MODULE
|
||||
case 15: // rar 1.5 compression
|
||||
if (!Fragmented)
|
||||
{
|
||||
await Unpack15Async(Solid, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
case 20: // rar 2.x compression
|
||||
case 26: // files larger than 2GB
|
||||
if (!Fragmented)
|
||||
{
|
||||
await Unpack20Async(Solid, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
#endif
|
||||
#if !RarV2017_RAR5ONLY
|
||||
case 29: // rar 3.x compression
|
||||
if (!Fragmented)
|
||||
{
|
||||
// TODO: Create Unpack29Async when ready
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
break;
|
||||
#endif
|
||||
case 50: // RAR 5.0 compression algorithm.
|
||||
/*#if RarV2017_RAR_SMP
|
||||
if (MaxUserThreads > 1)
|
||||
{
|
||||
// We do not use the multithreaded unpack routine to repack RAR archives
|
||||
// in 'suspended' mode, because unlike the single threaded code it can
|
||||
// write more than one dictionary for same loop pass. So we would need
|
||||
// larger buffers of unknown size. Also we do not support multithreading
|
||||
// in fragmented window mode.
|
||||
if (!Fragmented)
|
||||
{
|
||||
Unpack5MT(Solid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif*/
|
||||
Unpack5(Solid);
|
||||
// RAR 5.0 has full async support via UnpReadBufAsync and UnpWriteBuf
|
||||
await Unpack5Async(Solid, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
#if !Rar2017_NOSTRICT
|
||||
default:
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed
|
||||
{
|
||||
[CLSCompliant(true)]
|
||||
public class SqueezeStream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
@@ -35,12 +34,15 @@ namespace SharpCompress.Compressors.Squeezed
|
||||
private readonly int _compressedSize;
|
||||
private const int NUMVALS = 257;
|
||||
private const int SPEOF = 256;
|
||||
private bool _processed = false;
|
||||
|
||||
private Stream _decodedStream;
|
||||
|
||||
public SqueezeStream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
_compressedSize = compressedSize;
|
||||
_decodedStream = BuildDecodedStream();
|
||||
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(SqueezeStream));
|
||||
#endif
|
||||
@@ -51,52 +53,46 @@ namespace SharpCompress.Compressors.Squeezed
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugDispose(typeof(SqueezeStream));
|
||||
#endif
|
||||
_decodedStream?.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
return _decodedStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
// Read numnodes (equivalent to convert_u16!(numnodes, buf))
|
||||
var numnodes = binaryReader.ReadUInt16();
|
||||
private Stream BuildDecodedStream()
|
||||
{
|
||||
var binaryReader = new BinaryReader(_stream, Encoding.Default, leaveOpen: true);
|
||||
int numnodes = binaryReader.ReadUInt16();
|
||||
|
||||
// Validation: numnodes should be within bounds
|
||||
if (numnodes >= NUMVALS)
|
||||
if (numnodes >= NUMVALS || numnodes == 0)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Invalid number of nodes {numnodes} (max {NUMVALS - 1})"
|
||||
);
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
}
|
||||
|
||||
// Handle the case where no nodes exist
|
||||
if (numnodes == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build dnode (tree of nodes)
|
||||
var dnode = new int[numnodes, 2];
|
||||
for (int j = 0; j < numnodes; j++)
|
||||
{
|
||||
@@ -104,42 +100,27 @@ namespace SharpCompress.Compressors.Squeezed
|
||||
dnode[j, 1] = binaryReader.ReadInt16();
|
||||
}
|
||||
|
||||
// Initialize BitReader for reading bits
|
||||
var bitReader = new BitReader(_stream);
|
||||
var decoded = new List<byte>();
|
||||
|
||||
var huffmanDecoded = new MemoryStream();
|
||||
int i = 0;
|
||||
// Decode the buffer using the dnode tree
|
||||
|
||||
while (true)
|
||||
{
|
||||
i = dnode[i, bitReader.ReadBit() ? 1 : 0];
|
||||
if (i < 0)
|
||||
{
|
||||
i = (short)-(i + 1);
|
||||
i = -(i + 1);
|
||||
if (i == SPEOF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.Add((byte)i);
|
||||
i = 0;
|
||||
}
|
||||
huffmanDecoded.WriteByte((byte)i);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack the decoded buffer using the RLE class
|
||||
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
||||
unpacked.CopyTo(buffer, 0);
|
||||
return unpacked.Count();
|
||||
huffmanDecoded.Position = 0;
|
||||
return new RunLength90Stream(huffmanDecoded, (int)huffmanDecoded.Length);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Compressors.Xz;
|
||||
|
||||
public class XZIndexMarkerReachedException : Exception { }
|
||||
public class XZIndexMarkerReachedException : SharpCompressException { }
|
||||
|
||||
42
src/SharpCompress/Factories/ArjFactory.cs
Normal file
42
src/SharpCompress/Factories/ArjFactory.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Arj;
|
||||
|
||||
namespace SharpCompress.Factories
|
||||
{
|
||||
public class ArjFactory : Factory, IReaderFactory
|
||||
{
|
||||
public override string Name => "Arj";
|
||||
|
||||
public override ArchiveType? KnownArchiveType => ArchiveType.Arj;
|
||||
|
||||
public override IEnumerable<string> GetSupportedExtensions()
|
||||
{
|
||||
yield return "arj";
|
||||
}
|
||||
|
||||
public override bool IsArchive(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
int bufferSize = ReaderOptions.DefaultBufferSize
|
||||
)
|
||||
{
|
||||
var arjHeader = new ArjMainHeader(new ArchiveEncoding());
|
||||
if (arjHeader.Read(stream) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
ArjReader.Open(stream, options);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public abstract class Factory : IFactory
|
||||
RegisterFactory(new GZipFactory());
|
||||
RegisterFactory(new TarFactory());
|
||||
RegisterFactory(new ArcFactory());
|
||||
RegisterFactory(new ArjFactory());
|
||||
}
|
||||
|
||||
private static readonly HashSet<Factory> _factories = new();
|
||||
|
||||
@@ -143,7 +143,6 @@ namespace SharpCompress.IO
|
||||
var stack = new List<IStreamStack>();
|
||||
Stream? current = stream as Stream;
|
||||
int lastBufferingIndex = -1;
|
||||
int firstSeekableIndex = -1;
|
||||
Stream? firstSeekableStream = null;
|
||||
|
||||
// Traverse the stack, collecting info
|
||||
@@ -161,7 +160,6 @@ namespace SharpCompress.IO
|
||||
// Find the first seekable stream (closest to the root)
|
||||
if (current != null && current.CanSeek)
|
||||
{
|
||||
firstSeekableIndex = stack.Count;
|
||||
firstSeekableStream = current;
|
||||
}
|
||||
|
||||
@@ -169,16 +167,17 @@ namespace SharpCompress.IO
|
||||
if (lastBufferingIndex != -1)
|
||||
{
|
||||
var bufferingStream = stack[lastBufferingIndex];
|
||||
if (position >= 0 && position < bufferingStream.BufferSize)
|
||||
var targetBufferPosition =
|
||||
position - bufferingStream.GetPosition() + bufferingStream.BufferPosition;
|
||||
|
||||
if (targetBufferPosition >= 0 && targetBufferPosition <= bufferingStream.BufferSize)
|
||||
{
|
||||
bufferingStream.BufferPosition = (int)position;
|
||||
bufferingStream.BufferPosition = (int)targetBufferPosition;
|
||||
return position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If position is not in buffer, reset buffer and proceed as non-buffering
|
||||
bufferingStream.BufferPosition = 0;
|
||||
}
|
||||
|
||||
// If position is not in buffer, reset buffer and proceed as non-buffering
|
||||
bufferingStream.BufferPosition = 0;
|
||||
// Continue to seek as if no buffer is present
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
@@ -93,6 +95,47 @@ internal class ReadOnlySubStream : SharpCompressStream, IStreamStack
|
||||
}
|
||||
#endif
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (BytesLeftToRead < count)
|
||||
{
|
||||
count = (int)BytesLeftToRead;
|
||||
}
|
||||
var read = await Stream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read > 0)
|
||||
{
|
||||
BytesLeftToRead -= read;
|
||||
_position += read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK && !NETSTANDARD2_0
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var sliceLen = BytesLeftToRead < buffer.Length ? BytesLeftToRead : buffer.Length;
|
||||
var read = await Stream
|
||||
.ReadAsync(buffer.Slice(0, (int)sliceLen), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read > 0)
|
||||
{
|
||||
BytesLeftToRead -= read;
|
||||
_position += read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
@@ -79,7 +79,7 @@ public class SharpCompressStream : Stream, IStreamStack
|
||||
{
|
||||
if (value < 0 || value > _bufferedLength)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
_internalPosition = value;
|
||||
_internalPosition = _internalPosition - _bufferPosition + value;
|
||||
_bufferPosition = value;
|
||||
ValidateBufferState(); // Add here
|
||||
}
|
||||
@@ -90,8 +90,6 @@ public class SharpCompressStream : Stream, IStreamStack
|
||||
|
||||
public Stream Stream { get; }
|
||||
|
||||
//private MemoryStream _bufferStream = new();
|
||||
|
||||
private bool _readOnly; //some archive detection requires seek to be disabled to cause it to exception to try the next arc type
|
||||
|
||||
//private bool _isRewound;
|
||||
@@ -293,7 +291,7 @@ public class SharpCompressStream : Stream, IStreamStack
|
||||
|
||||
long bufferPos = _internalPosition - _bufferPosition;
|
||||
|
||||
if (targetPos >= bufferPos && targetPos < bufferPos + _bufferedLength)
|
||||
if (targetPos >= bufferPos && targetPos <= bufferPos + _bufferedLength)
|
||||
{
|
||||
_bufferPosition = (int)(targetPos - bufferPos); //repoint within the buffer
|
||||
_internalPosition = targetPos;
|
||||
|
||||
@@ -96,6 +96,33 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> MoveToNextEntryAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Cancelled)
|
||||
{
|
||||
throw new ReaderCancelledException("Reader has been cancelled.");
|
||||
}
|
||||
if (_entriesForCurrentReadStream is null)
|
||||
{
|
||||
return LoadStreamForReading(RequestInitialStream());
|
||||
}
|
||||
if (!_wroteCurrentEntry)
|
||||
{
|
||||
await SkipEntryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
_wroteCurrentEntry = false;
|
||||
if (NextEntryForCurrentStream())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_completed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool LoadStreamForReading(Stream stream)
|
||||
{
|
||||
_entriesForCurrentReadStream?.Dispose();
|
||||
@@ -129,6 +156,14 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SkipEntryAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Entry.IsDirectory)
|
||||
{
|
||||
await SkipAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Skip()
|
||||
{
|
||||
var part = Entry.Parts.First();
|
||||
@@ -151,6 +186,33 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
s.SkipEntry();
|
||||
}
|
||||
|
||||
private async Task SkipAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var part = Entry.Parts.First();
|
||||
|
||||
if (!Entry.IsSplitAfter && !Entry.IsSolid && Entry.CompressedSize > 0)
|
||||
{
|
||||
//not solid and has a known compressed size then we can skip raw bytes.
|
||||
var rawStream = part.GetRawStream();
|
||||
|
||||
if (rawStream != null)
|
||||
{
|
||||
var bytesToAdvance = Entry.CompressedSize;
|
||||
await rawStream.SkipAsync(bytesToAdvance, cancellationToken).ConfigureAwait(false);
|
||||
part.Skipped = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
//don't know the size so we have to try to decompress to skip
|
||||
#if NETFRAMEWORK || NETSTANDARD2_0
|
||||
using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await s.SkipEntryAsync(cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await s.SkipEntryAsync(cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void WriteEntryTo(Stream writableStream)
|
||||
{
|
||||
if (_wroteCurrentEntry)
|
||||
@@ -232,6 +294,21 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
return stream;
|
||||
}
|
||||
|
||||
public async Task<EntryStream> OpenEntryStreamAsync(
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (_wroteCurrentEntry)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"WriteEntryToAsync or OpenEntryStreamAsync can only be called once."
|
||||
);
|
||||
}
|
||||
var stream = await GetEntryStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
_wroteCurrentEntry = true;
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retains a reference to the entry stream, so we can check whether it completed later.
|
||||
/// </summary>
|
||||
@@ -241,6 +318,10 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtracti
|
||||
protected virtual EntryStream GetEntryStream() =>
|
||||
CreateEntryStream(Entry.Parts.First().GetCompressedStream());
|
||||
|
||||
protected virtual Task<EntryStream> GetEntryStreamAsync(
|
||||
CancellationToken cancellationToken = default
|
||||
) => Task.FromResult(GetEntryStream());
|
||||
|
||||
#endregion
|
||||
|
||||
IEntry IReader.Entry => Entry;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace SharpCompress.Readers.Arc
|
||||
|
||||
protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
|
||||
{
|
||||
ArcEntryHeader headerReader = new ArcEntryHeader(new ArchiveEncoding());
|
||||
ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding);
|
||||
ArcEntryHeader? header;
|
||||
while ((header = headerReader.ReadHeader(stream)) != null)
|
||||
{
|
||||
|
||||
89
src/SharpCompress/Readers/Arj/ArjReader.cs
Normal file
89
src/SharpCompress/Readers/Arj/ArjReader.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arj;
|
||||
using SharpCompress.Common.Arj.Headers;
|
||||
using SharpCompress.Readers.Rar;
|
||||
|
||||
namespace SharpCompress.Readers.Arj
|
||||
{
|
||||
public abstract class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
{
|
||||
internal ArjReader(ReaderOptions options)
|
||||
: base(options, ArchiveType.Arj) { }
|
||||
|
||||
/// <summary>
|
||||
/// Derived class must create or manage the Volume itself.
|
||||
/// AbstractReader.Volume is get-only, so it cannot be set here.
|
||||
/// </summary>
|
||||
public override ArjVolume? Volume => _volume;
|
||||
|
||||
private ArjVolume? _volume;
|
||||
|
||||
/// <summary>
|
||||
/// Opens an ArjReader for Non-seeking usage with a single volume
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static ArjReader Open(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeArjReader(stream, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an ArjReader for Non-seeking usage with multiple volumes
|
||||
/// </summary>
|
||||
/// <param name="streams"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static ArjReader Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeArjReader(streams, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
protected abstract void ValidateArchive(ArjVolume archive);
|
||||
|
||||
protected override IEnumerable<ArjEntry> GetEntries(Stream stream)
|
||||
{
|
||||
var encoding = new ArchiveEncoding();
|
||||
var mainHeaderReader = new ArjMainHeader(encoding);
|
||||
var localHeaderReader = new ArjLocalHeader(encoding);
|
||||
|
||||
var mainHeader = mainHeaderReader.Read(stream);
|
||||
if (mainHeader?.IsVolume == true)
|
||||
{
|
||||
throw new MultiVolumeExtractionException(
|
||||
"Multi volumes are currently not supported"
|
||||
);
|
||||
}
|
||||
if (mainHeader?.IsGabled == true)
|
||||
{
|
||||
throw new CryptographicException(
|
||||
"Password protected archives are currently not supported"
|
||||
);
|
||||
}
|
||||
|
||||
if (_volume == null)
|
||||
{
|
||||
_volume = new ArjVolume(stream, Options, 0);
|
||||
ValidateArchive(_volume);
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
var localHeader = localHeaderReader.Read(stream);
|
||||
if (localHeader == null)
|
||||
break;
|
||||
|
||||
yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<FilePart> CreateFilePartEnumerableForCurrentEntry() =>
|
||||
Entry.Parts;
|
||||
}
|
||||
}
|
||||
117
src/SharpCompress/Readers/Arj/MultiVolumeArjReader.cs
Normal file
117
src/SharpCompress/Readers/Arj/MultiVolumeArjReader.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arj;
|
||||
using SharpCompress.Readers.Rar;
|
||||
|
||||
namespace SharpCompress.Readers.Arj;
|
||||
|
||||
internal class MultiVolumeArjReader : ArjReader
|
||||
{
|
||||
private readonly IEnumerator<Stream> streams;
|
||||
private Stream tempStream;
|
||||
|
||||
internal MultiVolumeArjReader(IEnumerable<Stream> streams, ReaderOptions options)
|
||||
: base(options) => this.streams = streams.GetEnumerator();
|
||||
|
||||
protected override void ValidateArchive(ArjVolume archive) { }
|
||||
|
||||
protected override Stream RequestInitialStream()
|
||||
{
|
||||
if (streams.MoveNext())
|
||||
{
|
||||
return streams.Current;
|
||||
}
|
||||
throw new MultiVolumeExtractionException(
|
||||
"No stream provided when requested by MultiVolumeArjReader"
|
||||
);
|
||||
}
|
||||
|
||||
internal override bool NextEntryForCurrentStream()
|
||||
{
|
||||
if (!base.NextEntryForCurrentStream())
|
||||
{
|
||||
// if we're got another stream to try to process then do so
|
||||
return streams.MoveNext() && LoadStreamForReading(streams.Current);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<FilePart> CreateFilePartEnumerableForCurrentEntry()
|
||||
{
|
||||
var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream);
|
||||
tempStream = null;
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
private class MultiVolumeStreamEnumerator : IEnumerable<FilePart>, IEnumerator<FilePart>
|
||||
{
|
||||
private readonly MultiVolumeArjReader reader;
|
||||
private readonly IEnumerator<Stream> nextReadableStreams;
|
||||
private Stream tempStream;
|
||||
private bool isFirst = true;
|
||||
|
||||
internal MultiVolumeStreamEnumerator(
|
||||
MultiVolumeArjReader r,
|
||||
IEnumerator<Stream> nextReadableStreams,
|
||||
Stream tempStream
|
||||
)
|
||||
{
|
||||
reader = r;
|
||||
this.nextReadableStreams = nextReadableStreams;
|
||||
this.tempStream = tempStream;
|
||||
}
|
||||
|
||||
public IEnumerator<FilePart> GetEnumerator() => this;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this;
|
||||
|
||||
public FilePart Current { get; private set; }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
Current = reader.Entry.Parts.First();
|
||||
isFirst = false; //first stream already to go
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!reader.Entry.IsSplitAfter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (tempStream != null)
|
||||
{
|
||||
reader.LoadStreamForReading(tempStream);
|
||||
tempStream = null;
|
||||
}
|
||||
else if (!nextReadableStreams.MoveNext())
|
||||
{
|
||||
throw new MultiVolumeExtractionException(
|
||||
"No stream provided when requested by MultiVolumeArjReader"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.LoadStreamForReading(nextReadableStreams.Current);
|
||||
}
|
||||
|
||||
Current = reader.Entry.Parts.First();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset() { }
|
||||
}
|
||||
}
|
||||
31
src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs
Normal file
31
src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arj;
|
||||
|
||||
namespace SharpCompress.Readers.Arj
|
||||
{
|
||||
internal class SingleVolumeArjReader : ArjReader
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
|
||||
internal SingleVolumeArjReader(Stream stream, ReaderOptions options)
|
||||
: base(options)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
protected override Stream RequestInitialStream() => _stream;
|
||||
|
||||
protected override void ValidateArchive(ArjVolume archive)
|
||||
{
|
||||
if (archive.IsMultiVolume)
|
||||
{
|
||||
throw new MultiVolumeExtractionException(
|
||||
"Streamed archive is a Multi-volume archive. Use a different ArjReader method to extract."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,23 @@ public interface IReader : IDisposable
|
||||
/// <returns></returns>
|
||||
bool MoveToNextEntry();
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next entry asynchronously by reading more data from the underlying stream. This skips if data has not been read.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> MoveToNextEntryAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the current entry as a stream that will decompress as it is read.
|
||||
/// Read the entire stream or use SkipEntry on EntryStream.
|
||||
/// </summary>
|
||||
EntryStream OpenEntryStream();
|
||||
|
||||
/// <summary>
|
||||
/// Opens the current entry asynchronously as a stream that will decompress as it is read.
|
||||
/// Read the entire stream or use SkipEntry on EntryStream.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
Task<EntryStream> OpenEntryStreamAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -113,16 +113,54 @@ public abstract class RarReader : AbstractReader<RarReaderEntry, RarVolume>
|
||||
);
|
||||
if (Entry.IsRarV3)
|
||||
{
|
||||
return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream));
|
||||
return CreateEntryStream(RarCrcStream.Create(UnpackV1.Value, Entry.FileHeader, stream));
|
||||
}
|
||||
|
||||
if (Entry.FileHeader.FileCrc?.Length > 5)
|
||||
{
|
||||
return CreateEntryStream(
|
||||
new RarBLAKE2spStream(UnpackV2017.Value, Entry.FileHeader, stream)
|
||||
RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream)
|
||||
);
|
||||
}
|
||||
|
||||
return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream));
|
||||
return CreateEntryStream(RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream));
|
||||
}
|
||||
|
||||
protected override async System.Threading.Tasks.Task<EntryStream> GetEntryStreamAsync(
|
||||
System.Threading.CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (Entry.IsRedir)
|
||||
{
|
||||
throw new InvalidOperationException("no stream for redirect entry");
|
||||
}
|
||||
|
||||
var stream = new MultiVolumeReadOnlyStream(
|
||||
CreateFilePartEnumerableForCurrentEntry().Cast<RarFilePart>(),
|
||||
this
|
||||
);
|
||||
if (Entry.IsRarV3)
|
||||
{
|
||||
return CreateEntryStream(
|
||||
await RarCrcStream
|
||||
.CreateAsync(UnpackV1.Value, Entry.FileHeader, stream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
|
||||
if (Entry.FileHeader.FileCrc?.Length > 5)
|
||||
{
|
||||
return CreateEntryStream(
|
||||
await RarBLAKE2spStream
|
||||
.CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
|
||||
return CreateEntryStream(
|
||||
await RarCrcStream
|
||||
.CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public static class ReaderFactory
|
||||
}
|
||||
|
||||
throw new InvalidFormatException(
|
||||
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
|
||||
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<PackageReference Include="ZstdSharp.Port" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'net481' ">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
|
||||
@@ -37,6 +37,20 @@ public abstract class AbstractWriter(ArchiveType type, WriterOptions writerOptio
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public abstract void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
|
||||
public virtual async Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Default implementation calls synchronous version
|
||||
// Derived classes should override for true async behavior
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
|
||||
@@ -50,4 +50,7 @@ public sealed class GZipWriter : AbstractWriter
|
||||
source.CopyTo(stream);
|
||||
_wroteToStream = true;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime) =>
|
||||
throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
}
|
||||
|
||||
@@ -16,4 +16,10 @@ public interface IWriter : IDisposable
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ public static class IWriterExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteDirectory(this IWriter writer, string directoryName) =>
|
||||
writer.WriteDirectory(directoryName, null);
|
||||
|
||||
// Async extensions
|
||||
public static Task WriteAsync(
|
||||
this IWriter writer,
|
||||
@@ -121,4 +124,10 @@ public static class IWriterExtensions
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task WriteDirectoryAsync(
|
||||
this IWriter writer,
|
||||
string directoryName,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writer.WriteDirectoryAsync(directoryName, null, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.Compressors;
|
||||
@@ -72,6 +74,44 @@ public class TarWriter : AbstractWriter
|
||||
return filename.Trim('/');
|
||||
}
|
||||
|
||||
private string NormalizeDirectoryName(string directoryName)
|
||||
{
|
||||
directoryName = NormalizeFilename(directoryName);
|
||||
// Ensure directory name ends with '/' for tar format
|
||||
if (!string.IsNullOrEmpty(directoryName) && !directoryName.EndsWith('/'))
|
||||
{
|
||||
directoryName += '/';
|
||||
}
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime)
|
||||
{
|
||||
var normalizedName = NormalizeDirectoryName(directoryName);
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
{
|
||||
return; // Skip empty or root directory
|
||||
}
|
||||
|
||||
var header = new TarHeader(WriterOptions.ArchiveEncoding);
|
||||
header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH;
|
||||
header.Name = normalizedName;
|
||||
header.Size = 0;
|
||||
header.EntryType = EntryType.Directory;
|
||||
header.Write(OutputStream);
|
||||
}
|
||||
|
||||
public override async Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Synchronous implementation is sufficient for header-only write
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Write(string filename, Stream source, DateTime? modificationTime, long? size)
|
||||
{
|
||||
if (!source.CanSeek && size is null)
|
||||
@@ -91,6 +131,40 @@ public class TarWriter : AbstractWriter
|
||||
PadTo512(size.Value);
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(
|
||||
string filename,
|
||||
Stream source,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
) => await WriteAsync(filename, source, modificationTime, null, cancellationToken);
|
||||
|
||||
public async Task WriteAsync(
|
||||
string filename,
|
||||
Stream source,
|
||||
DateTime? modificationTime,
|
||||
long? size,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (!source.CanSeek && size is null)
|
||||
{
|
||||
throw new ArgumentException("Seekable stream is required if no size is given.");
|
||||
}
|
||||
|
||||
var realSize = size ?? source.Length;
|
||||
|
||||
var header = new TarHeader(WriterOptions.ArchiveEncoding);
|
||||
|
||||
header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH;
|
||||
header.Name = NormalizeFilename(filename);
|
||||
header.Size = realSize;
|
||||
header.Write(OutputStream);
|
||||
var written = await source
|
||||
.TransferToAsync(OutputStream, realSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
PadTo512(written);
|
||||
}
|
||||
|
||||
private void PadTo512(long size)
|
||||
{
|
||||
var zeros = unchecked((int)(((size + 511L) & ~511L) - size));
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
@@ -135,6 +137,73 @@ public class ZipWriter : AbstractWriter
|
||||
return filename.Trim('/');
|
||||
}
|
||||
|
||||
private string NormalizeDirectoryName(string directoryName)
|
||||
{
|
||||
directoryName = NormalizeFilename(directoryName);
|
||||
// Ensure directory name ends with '/' for zip format
|
||||
if (!string.IsNullOrEmpty(directoryName) && !directoryName.EndsWith('/'))
|
||||
{
|
||||
directoryName += '/';
|
||||
}
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime)
|
||||
{
|
||||
var normalizedName = NormalizeDirectoryName(directoryName);
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
{
|
||||
return; // Skip empty or root directory
|
||||
}
|
||||
|
||||
var options = new ZipWriterEntryOptions { ModificationDateTime = modificationTime };
|
||||
WriteDirectoryEntry(normalizedName, options);
|
||||
}
|
||||
|
||||
public override Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Synchronous implementation is sufficient for directory entries
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void WriteDirectoryEntry(string directoryPath, ZipWriterEntryOptions options)
|
||||
{
|
||||
var compression = ZipCompressionMethod.None;
|
||||
|
||||
options.ModificationDateTime ??= DateTime.Now;
|
||||
options.EntryComment ??= string.Empty;
|
||||
|
||||
var entry = new ZipCentralDirectoryEntry(
|
||||
compression,
|
||||
directoryPath,
|
||||
(ulong)streamPosition,
|
||||
WriterOptions.ArchiveEncoding
|
||||
)
|
||||
{
|
||||
Comment = options.EntryComment,
|
||||
ModificationTime = options.ModificationDateTime,
|
||||
Crc = 0,
|
||||
Compressed = 0,
|
||||
Decompressed = 0,
|
||||
};
|
||||
|
||||
// Use the archive default setting for zip64 and allow overrides
|
||||
var useZip64 = isZip64;
|
||||
if (options.EnableZip64.HasValue)
|
||||
{
|
||||
useZip64 = options.EnableZip64.Value;
|
||||
}
|
||||
|
||||
var headersize = (uint)WriteHeader(directoryPath, options, entry, useZip64);
|
||||
streamPosition += headersize;
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
private int WriteHeader(
|
||||
string filename,
|
||||
ZipWriterEntryOptions zipWriterEntryOptions,
|
||||
|
||||
@@ -335,9 +335,9 @@
|
||||
"net8.0": {
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.17, )",
|
||||
"resolved": "8.0.17",
|
||||
"contentHash": "x5/y4l8AtshpBOrCZdlE4txw8K3e3s9meBFeZeR3l8hbbku2V7kK6ojhXvrbjg1rk3G+JqL1BI26gtgc1ZrdUw=="
|
||||
"requested": "[8.0.21, )",
|
||||
"resolved": "8.0.21",
|
||||
"contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
|
||||
61
tests/SharpCompress.Test/AdcAsyncTest.cs
Normal file
61
tests/SharpCompress.Test/AdcAsyncTest.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.ADC;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class AdcAsyncTest : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsyncWholeChunk()
|
||||
{
|
||||
using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"));
|
||||
var decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
var test = new byte[262144];
|
||||
|
||||
await decStream.ReadAsync(test, 0, test.Length);
|
||||
|
||||
Assert.Equal(decompressed, test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsync()
|
||||
{
|
||||
using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"));
|
||||
var decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
using var decMs = new MemoryStream();
|
||||
var test = new byte[512];
|
||||
var count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
count = await decStream.ReadAsync(test, 0, test.Length);
|
||||
decMs.Write(test, 0, count);
|
||||
} while (count > 0);
|
||||
|
||||
Assert.Equal(decompressed, decMs.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestAdcStreamAsyncWithCancellation()
|
||||
{
|
||||
using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"));
|
||||
using var decStream = new ADCStream(cmpFs);
|
||||
var test = new byte[512];
|
||||
using var cts = new System.Threading.CancellationTokenSource();
|
||||
|
||||
// Read should complete without cancellation
|
||||
var bytesRead = await decStream.ReadAsync(test, 0, test.Length, cts.Token);
|
||||
|
||||
Assert.True(bytesRead > 0);
|
||||
}
|
||||
}
|
||||
@@ -23,36 +23,9 @@ namespace SharpCompress.Test.Arc
|
||||
public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None);
|
||||
|
||||
[Fact]
|
||||
public void Arc_Squeezed_Read()
|
||||
{
|
||||
ProcessArchive("Arc.squeezed.arc");
|
||||
}
|
||||
public void Arc_Squeezed_Read() => Read("Arc.squeezed.arc");
|
||||
|
||||
[Fact]
|
||||
public void Arc_Crunched_Read()
|
||||
{
|
||||
ProcessArchive("Arc.crunched.arc");
|
||||
}
|
||||
|
||||
private void ProcessArchive(string archiveName)
|
||||
{
|
||||
// Process a given archive by its name
|
||||
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName)))
|
||||
using (IReader reader = ArcReader.Open(stream))
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
reader.WriteEntryToDirectory(
|
||||
SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyFilesByExtension();
|
||||
}
|
||||
public void Arc_Crunched_Read() => Read("Arc.crunched.arc");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
@@ -569,4 +570,88 @@ public class ArchiveTests : ReaderTests
|
||||
|
||||
return (extractedData, crc);
|
||||
}
|
||||
|
||||
protected async Task ArchiveStreamReadAsync(
|
||||
string testArchive,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
await ArchiveStreamReadAsync(
|
||||
ArchiveFactory.AutoFactory,
|
||||
readerOptions,
|
||||
new[] { testArchive }
|
||||
);
|
||||
}
|
||||
|
||||
protected async Task ArchiveStreamReadAsync(
|
||||
IArchiveFactory archiveFactory,
|
||||
ReaderOptions? readerOptions,
|
||||
IEnumerable<string> testArchives
|
||||
)
|
||||
{
|
||||
foreach (var path in testArchives)
|
||||
{
|
||||
using (
|
||||
var stream = SharpCompressStream.Create(
|
||||
File.OpenRead(path),
|
||||
leaveOpen: true,
|
||||
throwOnDispose: true
|
||||
)
|
||||
)
|
||||
using (var archive = archiveFactory.Open(stream, readerOptions))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
{
|
||||
await entry.WriteToDirectoryAsync(
|
||||
SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
//SevenZipArchive_BZip2_Split test needs this
|
||||
stream.ThrowOnDispose = false;
|
||||
throw;
|
||||
}
|
||||
stream.ThrowOnDispose = false;
|
||||
}
|
||||
VerifyFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArchiveFactory_Open_WithPreWrappedStream()
|
||||
{
|
||||
// Test that ArchiveFactory.Open works correctly with a stream that's already wrapped
|
||||
// This addresses the issue where ZIP files fail to open on Linux
|
||||
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip");
|
||||
|
||||
// Open with a pre-wrapped stream
|
||||
using (var fileStream = File.OpenRead(testArchive))
|
||||
using (var wrappedStream = SharpCompressStream.Create(fileStream, bufferSize: 32768))
|
||||
using (var archive = ArchiveFactory.Open(wrappedStream))
|
||||
{
|
||||
Assert.Equal(ArchiveType.Zip, archive.Type);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArchiveFactory_Open_WithRawFileStream()
|
||||
{
|
||||
// Test that ArchiveFactory.Open works correctly with a raw FileStream
|
||||
// This is the common use case reported in the issue
|
||||
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip");
|
||||
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
using (var archive = ArchiveFactory.Open(stream))
|
||||
{
|
||||
Assert.Equal(ArchiveType.Zip, archive.Type);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
tests/SharpCompress.Test/Arj/ArjReaderTests.cs
Normal file
72
tests/SharpCompress.Test/Arj/ArjReaderTests.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Arj;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Arj
|
||||
{
|
||||
public class ArjReaderTests : ReaderTests
|
||||
{
|
||||
public ArjReaderTests()
|
||||
{
|
||||
UseExtensionInsteadOfNameToVerify = true;
|
||||
UseCaseInsensitiveToVerify = true;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None);
|
||||
|
||||
[Fact]
|
||||
public void Arj_Method4_Read() => Read("Arj.method4.arj");
|
||||
|
||||
[Fact]
|
||||
public void Arj_Encrypted_Read()
|
||||
{
|
||||
var exception = Assert.Throws<CryptographicException>(() => Read("Arj.encrypted.arj"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Arj_Multi_Reader()
|
||||
{
|
||||
var exception = Assert.Throws<MultiVolumeExtractionException>(() =>
|
||||
DoArj_Multi_Reader(
|
||||
[
|
||||
"Arj.store.split.arj",
|
||||
"Arj.store.split.a01",
|
||||
"Arj.store.split.a02",
|
||||
"Arj.store.split.a03",
|
||||
"Arj.store.split.a04",
|
||||
"Arj.store.split.a05",
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void DoArj_Multi_Reader(string[] archives)
|
||||
{
|
||||
using (
|
||||
var reader = ArjReader.Open(
|
||||
archives
|
||||
.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
|
||||
.Select(p => File.OpenRead(p))
|
||||
)
|
||||
)
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
reader.WriteEntryToDirectory(
|
||||
SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
VerifyFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
231
tests/SharpCompress.Test/BZip2/BZip2StreamAsyncTests.cs
Normal file
231
tests/SharpCompress.Test/BZip2/BZip2StreamAsyncTests.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.BZip2;
|
||||
|
||||
public class BZip2StreamAsyncTests
|
||||
{
|
||||
private byte[] CreateTestData(int size)
|
||||
{
|
||||
var data = new byte[size];
|
||||
// Create compressible data with repetitive pattern
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
data[i] = (byte)('A' + (i % 26));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2CompressDecompressAsyncTest()
|
||||
{
|
||||
var testData = CreateTestData(10000);
|
||||
byte[] compressed;
|
||||
|
||||
// Compress
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(testData, 0, testData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Verify compression occurred
|
||||
Assert.True(compressed.Length > 0);
|
||||
Assert.True(compressed.Length < testData.Length);
|
||||
|
||||
// Decompress
|
||||
byte[] decompressed;
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
decompressed = new byte[testData.Length];
|
||||
var totalRead = 0;
|
||||
int bytesRead;
|
||||
while (
|
||||
(
|
||||
bytesRead = await bzip2Stream.ReadAsync(
|
||||
decompressed,
|
||||
totalRead,
|
||||
testData.Length - totalRead
|
||||
)
|
||||
) > 0
|
||||
)
|
||||
{
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify decompression
|
||||
Assert.Equal(testData, decompressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2ReadAsyncWithCancellationTest()
|
||||
{
|
||||
var testData = Encoding.ASCII.GetBytes(new string('A', 5000)); // Repetitive data compresses well
|
||||
byte[] compressed;
|
||||
|
||||
// Compress
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(testData, 0, testData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Decompress with cancellation support
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var buffer = new byte[1024];
|
||||
using var cts = new System.Threading.CancellationTokenSource();
|
||||
|
||||
// Read should complete without cancellation
|
||||
var bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
Assert.True(bytesRead > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2MultipleAsyncWritesTest()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var data1 = Encoding.ASCII.GetBytes("Hello ");
|
||||
var data2 = Encoding.ASCII.GetBytes("World");
|
||||
var data3 = Encoding.ASCII.GetBytes("!");
|
||||
|
||||
await bzip2Stream.WriteAsync(data1, 0, data1.Length);
|
||||
await bzip2Stream.WriteAsync(data2, 0, data2.Length);
|
||||
await bzip2Stream.WriteAsync(data3, 0, data3.Length);
|
||||
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
|
||||
var compressed = memoryStream.ToArray();
|
||||
Assert.True(compressed.Length > 0);
|
||||
|
||||
// Decompress and verify
|
||||
using (var readStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
readStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var buffer = new byte[256];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
result.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead));
|
||||
}
|
||||
|
||||
Assert.Equal("Hello World!", result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BZip2LargeDataAsyncTest()
|
||||
{
|
||||
var largeData = CreateTestData(100000);
|
||||
|
||||
// Compress
|
||||
byte[] compressed;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Compress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
await bzip2Stream.WriteAsync(largeData, 0, largeData.Length);
|
||||
(bzip2Stream as BZip2Stream)?.Finish();
|
||||
}
|
||||
compressed = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Decompress
|
||||
byte[] decompressed;
|
||||
using (var memoryStream = new MemoryStream(compressed))
|
||||
{
|
||||
using (
|
||||
var bzip2Stream = new BZip2Stream(
|
||||
memoryStream,
|
||||
SharpCompress.Compressors.CompressionMode.Decompress,
|
||||
false
|
||||
)
|
||||
)
|
||||
{
|
||||
decompressed = new byte[largeData.Length];
|
||||
var totalRead = 0;
|
||||
int bytesRead;
|
||||
var buffer = new byte[4096];
|
||||
while ((bytesRead = await bzip2Stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
Array.Copy(buffer, 0, decompressed, totalRead, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify
|
||||
Assert.Equal(largeData, decompressed);
|
||||
}
|
||||
}
|
||||
116
tests/SharpCompress.Test/ExceptionHierarchyTests.cs
Normal file
116
tests/SharpCompress.Test/ExceptionHierarchyTests.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class ExceptionHierarchyTests
|
||||
{
|
||||
[Fact]
|
||||
public void AllSharpCompressExceptions_InheritFromSharpCompressException()
|
||||
{
|
||||
// Verify that ArchiveException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ArchiveException)));
|
||||
|
||||
// Verify that ExtractionException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ExtractionException)));
|
||||
|
||||
// Verify that InvalidFormatException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(InvalidFormatException))
|
||||
);
|
||||
|
||||
// Verify that CryptographicException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(CryptographicException))
|
||||
);
|
||||
|
||||
// Verify that IncompleteArchiveException inherits from SharpCompressException (through ArchiveException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(IncompleteArchiveException))
|
||||
);
|
||||
|
||||
// Verify that ReaderCancelledException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(ReaderCancelledException))
|
||||
);
|
||||
|
||||
// Verify that MultipartStreamRequiredException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(
|
||||
typeof(MultipartStreamRequiredException)
|
||||
)
|
||||
);
|
||||
|
||||
// Verify that MultiVolumeExtractionException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(MultiVolumeExtractionException))
|
||||
);
|
||||
|
||||
// Verify that ZlibException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ZlibException)));
|
||||
|
||||
// Verify that XZIndexMarkerReachedException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(XZIndexMarkerReachedException))
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SharpCompressException_CanBeCaughtByBaseType()
|
||||
{
|
||||
// Test that a derived exception can be caught as SharpCompressException
|
||||
var exception = new InvalidFormatException("Test message");
|
||||
var caughtException = false;
|
||||
|
||||
try
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
catch (SharpCompressException ex)
|
||||
{
|
||||
caughtException = true;
|
||||
Assert.Same(exception, ex);
|
||||
}
|
||||
|
||||
Assert.True(caughtException, "Exception should have been caught as SharpCompressException");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InternalLzmaExceptions_InheritFromSharpCompressException()
|
||||
{
|
||||
// Use reflection to verify internal exception types
|
||||
var dataErrorExceptionType = Type.GetType(
|
||||
"SharpCompress.Compressors.LZMA.DataErrorException, SharpCompress"
|
||||
);
|
||||
Assert.NotNull(dataErrorExceptionType);
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(dataErrorExceptionType));
|
||||
|
||||
var invalidParamExceptionType = Type.GetType(
|
||||
"SharpCompress.Compressors.LZMA.InvalidParamException, SharpCompress"
|
||||
);
|
||||
Assert.NotNull(invalidParamExceptionType);
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(invalidParamExceptionType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionConstructors_WorkCorrectly()
|
||||
{
|
||||
// Test parameterless constructor
|
||||
var ex1 = new SharpCompressException();
|
||||
Assert.NotNull(ex1);
|
||||
|
||||
// Test message constructor
|
||||
var ex2 = new SharpCompressException("Test message");
|
||||
Assert.Equal("Test message", ex2.Message);
|
||||
|
||||
// Test message and inner exception constructor
|
||||
var inner = new InvalidOperationException("Inner");
|
||||
var ex3 = new SharpCompressException("Test message", inner);
|
||||
Assert.Equal("Test message", ex3.Message);
|
||||
Assert.Same(inner, ex3.InnerException);
|
||||
}
|
||||
}
|
||||
99
tests/SharpCompress.Test/ExtractionTests.cs
Normal file
99
tests/SharpCompress.Test/ExtractionTests.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Zip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class ExtractionTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows()
|
||||
{
|
||||
// This test validates that extraction succeeds when Path.GetFullPath returns paths
|
||||
// with casing that matches the platform's file system behavior. On Windows,
|
||||
// Path.GetFullPath can return different casing than the actual directory on disk
|
||||
// (e.g., "system32" vs "System32"), and the extraction should succeed because
|
||||
// Windows file systems are case-insensitive. On Unix-like systems, this test
|
||||
// verifies that the case-sensitive comparison is used correctly.
|
||||
|
||||
var testArchive = Path.Combine(SCRATCH2_FILES_PATH, "test-extraction.zip");
|
||||
var extractPath = SCRATCH_FILES_PATH;
|
||||
|
||||
// Create a simple test archive with a single file
|
||||
using (var stream = File.Create(testArchive))
|
||||
{
|
||||
using var writer = (ZipWriter)
|
||||
WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate);
|
||||
|
||||
// Create a test file to add to the archive
|
||||
var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile.txt");
|
||||
File.WriteAllText(testFilePath, "Test content");
|
||||
|
||||
writer.Write("testfile.txt", testFilePath);
|
||||
}
|
||||
|
||||
// Extract the archive - this should succeed regardless of path casing
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
{
|
||||
using var reader = ReaderFactory.Open(stream);
|
||||
|
||||
// This should not throw an exception even if Path.GetFullPath returns
|
||||
// a path with different casing than the actual directory
|
||||
var exception = Record.Exception(() =>
|
||||
reader.WriteAllToDirectory(
|
||||
extractPath,
|
||||
new ExtractionOptions { ExtractFullPath = false, Overwrite = true }
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Null(exception);
|
||||
}
|
||||
|
||||
// Verify the file was extracted successfully
|
||||
var extractedFile = Path.Combine(extractPath, "testfile.txt");
|
||||
Assert.True(File.Exists(extractedFile));
|
||||
Assert.Equal("Test content", File.ReadAllText(extractedFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Extraction_ShouldPreventPathTraversalAttacks()
|
||||
{
|
||||
// This test ensures that the security check still works to prevent
|
||||
// path traversal attacks (e.g., using "../" to escape the destination directory)
|
||||
|
||||
var testArchive = Path.Combine(SCRATCH2_FILES_PATH, "test-traversal.zip");
|
||||
var extractPath = SCRATCH_FILES_PATH;
|
||||
|
||||
// Create a test archive with a path traversal attempt
|
||||
using (var stream = File.Create(testArchive))
|
||||
{
|
||||
using var writer = (ZipWriter)
|
||||
WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate);
|
||||
|
||||
var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile2.txt");
|
||||
File.WriteAllText(testFilePath, "Test content");
|
||||
|
||||
// Try to write with a path that attempts to escape the destination directory
|
||||
writer.Write("../../evil.txt", testFilePath);
|
||||
}
|
||||
|
||||
// Extract the archive - this should throw an exception for path traversal
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
{
|
||||
using var reader = ReaderFactory.Open(stream);
|
||||
|
||||
var exception = Assert.Throws<ExtractionException>(() =>
|
||||
reader.WriteAllToDirectory(
|
||||
extractPath,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Contains("outside of the destination", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers;
|
||||
using Xunit;
|
||||
@@ -191,12 +193,7 @@ public class AsyncTests : TestBase
|
||||
|
||||
// Test async write with GZipStream
|
||||
using (var fileStream = File.Create(compressedPath))
|
||||
using (
|
||||
var gzipStream = new Compressors.Deflate.GZipStream(
|
||||
fileStream,
|
||||
Compressors.CompressionMode.Compress
|
||||
)
|
||||
)
|
||||
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Compress))
|
||||
{
|
||||
await gzipStream.WriteAsync(testData, 0, testData.Length);
|
||||
await gzipStream.FlushAsync();
|
||||
@@ -207,12 +204,7 @@ public class AsyncTests : TestBase
|
||||
|
||||
// Test async read with GZipStream
|
||||
using (var fileStream = File.OpenRead(compressedPath))
|
||||
using (
|
||||
var gzipStream = new Compressors.Deflate.GZipStream(
|
||||
fileStream,
|
||||
Compressors.CompressionMode.Decompress
|
||||
)
|
||||
)
|
||||
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
|
||||
{
|
||||
var decompressed = new byte[testData.Length];
|
||||
var totalRead = 0;
|
||||
|
||||
127
tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs
Normal file
127
tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.GZip;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.GZip;
|
||||
|
||||
public class GZipArchiveAsyncTests : ArchiveTests
|
||||
{
|
||||
public GZipArchiveAsyncTests() => UseExtensionInsteadOfNameToVerify = true;
|
||||
|
||||
[Fact]
|
||||
public async Task GZip_Archive_Generic_Async()
|
||||
{
|
||||
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")))
|
||||
using (var archive = ArchiveFactory.Open(stream))
|
||||
{
|
||||
var entry = archive.Entries.First();
|
||||
await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull()));
|
||||
|
||||
var size = entry.Size;
|
||||
var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"));
|
||||
var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"));
|
||||
|
||||
Assert.Equal(size, scratch.Length);
|
||||
Assert.Equal(size, test.Length);
|
||||
}
|
||||
CompareArchivesByPath(
|
||||
Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"),
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GZip_Archive_Async()
|
||||
{
|
||||
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")))
|
||||
using (var archive = GZipArchive.Open(stream))
|
||||
{
|
||||
var entry = archive.Entries.First();
|
||||
await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull()));
|
||||
|
||||
var size = entry.Size;
|
||||
var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"));
|
||||
var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"));
|
||||
|
||||
Assert.Equal(size, scratch.Length);
|
||||
Assert.Equal(size, test.Length);
|
||||
}
|
||||
CompareArchivesByPath(
|
||||
Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"),
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GZip_Archive_NoAdd_Async()
|
||||
{
|
||||
var jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg");
|
||||
using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
using var archive = GZipArchive.Open(stream);
|
||||
Assert.Throws<InvalidFormatException>(() => archive.AddEntry("jpg\\test.jpg", jpg));
|
||||
await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GZip_Archive_Multiple_Reads_Async()
|
||||
{
|
||||
var inputStream = new MemoryStream();
|
||||
using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")))
|
||||
{
|
||||
await fileStream.CopyToAsync(inputStream);
|
||||
inputStream.Position = 0;
|
||||
}
|
||||
using var archive = GZipArchive.Open(inputStream);
|
||||
var archiveEntry = archive.Entries.First();
|
||||
|
||||
MemoryStream tarStream;
|
||||
using (var entryStream = archiveEntry.OpenEntryStream())
|
||||
{
|
||||
tarStream = new MemoryStream();
|
||||
await entryStream.CopyToAsync(tarStream);
|
||||
}
|
||||
var size = tarStream.Length;
|
||||
using (var entryStream = archiveEntry.OpenEntryStream())
|
||||
{
|
||||
tarStream = new MemoryStream();
|
||||
await entryStream.CopyToAsync(tarStream);
|
||||
}
|
||||
Assert.Equal(size, tarStream.Length);
|
||||
using (var entryStream = archiveEntry.OpenEntryStream())
|
||||
{
|
||||
var result = TarArchive.IsTarFile(entryStream);
|
||||
Assert.True(result);
|
||||
}
|
||||
Assert.Equal(size, tarStream.Length);
|
||||
using (var entryStream = archiveEntry.OpenEntryStream())
|
||||
{
|
||||
tarStream = new MemoryStream();
|
||||
await entryStream.CopyToAsync(tarStream);
|
||||
}
|
||||
Assert.Equal(size, tarStream.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGzCrcWithMostSignificantBitNotNegative_Async()
|
||||
{
|
||||
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
using var archive = GZipArchive.Open(stream);
|
||||
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
{
|
||||
Assert.InRange(entry.Crc, 0L, 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGzArchiveTypeGzip_Async()
|
||||
{
|
||||
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
using var archive = GZipArchive.Open(stream);
|
||||
Assert.Equal(archive.Type, ArchiveType.GZip);
|
||||
}
|
||||
}
|
||||
19
tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs
Normal file
19
tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Archives.GZip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.GZip;
|
||||
|
||||
public class GZipArchiveDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GZipArchive_AddDirectoryEntry_ThrowsNotSupportedException()
|
||||
{
|
||||
using var archive = GZipArchive.Create();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Archives;
|
||||
@@ -124,4 +125,60 @@ public class GZipArchiveTests : ArchiveTests
|
||||
using var archive = GZipArchive.Open(stream);
|
||||
Assert.Equal(archive.Type, ArchiveType.GZip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GZip_Archive_NonSeekableStream()
|
||||
{
|
||||
// Test that GZip extraction works with non-seekable streams (like HttpBaseStream)
|
||||
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
|
||||
var buffer = new MemoryStream();
|
||||
fileStream.CopyTo(buffer);
|
||||
buffer.Position = 0;
|
||||
|
||||
// Create a non-seekable wrapper around the MemoryStream
|
||||
using var nonSeekableStream = new NonSeekableStream(buffer);
|
||||
using var reader = SharpCompress.Readers.GZip.GZipReader.Open(nonSeekableStream);
|
||||
|
||||
// Verify we can move to the first entry and read it without exceptions
|
||||
Assert.True(reader.MoveToNextEntry());
|
||||
Assert.NotNull(reader.Entry);
|
||||
|
||||
// Extract and verify the entry can be read
|
||||
using var outputStream = new MemoryStream();
|
||||
reader.WriteEntryTo(outputStream);
|
||||
|
||||
Assert.True(outputStream.Length > 0);
|
||||
}
|
||||
|
||||
// Helper class to simulate a non-seekable stream like HttpBaseStream
|
||||
private class NonSeekableStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
public NonSeekableStream(Stream baseStream) => _baseStream = baseStream;
|
||||
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
public override bool CanSeek => false; // Simulate non-seekable stream
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => _baseStream.Flush();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
_baseStream.Read(buffer, offset, count);
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user