Compare commits

...

124 Commits

Author SHA1 Message Date
Adam Hathcock
73e9b61fcc try a new agent config 2025-10-25 16:17:42 +01:00
Adam Hathcock
699bc5f34b chore: add Copilot coding agent config and CI workflow 2025-10-25 16:05:09 +01:00
Adam Hathcock
9eed8e842c Merge pull request #972 from TwanVanDongen/master
Handle vendor-specific and malformed ZIP extra fields safely
2025-10-25 13:53:10 +01:00
Twan van Dongen
6d652a12ee And again forgot to apply CSharpierAdds bounds checks to prevent exceptions when extra fields are truncated or non-standard (e.g., 0x4341 "AC"/ARC0). Stops parsing gracefully, allowing other fields to be processed. 2025-10-24 17:18:37 +02:00
Adam Hathcock
e043e06656 Merge pull request #969 from adamhathcock/adam/perf
Add JB perf testing project.
2025-10-23 14:34:43 +01:00
Adam Hathcock
14b52599f4 Update src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 14:20:54 +01:00
Adam Hathcock
e3e2c0c567 Update tests/SharpCompress.Performance/LargeMemoryStream.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 14:19:16 +01:00
Adam Hathcock
4fc5d60f03 reduce visibility 2025-10-23 14:16:39 +01:00
Adam Hathcock
c37a9e0f82 Merge remote-tracking branch 'origin/adam/perf' into adam/perf 2025-10-23 13:50:31 +01:00
Adam Hathcock
fed17ebb96 fmt 2025-10-23 13:50:07 +01:00
Adam Hathcock
eeac678872 More usage of pool and better copy 2025-10-23 13:49:54 +01:00
Adam Hathcock
f9ed0f2df9 Update tests/SharpCompress.Performance/Program.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 11:47:42 +01:00
Adam Hathcock
0ddbacac85 Update src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 11:47:27 +01:00
Adam Hathcock
f0d28aa5cf fmt 2025-10-23 11:43:38 +01:00
Adam Hathcock
cc84f6fee4 more making rar faster 2025-10-23 11:43:21 +01:00
Adam Hathcock
00e6eef369 used AI to optimize some copys and shifting 2025-10-23 11:18:50 +01:00
Adam Hathcock
1ae71907bc don't need to clear everything 2025-10-23 10:53:54 +01:00
Adam Hathcock
3ff688fba2 clear and null check 2025-10-23 10:48:18 +01:00
Adam Hathcock
bb59b3d456 add pool to LZMA out window 2025-10-23 09:54:52 +01:00
Adam Hathcock
186ea74ada add some fixes for rar to pool memory 2025-10-23 09:40:15 +01:00
Adam Hathcock
c108f2dcf3 add perf testing project using JB memory and cpu 2025-10-23 09:39:57 +01:00
Adam Hathcock
4cca232d83 Merge pull request #959 from adamhathcock/adam/xz-wrapped-often
Removed wrappers that weren't needed (probably)
2025-10-22 11:54:47 +01:00
Adam Hathcock
1db511e9cb Merge branch 'master' into adam/xz-wrapped-often 2025-10-22 11:51:46 +01:00
Adam Hathcock
76afa7d3bf Merge pull request #968 from adamhathcock/adam/rework-deps
rework dependencies to be correct for frameworks and update
2025-10-22 11:51:30 +01:00
Adam Hathcock
3513f7b1cd Update src/SharpCompress/SharpCompress.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 10:51:12 +01:00
Adam Hathcock
4531fe39e6 Merge branch 'master' into adam/rework-deps 2025-10-22 10:48:16 +01:00
Adam Hathcock
8d276a85bc rework dependencies to be correct for frameworks and update 2025-10-22 10:47:43 +01:00
Adam Hathcock
5f0d042bc3 Merge pull request #967 from adamhathcock/adam/reduce-custom-utilities
Reduce custom utilities for arrays/bytes
2025-10-22 10:41:10 +01:00
Adam Hathcock
408f07e3c4 Merge branch 'master' into adam/reduce-custom-utilities 2025-10-22 10:38:01 +01:00
Adam Hathcock
d1a540c90c use windows instead of skippable fact 2025-10-22 10:32:47 +01:00
Adam Hathcock
00df8e930e add windows only compile constant 2025-10-22 10:30:40 +01:00
Adam Hathcock
3b768b1b77 Merge pull request #961 from adamhathcock/dependabot/nuget/AwesomeAssertions-9.2.1
Bump AwesomeAssertions from 9.2.0 to 9.2.1
2025-10-22 10:25:01 +01:00
Adam Hathcock
42a7ececa0 Merge branch 'master' into adam/xz-wrapped-often 2025-10-22 10:22:36 +01:00
Adam Hathcock
e8867de049 Merge branch 'master' into dependabot/nuget/AwesomeAssertions-9.2.1 2025-10-22 10:21:59 +01:00
Adam Hathcock
a1dfa3dfa3 xplat tests for path characters 2025-10-22 10:21:22 +01:00
Adam Hathcock
83917d4f79 Merge remote-tracking branch 'origin/master' into adam/reduce-custom-utilities 2025-10-22 10:17:20 +01:00
Adam Hathcock
513cd4f905 some AI suggestions 2025-10-22 10:16:45 +01:00
Adam Hathcock
eda0309df3 Merge pull request #966 from adamhathcock/adam/reduce-stackalloc
Remove a dynamically created stackalloc
2025-10-22 10:13:14 +01:00
Adam Hathcock
74e27c028e fix the span length 2025-10-22 10:10:07 +01:00
Adam Hathcock
36c06c4089 ugh, this is used because it shadows a field 2025-10-22 09:32:19 +01:00
Adam Hathcock
249b8a9cdd add AI generated tests 2025-10-22 09:28:07 +01:00
Adam Hathcock
62bee15f00 fmt 2025-10-22 09:19:30 +01:00
Adam Hathcock
d8797b69e4 remove do while 2025-10-22 09:19:09 +01:00
Adam Hathcock
084fe72b02 Consolidate not null 2025-10-22 09:17:13 +01:00
Adam Hathcock
c823acaa3f optimize ReadFully and Skip 2025-10-22 09:10:16 +01:00
Adam Hathcock
e0d6cd9cb7 Try to reduce custom functions for array/byte management 2025-10-22 09:00:21 +01:00
Adam Hathcock
01021e102b remove some extra stackallocs 2025-10-22 08:36:03 +01:00
Adam Hathcock
6de738ff17 reduce dynamic stackallocs in unpackv1 2025-10-22 08:32:19 +01:00
Adam Hathcock
c0612547eb Merge pull request #964 from adamhathcock/adam/extract-all-solid-only
Only allow extract all on archives that are solid (some rars and 7zip only)
2025-10-21 14:08:23 +01:00
Adam Hathcock
e960907698 Update src/SharpCompress/Archives/AbstractArchive.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-21 13:55:56 +01:00
Adam Hathcock
84e03b1b27 Allow 7zip files of all sizes? 2025-10-21 10:28:58 +01:00
Adam Hathcock
f1a80da34b fix tests that use extract all wrongly 2025-10-21 09:56:29 +01:00
Adam Hathcock
5a5a55e556 fmt 2025-10-21 09:22:35 +01:00
Adam Hathcock
e1f132b45b Only allow extract all on archives that are solid (some rars and 7zip only) 2025-10-21 09:21:46 +01:00
dependabot[bot]
087011aede Bump AwesomeAssertions from 9.2.0 to 9.2.1
---
updated-dependencies:
- dependency-name: AwesomeAssertions
  dependency-version: 9.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 10:44:17 +00:00
Adam Hathcock
1430bf9b31 fmt 2025-10-15 09:54:13 +01:00
Adam Hathcock
4e5de817ef Removed too many wrappers
# Conflicts:
#	src/SharpCompress/Compressors/Xz/XZIndex.cs
2025-10-15 09:53:46 +01:00
Adam Hathcock
5d6b94f8c3 Merge pull request #952 from adamhathcock/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-10-14 08:25:53 +01:00
Adam Hathcock
8dfbe56f42 Merge branch 'master' into dependabot/github_actions/actions/checkout-5 2025-10-14 08:23:18 +01:00
Adam Hathcock
df79d983d7 Merge pull request #957 from adamhathcock/dependabot/github_actions/actions/setup-dotnet-5
Bump actions/setup-dotnet from 4 to 5
2025-10-14 08:22:47 +01:00
dependabot[bot]
6c23a28826 Bump actions/setup-dotnet from 4 to 5
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 16:21:25 +00:00
dependabot[bot]
f72289570a Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 16:12:51 +00:00
Adam Hathcock
51bc9dc20e Merge pull request #950 from adamhathcock/adamhathcock-patch-1
Configure Dependabot for NuGet updates
2025-10-13 16:52:07 +01:00
Adam Hathcock
e45ac6bfa9 Update .github/dependabot.yml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-13 16:48:55 +01:00
Adam Hathcock
44d1cbdb0c Configure Dependabot for NuGet updates
Added NuGet package ecosystem configuration for Dependabot.
2025-10-13 16:47:46 +01:00
Adam Hathcock
3fa7e854d3 Merge pull request #948 from adamhathcock/adam/update-0410
update to 0.41.0 and change symbols type
2025-10-13 12:32:54 +01:00
Adam Hathcock
0f5c080be6 update to 0.41.0 and change symbols type 2025-10-13 12:29:02 +01:00
Adam Hathcock
871009ef8d Merge pull request #947 from adamhathcock/adam/update-deps
Update dependencies and csharpier
2025-10-13 12:13:51 +01:00
Adam Hathcock
89a565c6c4 format 2025-10-13 12:10:42 +01:00
Adam Hathcock
6ed60e4d5f adjust Microsoft.NETFramework.ReferenceAssemblies 2025-10-13 12:08:39 +01:00
Adam Hathcock
4dab1df3ae adjust usage of sourcelink 2025-10-13 12:06:34 +01:00
Adam Hathcock
7b7aa2cdf0 Merge remote-tracking branch 'origin/master' into adam/update-deps 2025-10-13 11:56:50 +01:00
Adam Hathcock
d1e1a65f32 add agents file 2025-10-13 11:55:41 +01:00
Adam Hathcock
9d332b4ac5 add agents file 2025-10-13 11:52:18 +01:00
Adam Hathcock
fc2462e281 Update dependencies and csharpier 2025-10-13 10:40:43 +01:00
Adam Hathcock
fb2cddabf0 Merge pull request #945 from mitchcapper/archive_extension_hinting_pr
Extension hinting for ReaderFactory for better first try factory success
2025-09-29 09:02:11 +01:00
Mitch Capper
33481a9465 Extension hinting for ReaderFactory for better first try factory success
Also used by TarFactory to hint what compressiontype to attempt first
2025-09-24 00:04:07 -07:00
Adam Hathcock
45de87cb97 Merge pull request #943 from mitchcapper/zstandard_reading_pr
ZStandard tar support
2025-09-23 13:34:26 +01:00
Mitch Capper
553c533ada ZStandard tar support 2025-09-23 03:26:33 -07:00
Adam Hathcock
634e562b93 Merge pull request #935 from Nanook/bugfix/StreamBufferFix
Rewind buffer fix for directory extract.
2025-08-22 14:08:39 +01:00
Adam Hathcock
c789ead590 Merge branch 'master' into bugfix/StreamBufferFix 2025-08-22 14:05:35 +01:00
Adam Hathcock
d23560a441 Merge pull request #939 from Morilli/fix-winaescryptostream-dispose
Fix WinzipAesCryptoStream potentially not getting disposed
2025-08-22 14:03:06 +01:00
Morilli
9f306966db Adjust logic in CreateDecompressionStream to not wrap the returned stream
a ReadOnlySubStream never disposes its passes in stream which is problematic when that stream needs to be disposed. It's hard to tell who exactly has ownership over which stream here, but I'll just assume that whatever stream is passed in here should be disposed.
2025-08-21 20:08:57 +02:00
Morilli
8eea2cef97 add failing test 2025-08-21 20:08:34 +02:00
Nanook
3abbb89c2e Comment typo edit. 2025-08-01 21:50:36 +01:00
Nanook
76de7d54c7 Rewind buffer fix for directory extract. 2025-08-01 21:47:09 +01:00
Adam Hathcock
35aee6e066 Merge pull request #934 from Nanook/feature/zstd_zip_writing
Zip ZStandard Writing with tests. Level support.
2025-07-24 08:24:32 +01:00
Nanook
8a0152fe7c Fixed up test issue after copilot "fixes" :S. 2025-07-24 01:33:18 +01:00
Nanook
7769435fc8 CSharpier fixes after copilot tweaks. 2025-07-24 01:31:30 +01:00
Nanook
ae311ea66e Update tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 01:28:56 +01:00
Nanook
d15816f162 Update tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 01:28:40 +01:00
Nanook
5cbb31c559 Update tests/SharpCompress.Test/Zip/TestPseudoTextStream.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 01:28:23 +01:00
Nanook
b7f5b36f2b Update src/SharpCompress/Writers/Zip/ZipWriter.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 01:28:11 +01:00
Nanook
e9dd413de6 CSharpier formatting. 2025-07-24 00:46:09 +01:00
Nanook
c5de3d8cc1 Zip ZStandard Writing with tests. Level support. 2025-07-24 00:23:55 +01:00
Adam Hathcock
624c385e27 Merge pull request #930 from Nanook/feature/StreamStack
Added IStreamStack for debugging and configurable buffer management. …
2025-07-23 08:48:58 +01:00
Adam Hathcock
893890c985 Merge branch 'master' into feature/StreamStack 2025-07-23 08:46:16 +01:00
Adam Hathcock
e12efc8b52 Merge pull request #933 from Morilli/zipentry-attribute
Implement `Attrib` for `ZipEntry`
2025-07-23 08:45:04 +01:00
Morilli
015dfa41b1 implement Attrib for ZipEntrys 2025-07-22 20:12:22 +02:00
Morilli
5e72ce5fbe add failing test 2025-07-22 20:11:13 +02:00
Adam Hathcock
236e653176 Merge branch 'master' into feature/StreamStack 2025-07-22 11:55:57 +01:00
Adam Hathcock
3946c3ba03 Merge pull request #931 from SimonCahill/feat/add-throw-on-unseekable-stream
Added ArgumentException to Archive.Open implementations
2025-07-22 11:54:23 +01:00
Adam Hathcock
5bdd39e6eb Merge branch 'master' into feat/add-throw-on-unseekable-stream 2025-07-22 11:50:04 +01:00
Adam Hathcock
46267c0531 Merge pull request #929 from Morilli/fix-zipentry-comment
Fix zipentry comment implementation
2025-07-22 11:49:12 +01:00
Adam Hathcock
7b96eb7704 Merge pull request #928 from Morilli/fix-dotsettings
fix DotSettings options to conform to current code style and editorconfig
2025-07-22 11:47:36 +01:00
Nanook
60d5955101 Final tidy. 2025-07-22 00:12:30 +01:00
Nanook
587675e488 Fixed some missing features caused by merging an earlier branch than required. 2025-07-21 23:45:57 +01:00
Simon Cahill
c0ae319c63 Reverted debug change 2025-07-21 15:41:33 +02:00
Simon Cahill
d810f8d8db Reverted back; I don't know how this file changed 2025-07-21 15:38:14 +02:00
Simon Cahill
a88390a546 Added ArgumentException which is thrown when a non-seekable Stream instance is passed 2025-07-21 15:34:03 +02:00
Nanook
8575e5615d Formatting fix. 2025-07-20 19:18:31 +01:00
Nanook
5fe9516c09 Various copilot suggestions. Tests still passing. 2025-07-20 19:16:11 +01:00
Nanook
938775789d Formated with Sharpier. 2025-07-20 18:11:37 +01:00
Nanook
f37bebe51f Merge branch 'master' into feature/StreamStack 2025-07-20 17:46:28 +01:00
Nanook
21f14cd3f2 Added IStreamStack for debugging and configurable buffer management. Added SharpCompressStream to consolodate streams to help simplify debugging. All unit tests passing. 2025-07-20 17:35:22 +01:00
Morilli
4e7baeb2c9 fix zipentry comment being lost after reading local entry header 2025-07-19 19:56:41 +02:00
Morilli
d78a682dd8 add failing test 2025-07-19 19:56:38 +02:00
Morilli
44021b7abc fix DotSettings options to conform to current code style and editorconfig 2025-07-19 19:30:52 +02:00
Adam Hathcock
7f9e543213 Merge pull request #924 from Morilli/7zip-1block-solid-archive
Fix 7-zip solid archive detection
2025-07-14 09:12:43 +01:00
Morilli
c489e5a59b fix SevenZipArchive solid detection 2025-07-13 15:09:59 +02:00
Morilli
8de30db7f9 add failing test 2025-07-13 15:08:45 +02:00
Adam Hathcock
415f0c6774 Merge pull request #921 from Morilli/fix-volume-filename
Fix volume FileName property potentially missing
2025-07-07 09:22:45 +01:00
Morilli
aa9cf195a5 Fix volume FileName property potentially missing 2025-07-06 21:56:54 +02:00
Adam Hathcock
6412fc8135 Mark for 0.40 2025-06-03 08:48:34 +01:00
155 changed files with 8192 additions and 4168 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.0.2",
"version": "1.1.2",
"commands": [
"csharpier"
],

13
.github/COPILOT_AGENT_README.md vendored Normal file
View File

@@ -0,0 +1,13 @@
# Copilot Coding Agent Configuration
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/workflows/dotnetcore.yml: CI runs on PRs touching the solution, source, or tests to validate changes
- AGENTS.yml: general information for this project
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.

7
.github/agents/copilot-agent.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
enabled: true
agent:
name: copilot-coding-agent
allow:
- paths: ["src/**/*", "tests/**/*", "README.md", "AGENTS.md"]
actions: ["create", "modify"]
require_review_before_merge: true

View File

@@ -1,6 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions" # search for actions - there are other options available
directory: "/" # search in .github/workflows under root `/`
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly" # check for action update every week
interval: "weekly"
- package-ecosystem: "nuget"
directory: "/" # change to "/src/YourProject" if .csproj files are in subfolders
schedule:
interval: "weekly"
open-pull-requests-limit: 5
# optional: target-branch: "master"

View File

@@ -14,8 +14,8 @@ jobs:
os: [windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x
- run: dotnet run --project build/build.csproj

64
AGENTS.md Normal file
View File

@@ -0,0 +1,64 @@
---
description: 'Guidelines for building C# applications'
applyTo: '**/*.cs'
---
# C# Development
## C# Instructions
- Always use the latest version C#, currently C# 13 features.
- Write clear and concise comments for each function.
## 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.
## Naming Conventions
- Follow PascalCase for component names, method names, and public members.
- Use camelCase for private fields and local variables.
- Prefix interface names with "I" (e.g., IUserService).
## 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 .`
- 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.
## 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.
## Nullable Reference Types
- Declare variables non-nullable, and check for `null` at entry points.
- 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.
## Testing
- Always include test cases for critical paths of the application.
- Guide users through creating unit tests.
- 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.

View File

@@ -1,19 +1,20 @@
<Project>
<ItemGroup>
<PackageVersion Include="Bullseye" Version="6.0.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.0.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.2.1" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="JetBrains.Profiler.SelfApi" Version="2.5.14" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.Buffers" Version="4.6.0" />
<PackageVersion Include="System.Memory" Version="4.6.0" />
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0" />
<PackageVersion Include="xunit.SkippableFact" Version="1.5.23" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.5" />
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
</ItemGroup>
</Project>

View File

@@ -15,6 +15,7 @@
| Tar | None | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.GZip | DEFLATE | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.Zstandard | ZStandard | Decompress | TarArchive | TarReader | N/A |
| Tar.LZip | LZMA | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | TarWriter (3) |
| GZip (single file) | DEFLATE | Both | GZipArchive | GZipReader | GZipWriter |
@@ -41,6 +42,7 @@ For those who want to directly compress/decompress bits. The single file formats
| ADCStream | Decompress |
| LZipStream | Both |
| XZStream | Decompress |
| ZStandardStream | Decompress |
## Archive Formats vs Compression

View File

@@ -1,6 +1,6 @@
# SharpCompress
SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0 and NET 8.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0 and NET 8.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip, unzstd with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream).
@@ -20,10 +20,12 @@ In general, I recommend GZip (Deflate)/BZip2 (BZip)/LZip (LZMA) as the simplicit
Zip is okay, but it's a very hap-hazard format and the variation in headers and implementations makes it hard to get correct. Uses Deflate by default but supports a lot of compression methods.
RAR is not recommended as it's a propriatory format and the compression is closed source. Use Tar/LZip for LZMA
RAR is not recommended as it's a proprietary format and the compression is closed source. Use Tar/LZip for LZMA
7Zip and XZ both are overly complicated. 7Zip does not support streamable formats. XZ has known holes explained here: (http://www.nongnu.org/lzip/xz_inadequate.html) Use Tar/LZip for LZMA compression instead.
ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for. We currently only implement decompression for ZStandard but as we leverage the [ZstdSharp](https://github.com/oleg-st/ZstdSharp) library one could likely add compression support without much trouble (PRs are welcome!).
## A Simple Request
Hi everyone. I hope you're using SharpCompress and finding it useful. Please give me feedback on what you'd like to see changed especially as far as usability goes. New feature suggestions are always welcome as well. I would also like to know what projects SharpCompress is being used in. I like seeing how it is used to give me ideas for future versions. Thanks!
@@ -40,6 +42,7 @@ I'm always looking for help or ideas. Please submit code or email with ideas. Un
* 7Zip writing
* Zip64 (Need writing and extend Reading)
* Multi-volume Zip support.
* ZStandard writing
## Version Log

View File

@@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{CDB425
.github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCompress.Performance", "tests\SharpCompress.Performance\SharpCompress.Performance.csproj", "{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -41,6 +43,10 @@ Global
{D4D613CB-5E94-47FB-85BE-B8423D20C545}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4D613CB-5E94-47FB-85BE-B8423D20C545}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4D613CB-5E94-47FB-85BE-B8423D20C545}.Release|Any CPU.Build.0 = Release|Any CPU
{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -48,5 +54,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{FD19DDD8-72B2-4024-8665-0D1F7A2AA998} = {3C5BE746-03E5-4895-9988-0B57F162F86C}
{F2B1A1EB-0FA6-40D0-8908-E13247C7226F} = {0F0901FF-E8D9-426A-B5A2-17C7F47C1529}
{5BDE6DBC-9E5F-4E21-AB71-F138A3E72B17} = {0F0901FF-E8D9-426A-B5A2-17C7F47C1529}
EndGlobalSection
EndGlobal

View File

@@ -15,17 +15,17 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">Basic Clean</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/APPLY_ON_COMPLETION/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ARGUMENTS_NAMED/@EntryValue">Named</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ARGUMENTS_NAMED/@EntryValue">Positional</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_FIRST_ARG_BY_PAREN/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_FIRST_ARG_BY_PAREN/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_LINQ_QUERY/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARGUMENT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARRAY_AND_OBJECT_INITIALIZER/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARRAY_AND_OBJECT_INITIALIZER/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXPRESSION/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXPRESSION/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_FOR_STMT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PARAMETER/@EntryValue">True</s:Boolean>
@@ -42,7 +42,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_IFELSE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_USING_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_WHILE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_ANONYMOUS_METHOD_BLOCK/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_ANONYMOUS_METHOD_BLOCK/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
@@ -50,12 +50,12 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CONSTRUCTOR_INITIALIZER_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSORHOLDER_ON_SINGLE_LINE/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSORHOLDER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
@@ -67,13 +67,13 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/STICK_COMMENT/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
@@ -122,6 +122,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CustomTools/CustomToolsData/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>

View File

@@ -14,31 +14,11 @@
"resolved": "1.1.9",
"contentHash": "AfK5+ECWYTP7G3AAdnU8IfVj+QpGjrh9GC2mpdcJzCvtQ4pnerAGwHsxJ9D4/RnhDUz2DSzd951O/lQjQby2Sw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"SimpleExec": {
"type": "Direct",
"requested": "[12.0.0, )",
"resolved": "12.0.0",
"contentHash": "ptxlWtxC8vM6Y6e3h9ZTxBBkOWnWrm/Sa1HT+2i1xcXY3Hx2hmKDZP5RShPf8Xr9D+ivlrXNy57ktzyH8kyt+Q=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
}
}
}

View File

@@ -144,6 +144,12 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
/// <returns></returns>
public IReader ExtractAllEntries()
{
if (!IsSolid && Type != ArchiveType.SevenZip)
{
throw new InvalidOperationException(
"ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)."
);
}
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return CreateReaderForSolidExtraction();
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
@@ -19,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);
return FindFactory<IArchiveFactory>(stream).Open(stream, readerOptions);
}
@@ -44,7 +45,7 @@ public static class ArchiveFactory
/// <param name="options"></param>
public static IArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), options);
}
@@ -67,7 +68,7 @@ public static class ArchiveFactory
/// <param name="options"></param>
public static IArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
{
@@ -80,7 +81,7 @@ public static class ArchiveFactory
return Open(fileInfo, options);
}
fileInfo.CheckNotNull(nameof(fileInfo));
fileInfo.NotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
return FindFactory<IMultiArchiveFactory>(fileInfo).Open(filesArray, options);
@@ -93,7 +94,7 @@ public static class ArchiveFactory
/// <param name="options"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
@@ -106,7 +107,7 @@ public static class ArchiveFactory
return Open(firstStream, options);
}
firstStream.CheckNotNull(nameof(firstStream));
firstStream.NotNull(nameof(firstStream));
options ??= new ReaderOptions();
return FindFactory<IMultiArchiveFactory>(firstStream).Open(streamsArray, options);
@@ -128,7 +129,7 @@ public static class ArchiveFactory
private static T FindFactory<T>(FileInfo finfo)
where T : IFactory
{
finfo.CheckNotNull(nameof(finfo));
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return FindFactory<T>(stream);
}
@@ -136,7 +137,7 @@ public static class ArchiveFactory
private static T FindFactory<T>(Stream stream)
where T : IFactory
{
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
@@ -165,17 +166,25 @@ public static class ArchiveFactory
);
}
public static bool IsArchive(string filePath, out ArchiveType? type)
public static bool IsArchive(
string filePath,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
using Stream s = File.OpenRead(filePath);
return IsArchive(s, out type);
return IsArchive(s, out type, bufferSize);
}
public static bool IsArchive(Stream stream, out ArchiveType? type)
public static bool IsArchive(
Stream stream,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
type = null;
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
@@ -206,7 +215,7 @@ public static class ArchiveFactory
/// <returns></returns>
public static IEnumerable<string> GetFileParts(string part1)
{
part1.CheckNotNullOrEmpty(nameof(part1));
part1.NotNullOrEmpty(nameof(part1));
return GetFileParts(new FileInfo(part1)).Select(a => a.FullName);
}
@@ -217,7 +226,7 @@ public static class ArchiveFactory
/// <returns></returns>
public static IEnumerable<FileInfo> GetFileParts(FileInfo part1)
{
part1.CheckNotNull(nameof(part1));
part1.NotNull(nameof(part1));
yield return part1;
foreach (var factory in Factory.Factories.OfType<IFactory>())

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
@@ -14,8 +14,11 @@ class AutoArchiveFactory : IArchiveFactory
public IEnumerable<string> GetSupportedExtensions() => throw new NotSupportedException();
public bool IsArchive(Stream stream, string? password = null) =>
throw new NotSupportedException();
public bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => throw new NotSupportedException();
public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException();

View File

@@ -21,7 +21,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="readerOptions"></param>
public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
@@ -32,7 +32,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="readerOptions"></param>
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
fileInfo.NotNull(nameof(fileInfo));
return new GZipArchive(
new SourceStream(
fileInfo,
@@ -52,7 +52,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new GZipArchive(
new SourceStream(
@@ -70,7 +70,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="readerOptions"></param>
public static GZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new GZipArchive(
new SourceStream(
@@ -88,7 +88,13 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
/// <param name="readerOptions"></param>
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new GZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);

View File

@@ -58,7 +58,7 @@ internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArch
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return NonDisposingStream.Create(stream);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()

View File

@@ -25,7 +25,7 @@ public static class IArchiveEntryExtensions
using (entryStream)
{
using Stream s = new ListeningStream(streamListener, entryStream);
s.TransferTo(streamToWriteTo);
s.CopyTo(streamToWriteTo);
}
streamListener.FireEntryExtractionEnd(archiveEntry);
}

View File

@@ -45,12 +45,10 @@ public static class IArchiveExtensions
var seenDirectories = new HashSet<string>();
// Extract
var entries = archive.ExtractAllEntries();
while (entries.MoveToNextEntry())
foreach (var entry in archive.Entries)
{
cancellationToken.ThrowIfCancellationRequested();
var entry = entries.Entry;
if (entry.IsDirectory)
{
var dirPath = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
@@ -77,7 +75,7 @@ public static class IArchiveExtensions
// Write file
using var fs = File.OpenWrite(path);
entries.WriteEntryTo(fs);
entry.WriteTo(fs);
// Update progress
bytesRead += entry.Size;

View File

@@ -95,7 +95,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="options"></param>
public static RarArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
var fileInfo = new FileInfo(filePath);
return new RarArchive(
new SourceStream(
@@ -113,7 +113,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="options"></param>
public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
fileInfo.NotNull(nameof(fileInfo));
return new RarArchive(
new SourceStream(
fileInfo,
@@ -130,7 +130,13 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="options"></param>
public static RarArchive Open(Stream stream, ReaderOptions? options = null)
{
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions()));
}
@@ -144,7 +150,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new RarArchive(
new SourceStream(
@@ -162,7 +168,7 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
/// <param name="readerOptions"></param>
public static RarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new RarArchive(
new SourceStream(

View File

@@ -21,7 +21,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty("filePath");
filePath.NotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
@@ -32,7 +32,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull("fileInfo");
fileInfo.NotNull("fileInfo");
return new SevenZipArchive(
new SourceStream(
fileInfo,
@@ -52,7 +52,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new SevenZipArchive(
new SourceStream(
@@ -73,7 +73,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
ReaderOptions? readerOptions = null
)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new SevenZipArchive(
new SourceStream(
@@ -91,7 +91,13 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull("stream");
stream.NotNull("stream");
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new SevenZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);
@@ -194,7 +200,10 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
new SevenZipReader(ReaderOptions, this);
public override bool IsSolid =>
Entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder).Count() > 1;
Entries
.Where(x => !x.IsDirectory)
.GroupBy(x => x.FilePart.Folder)
.Any(folder => folder.Count() > 1);
public override long TotalSize =>
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;

View File

@@ -22,7 +22,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="readerOptions"></param>
public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
@@ -33,7 +33,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="readerOptions"></param>
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
fileInfo.NotNull(nameof(fileInfo));
return new TarArchive(
new SourceStream(
fileInfo,
@@ -53,7 +53,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new TarArchive(
new SourceStream(
@@ -71,7 +71,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="readerOptions"></param>
public static TarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new TarArchive(
new SourceStream(
@@ -89,7 +89,13 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
/// <param name="readerOptions"></param>
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new TarArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
@@ -172,7 +178,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
entryStream.TransferTo(memoryStream);
entryStream.CopyTo(memoryStream);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();

View File

@@ -58,7 +58,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return NonDisposingStream.Create(stream);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()

View File

@@ -43,7 +43,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="readerOptions"></param>
public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
filePath.NotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
@@ -54,7 +54,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="readerOptions"></param>
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
fileInfo.NotNull(nameof(fileInfo));
return new ZipArchive(
new SourceStream(
fileInfo,
@@ -74,7 +74,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new ZipArchive(
new SourceStream(
@@ -92,7 +92,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="readerOptions"></param>
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new ZipArchive(
new SourceStream(
@@ -110,30 +110,52 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
/// <param name="readerOptions"></param>
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
stream.NotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new ZipArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
}
public static bool IsZipFile(string filePath, string? password = null) =>
IsZipFile(new FileInfo(filePath), password);
public static bool IsZipFile(
string filePath,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => IsZipFile(new FileInfo(filePath), password, bufferSize);
public static bool IsZipFile(FileInfo fileInfo, string? password = null)
public static bool IsZipFile(
FileInfo fileInfo,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
if (!fileInfo.Exists)
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsZipFile(stream, password);
return IsZipFile(stream, password, bufferSize);
}
public static bool IsZipFile(Stream stream, string? password = null)
public static bool IsZipFile(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
{
if (stream is not SharpCompressStream)
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
@@ -153,11 +175,20 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
}
}
public static bool IsZipMulti(Stream stream, string? password = null)
public static bool IsZipMulti(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
{
if (stream is not SharpCompressStream)
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
@@ -196,7 +227,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
if (streams.Count() > 1) //test part 2 - true = multipart not split
{
streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
var isZip = IsZipFile(streams[1], ReaderOptions.Password);
var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize);
streams[1].Position -= 4;
if (isZip)
{
@@ -299,7 +330,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
}

View File

@@ -18,6 +18,4 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry
public bool IsComplete => true;
#endregion
public string? Comment => ((SeekableZipFilePart)Parts.Single()).Comment;
}

View File

@@ -59,7 +59,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return NonDisposingStream.Create(stream);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()

View File

@@ -28,4 +28,5 @@ public enum CompressionType
Squashed,
Crushed,
Distilled,
ZStandard,
}

View File

@@ -1,11 +1,33 @@
using System;
using System.IO;
using System.IO.Compression;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Common;
public class EntryStream : Stream
public class EntryStream : 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 IReader _reader;
private readonly Stream _stream;
private bool _completed;
@@ -15,6 +37,9 @@ public class EntryStream : Stream
{
_reader = reader;
_stream = stream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(EntryStream));
#endif
}
/// <summary>
@@ -32,11 +57,28 @@ public class EntryStream : Stream
{
SkipEntry();
}
//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
{
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{
deflateStream.Flush(); //Deflate over reads. Knock it back
}
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
{
lzmaStream.Flush(); //Lzma over reads. Knock it back
}
}
if (_isDisposed)
{
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(EntryStream));
#endif
base.Dispose(disposing);
_stream.Dispose();
}
@@ -53,7 +95,7 @@ public class EntryStream : Stream
public override long Position
{
get => throw new NotSupportedException();
get => _stream.Position; //throw new NotSupportedException();
set => throw new NotSupportedException();
}

View File

@@ -4,13 +4,38 @@ using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
internal class TarReadOnlySubStream : NonDisposingStream
internal class TarReadOnlySubStream : SharpCompressStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => base.Stream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private bool _isDisposed;
private long _amountRead;
public TarReadOnlySubStream(Stream stream, long bytesToRead)
: base(stream, throwOnDispose: false) => BytesLeftToRead = bytesToRead;
: base(stream, leaveOpen: true, throwOnDispose: false)
{
BytesLeftToRead = bytesToRead;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(TarReadOnlySubStream));
#endif
}
protected override void Dispose(bool disposing)
{
@@ -20,7 +45,9 @@ internal class TarReadOnlySubStream : NonDisposingStream
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(TarReadOnlySubStream));
#endif
if (disposing)
{
// Ensure we read all remaining blocks for this entry.

View File

@@ -7,16 +7,22 @@ namespace SharpCompress.Common;
public abstract class Volume : IVolume
{
private readonly Stream _baseStream;
private readonly Stream _actualStream;
internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0)
{
Index = index;
ReaderOptions = readerOptions ?? new ReaderOptions();
_baseStream = stream;
if (ReaderOptions.LeaveStreamOpen)
{
stream = NonDisposingStream.Create(stream);
stream = SharpCompressStream.Create(stream, leaveOpen: true);
}
if (stream is IStreamStack ss)
ss.SetBuffer(ReaderOptions.BufferSize, true);
_actualStream = stream;
}
@@ -32,7 +38,7 @@ public abstract class Volume : IVolume
public virtual int Index { get; internal set; }
public string? FileName => (_actualStream as FileStream)?.Name;
public string? FileName => (_baseStream as FileStream)?.Name;
/// <summary>
/// RarArchive is part of a multi-part archive.

View File

@@ -123,11 +123,7 @@ internal class DirectoryEntryHeader : ZipFileEntry
public long RelativeOffsetOfEntryHeader { get; set; }
public uint ExternalFileAttributes { get; set; }
public ushort InternalFileAttributes { get; set; }
public ushort DiskNumberStart { get; set; }
public string? Comment { get; private set; }
}

View File

@@ -91,8 +91,15 @@ internal abstract class ZipFileEntry : ZipHeader
protected void LoadExtra(byte[] extra)
{
for (var i = 0; i < extra.Length - 4; )
for (var i = 0; i < extra.Length; )
{
// Ensure we have at least a header (2-byte ID + 2-byte length)
if (i + 4 > extra.Length)
{
// Incomplete header — stop parsing extras
break;
}
var type = (ExtraDataType)BinaryPrimitives.ReadUInt16LittleEndian(extra.AsSpan(i));
if (!Enum.IsDefined(typeof(ExtraDataType), type))
{
@@ -106,7 +113,17 @@ internal abstract class ZipFileEntry : ZipHeader
if (length > extra.Length)
{
// bad extras block
return;
break; // allow processing optional other blocks
}
// Some ZIP files contain vendor-specific or malformed extra fields where the declared
// data length extends beyond the remaining buffer. This adjustment ensures that
// we only read data within bounds (i + 4 + length <= extra.Length)
// The example here is: 41 43 18 00 41 52 43 30 46 EB FF FF 51 29 03 C6 03 00 00 00 00 00 00 00 00
// No existing zip utility uses 0x4341 ('AC')
if (i + 4 + length > extra.Length)
{
// incomplete or corrupt field
break; // allow processing optional other blocks
}
var data = new byte[length];
@@ -120,4 +137,8 @@ internal abstract class ZipFileEntry : ZipHeader
internal ZipFilePart? Part { get; set; }
internal bool IsZip64 => CompressedSize >= uint.MaxValue;
internal uint ExternalFileAttributes { get; set; }
internal string? Comment { get; set; }
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
@@ -9,8 +10,28 @@ internal enum CryptoMode
Decrypt,
}
internal class PkwareTraditionalCryptoStream : Stream
internal class PkwareTraditionalCryptoStream : 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 { return; }
}
int IStreamStack.BufferPosition
{
get => 0;
set { return; }
}
void IStreamStack.SetPosition(long position) { }
private readonly PkwareTraditionalEncryptionData _encryptor;
private readonly CryptoMode _mode;
private readonly Stream _stream;
@@ -25,6 +46,10 @@ internal class PkwareTraditionalCryptoStream : Stream
_encryptor = encryptor;
_stream = stream;
_mode = mode;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(PkwareTraditionalCryptoStream));
#endif
}
public override bool CanRead => (_mode == CryptoMode.Decrypt);
@@ -100,6 +125,9 @@ internal class PkwareTraditionalCryptoStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(PkwareTraditionalCryptoStream));
#endif
base.Dispose(disposing);
_stream.Dispose();
}

View File

@@ -25,14 +25,8 @@ internal class SeekableZipFilePart : ZipFilePart
return base.GetCompressedStream();
}
internal string? Comment => ((DirectoryEntryHeader)Header).Comment;
private void LoadLocalHeader()
{
var hasData = Header.HasData;
Header = _headerFactory.GetLocalHeader(BaseStream, ((DirectoryEntryHeader)Header));
Header.HasData = hasData;
}
private void LoadLocalHeader() =>
Header = _headerFactory.GetLocalHeader(BaseStream, (DirectoryEntryHeader)Header);
protected override Stream CreateBaseStream()
{

View File

@@ -149,6 +149,12 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
{
throw new InvalidOperationException();
}
// populate fields only known from the DirectoryEntryHeader
localEntryHeader.HasData = directoryEntryHeader.HasData;
localEntryHeader.ExternalFileAttributes = directoryEntryHeader.ExternalFileAttributes;
localEntryHeader.Comment = directoryEntryHeader.Comment;
if (FlagUtility.HasFlag(localEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor))
{
localEntryHeader.Crc = directoryEntryHeader.Crc;

View File

@@ -26,12 +26,12 @@ internal sealed class StreamingZipFilePart : ZipFilePart
);
if (LeaveStreamOpen)
{
return NonDisposingStream.Create(_decompressionStream);
return SharpCompressStream.Create(_decompressionStream, leaveOpen: true);
}
return _decompressionStream;
}
internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStream)
internal BinaryReader FixStreamedFileLocation(ref SharpCompressStream rewindableStream)
{
if (Header.IsDirectory)
{
@@ -49,7 +49,7 @@ internal sealed class StreamingZipFilePart : ZipFilePart
if (_decompressionStream is DeflateStream deflateStream)
{
rewindableStream.Rewind(deflateStream.InputBuffer);
((IStreamStack)rewindableStream).StackSeek(0);
}
Skipped = true;

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -19,16 +20,24 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
internal IEnumerable<ZipHeader> ReadStreamHeader(Stream stream)
{
RewindableStream rewindableStream;
if (stream is not SharpCompressStream) //ensure the stream is already a SharpCompressStream. So the buffer/size will already be set
{
//the original code wrapped this with RewindableStream. Wrap with SharpCompressStream as we can get the buffer size
if (stream is SourceStream src)
{
stream = new SharpCompressStream(
stream,
src.ReaderOptions.LeaveStreamOpen,
bufferSize: src.ReaderOptions.BufferSize
);
}
else
{
throw new ArgumentException("Stream must be a SharpCompressStream", nameof(stream));
}
}
SharpCompressStream rewindableStream = (SharpCompressStream)stream;
if (stream is RewindableStream rs)
{
rewindableStream = rs;
}
else
{
rewindableStream = new RewindableStream(stream);
}
while (true)
{
ZipHeader? header;
@@ -43,9 +52,8 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
{
continue;
}
reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(
ref rewindableStream
);
// removed requirement for FixStreamedFileLocation()
var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null;
@@ -56,26 +64,32 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
}
_lastEntryHeader.Crc = crc;
// The DataDescriptor can be either 64bit or 32bit
var compressed_size = reader.ReadUInt32();
var uncompressed_size = reader.ReadUInt32();
// Check if we have header or 64bit DataDescriptor
//attempt 32bit read
ulong compSize = reader.ReadUInt32();
ulong uncompSize = reader.ReadUInt32();
headerBytes = reader.ReadUInt32();
var test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50);
var test_64bit = ((long)uncompressed_size << 32) | compressed_size;
if (test_64bit == _lastEntryHeader.CompressedSize && test_header)
//check for zip64 sentinel or unexpected header
bool isSentinel = compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF;
bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50;
if (!isHeader && !isSentinel)
{
_lastEntryHeader.UncompressedSize =
((long)reader.ReadUInt32() << 32) | headerBytes;
//reshuffle into 64-bit values
compSize = (uncompSize << 32) | compSize;
uncompSize = ((ulong)headerBytes << 32) | reader.ReadUInt32();
headerBytes = reader.ReadUInt32();
}
else
else if (isSentinel)
{
_lastEntryHeader.UncompressedSize = uncompressed_size;
//standards-compliant zip64 descriptor
compSize = reader.ReadUInt64();
uncompSize = reader.ReadUInt64();
}
_lastEntryHeader.CompressedSize = (long)compSize;
_lastEntryHeader.UncompressedSize = (long)uncompSize;
if (pos.HasValue)
{
_lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize;
@@ -86,9 +100,9 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
if (_lastEntryHeader.Part is null)
continue;
reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(
ref rewindableStream
);
//reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(
// ref rewindableStream
//);
var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null;
@@ -173,16 +187,11 @@ internal class StreamingZipHeaderFactory : ZipHeaderFactory
} // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor )
else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor))
{
var isRecording = rewindableStream.IsRecording;
if (!isRecording)
{
rewindableStream.StartRecording();
}
var nextHeaderBytes = reader.ReadUInt32();
((IStreamStack)rewindableStream).Rewind(sizeof(uint));
// Check if next data is PostDataDescriptor, streamed file with 0 length
header.HasData = !IsHeader(nextHeaderBytes);
rewindableStream.Rewind(!isRecording);
}
else // We are not streaming and compressed size is 0, we have no data
{

View File

@@ -2,11 +2,32 @@ using System;
using System.Buffers.Binary;
using System.IO;
using System.Security.Cryptography;
using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
internal class WinzipAesCryptoStream : Stream
internal class WinzipAesCryptoStream : 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 const int BLOCK_SIZE_IN_BYTES = 16;
private readonly SymmetricAlgorithm _cipher;
private readonly byte[] _counter = new byte[BLOCK_SIZE_IN_BYTES];
@@ -27,6 +48,10 @@ internal class WinzipAesCryptoStream : Stream
_stream = stream;
_totalBytesLeftToRead = length;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(WinzipAesCryptoStream));
#endif
_cipher = CreateCipher(winzipAesEncryptionData);
var iv = new byte[BLOCK_SIZE_IN_BYTES];
@@ -64,6 +89,9 @@ internal class WinzipAesCryptoStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(WinzipAesCryptoStream));
#endif
if (disposing)
{
//read out last 10 auth bytes

View File

@@ -13,7 +13,7 @@ internal enum ZipCompressionMethod
Deflate64 = 9,
BZip2 = 12,
LZMA = 14,
ZStd = 93,
ZStandard = 93,
Xz = 95,
PPMd = 98,
WinzipAes = 0x63, //http://www.winzip.com/aes_info.htm

View File

@@ -46,6 +46,7 @@ public class ZipEntry : Entry
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
ZipCompressionMethod.Explode => CompressionType.Explode,
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
_ => CompressionType.Unknown,
};
@@ -83,4 +84,8 @@ public class ZipEntry : Entry
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
public override int? Attrib => (int?)_filePart?.Header.ExternalFileAttributes;
public string? Comment => _filePart?.Header.Comment;
}

View File

@@ -45,7 +45,7 @@ internal abstract class ZipFilePart : FilePart
);
if (LeaveStreamOpen)
{
return NonDisposingStream.Create(decompressionStream);
return SharpCompressStream.Create(decompressionStream, leaveOpen: true);
}
return decompressionStream;
}
@@ -70,17 +70,12 @@ internal abstract class ZipFilePart : FilePart
{
case ZipCompressionMethod.None:
{
if (stream is ReadOnlySubStream)
if (Header.CompressedSize is 0)
{
return stream;
return new DataDescriptorStream(stream);
}
if (Header.CompressedSize > 0)
{
return new ReadOnlySubStream(stream, Header.CompressedSize);
}
return new DataDescriptorStream(stream);
return stream;
}
case ZipCompressionMethod.Shrink:
{
@@ -152,7 +147,7 @@ internal abstract class ZipFilePart : FilePart
{
return new XZStream(stream);
}
case ZipCompressionMethod.ZStd:
case ZipCompressionMethod.ZStandard:
{
return new DecompressionStream(stream);
}
@@ -218,7 +213,7 @@ internal abstract class ZipFilePart : FilePart
) || Header.IsZip64
)
{
plainStream = NonDisposingStream.Create(plainStream); //make sure AES doesn't close
plainStream = SharpCompressStream.Create(plainStream, leaveOpen: true); //make sure AES doesn't close
}
else
{

View File

@@ -1,4 +1,4 @@
//
//
// ADC.cs
//
// Author:
@@ -28,14 +28,35 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.ADC;
/// <summary>
/// Provides a forward readable only stream that decompresses ADC data
/// </summary>
public sealed class ADCStream : Stream
public sealed class ADCStream : 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) { }
/// <summary>
/// This stream holds the compressed data
/// </summary>
@@ -74,6 +95,9 @@ public sealed class ADCStream : Stream
}
_stream = stream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ADCStream));
#endif
}
public override bool CanRead => _stream.CanRead;
@@ -99,6 +123,9 @@ public sealed class ADCStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(ADCStream));
#endif
base.Dispose(disposing);
}

View File

@@ -4,9 +4,30 @@ using System.IO;
using System.Linq;
using SharpCompress.Compressors.RLE90;
using SharpCompress.Compressors.Squeezed;
using SharpCompress.IO;
public partial class ArcLzwStream : Stream
public partial class ArcLzwStream : 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 Stream _stream;
private bool _processed;
private bool _useCrunched;
@@ -33,6 +54,9 @@ public partial class ArcLzwStream : Stream
public ArcLzwStream(Stream stream, int compressedSize, bool useCrunched = true)
{
_stream = stream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ArcLzwStream));
#endif
_useCrunched = useCrunched;
_compressedSize = compressedSize;
@@ -196,4 +220,12 @@ public partial class ArcLzwStream : Stream
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotImplementedException();
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ArcLzwStream));
#endif
base.Dispose(disposing);
}
}

View File

@@ -1,10 +1,31 @@
using System;
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.BZip2;
public sealed class BZip2Stream : Stream
public sealed class BZip2Stream : 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 Stream stream;
private bool isDisposed;
@@ -16,6 +37,9 @@ public sealed class BZip2Stream : Stream
/// <param name="decompressConcatenated">Decompress Concatenated</param>
public BZip2Stream(Stream stream, CompressionMode compressionMode, bool decompressConcatenated)
{
#if DEBUG_STREAMS
this.DebugConstruct(typeof(BZip2Stream));
#endif
Mode = compressionMode;
if (Mode == CompressionMode.Compress)
{
@@ -36,6 +60,9 @@ public sealed class BZip2Stream : Stream
return;
}
isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(BZip2Stream));
#endif
if (disposing)
{
stream.Dispose();

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using SharpCompress.IO;
/*
* Copyright 2001,2004-2005 The Apache Software Foundation
@@ -37,8 +38,28 @@ namespace SharpCompress.Compressors.BZip2;
* start of the BZIP2 stream to make it compatible with other PGP programs.
*/
internal class CBZip2InputStream : Stream
internal class CBZip2InputStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => bsStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private static void Cadvise()
{
//System.out.Println("CRC Error");
@@ -164,6 +185,10 @@ internal class CBZip2InputStream : Stream
ll8 = null;
tt = null;
BsSetStream(zStream);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(CBZip2InputStream));
#endif
Initialize(true);
InitBlock();
SetupBlock();
@@ -176,6 +201,9 @@ internal class CBZip2InputStream : Stream
return;
}
isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(CBZip2InputStream));
#endif
base.Dispose(disposing);
bsStream?.Dispose();
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using SharpCompress.IO;
/*
* Copyright 2001,2004-2005 The Apache Software Foundation
@@ -38,8 +39,28 @@ namespace SharpCompress.Compressors.BZip2;
* start of the BZIP2 stream to make it compatible with other PGP programs.
*/
internal sealed class CBZip2OutputStream : Stream
internal sealed class CBZip2OutputStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => bsStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private const int SETMASK = (1 << 21);
private const int CLEARMASK = (~SETMASK);
private const int GREATER_ICOST = 15;
@@ -334,6 +355,10 @@ internal sealed class CBZip2OutputStream : Stream
BsSetStream(inStream);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(CBZip2OutputStream));
#endif
workFactor = 50;
if (inBlockSize > 9)
{
@@ -450,6 +475,9 @@ internal sealed class CBZip2OutputStream : Stream
Finish();
disposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(CBZip2OutputStream));
#endif
Dispose();
bsStream?.Dispose();
bsStream = null;

View File

@@ -27,11 +27,33 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Deflate;
public class DeflateStream : Stream
public class DeflateStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _baseStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly ZlibBaseStream _baseStream;
private bool _disposed;
@@ -40,7 +62,8 @@ public class DeflateStream : Stream
CompressionMode mode,
CompressionLevel level = CompressionLevel.Default,
Encoding? forceEncoding = null
) =>
)
{
_baseStream = new ZlibBaseStream(
stream,
mode,
@@ -49,6 +72,11 @@ public class DeflateStream : Stream
forceEncoding
);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(DeflateStream));
#endif
}
#region Zlib properties
/// <summary>
@@ -233,6 +261,9 @@ public class DeflateStream : Stream
{
if (!_disposed)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(DeflateStream));
#endif
if (disposing)
{
_baseStream?.Dispose();
@@ -290,6 +321,7 @@ public class DeflateStream : Stream
{
throw new ObjectDisposedException("DeflateStream");
}
return _baseStream.Read(buffer, offset, count);
}

View File

@@ -30,11 +30,32 @@ using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Deflate;
public class GZipStream : Stream
public class GZipStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => BaseStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
internal static readonly DateTime UNIX_EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private string? _comment;
@@ -62,6 +83,9 @@ public class GZipStream : Stream
)
{
BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(GZipStream));
#endif
_encoding = encoding;
}
@@ -210,6 +234,9 @@ public class GZipStream : Stream
Crc32 = BaseStream.Crc32;
}
_disposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(GZipStream));
#endif
}
}
finally

View File

@@ -32,6 +32,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Deflate;
@@ -42,8 +43,28 @@ internal enum ZlibStreamFlavor
GZIP = 1952,
}
internal class ZlibBaseStream : Stream
internal class ZlibBaseStream : 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) { }
protected internal ZlibCodec _z; // deferred init... new ZlibCodec();
protected internal StreamMode _streamMode = StreamMode.Undefined;
@@ -87,6 +108,10 @@ internal class ZlibBaseStream : Stream
_encoding = encoding;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ZlibBaseStream));
#endif
// workitem 7159
if (flavor == ZlibStreamFlavor.GZIP)
{
@@ -334,6 +359,9 @@ internal class ZlibBaseStream : Stream
return;
}
isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(ZlibBaseStream));
#endif
base.Dispose(disposing);
if (disposing)
{
@@ -354,7 +382,13 @@ internal class ZlibBaseStream : Stream
}
}
public override void Flush() => _stream.Flush();
public override void Flush()
{
_stream.Flush();
//rewind the buffer
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
z.AvailableBytesIn = 0;
}
public override Int64 Seek(Int64 offset, SeekOrigin origin) =>
throw new NotSupportedException();
@@ -634,6 +668,13 @@ internal class ZlibBaseStream : Stream
crc.SlurpBlock(buffer, offset, rc);
}
if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress)
{
//rewind the buffer
((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused
z.AvailableBytesIn = 0;
}
return rc;
}

View File

@@ -28,11 +28,32 @@
using System;
using System.IO;
using System.Text;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Deflate;
public class ZlibStream : Stream
public class ZlibStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _baseStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly ZlibBaseStream _baseStream;
private bool _disposed;
@@ -47,7 +68,13 @@ public class ZlibStream : Stream
CompressionMode mode,
CompressionLevel level,
Encoding encoding
) => _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding);
)
{
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ZlibStream));
#endif
}
#region Zlib properties
@@ -216,6 +243,9 @@ public class ZlibStream : Stream
_baseStream?.Dispose();
}
_disposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(ZlibStream));
#endif
}
}
finally

View File

@@ -10,11 +10,32 @@ using System.IO;
using System.Runtime.CompilerServices;
using SharpCompress.Common;
using SharpCompress.Common.Zip;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Deflate64;
public sealed class Deflate64Stream : Stream
public sealed class Deflate64Stream : 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 const int DEFAULT_BUFFER_SIZE = 8192;
private Stream _stream;
@@ -42,6 +63,9 @@ public sealed class Deflate64Stream : Stream
}
InitializeInflater(stream, ZipCompressionMethod.Deflate64);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(Deflate64Stream));
#endif
}
/// <summary>
@@ -252,6 +276,9 @@ public sealed class Deflate64Stream : Stream
// In this case, we still need to clean up internal resources, hence the inner finally blocks.
try
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(Deflate64Stream));
#endif
if (disposing)
{
_stream?.Dispose();

View File

@@ -1,11 +1,32 @@
using System;
using System.IO;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Explode;
public class ExplodeStream : Stream
public class ExplodeStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => inStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private const int INVALID_CODE = 99;
private const int WSIZE = 64 * 1024;
@@ -45,6 +66,9 @@ public class ExplodeStream : Stream
)
{
inStream = inStr;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ExplodeStream));
#endif
this.compressedSize = (int)compressedSize;
unCompressedSize = (long)uncompressedSize;
this.generalPurposeBitFlag = generalPurposeBitFlag;
@@ -54,6 +78,14 @@ public class ExplodeStream : Stream
explode_var_init();
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ExplodeStream));
#endif
base.Dispose(disposing);
}
public override void Flush()
{
throw new NotImplementedException();

View File

@@ -3,11 +3,32 @@ using System.IO;
using System.Security.Cryptography;
using System.Text;
using SharpCompress.Compressors.LZMA.Utilites;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA;
internal sealed class AesDecoderStream : DecoderStream2
internal sealed class AesDecoderStream : DecoderStream2, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => mStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly Stream mStream;
private readonly ICryptoTransform mDecoder;
private readonly byte[] mBuffer;
@@ -31,6 +52,10 @@ internal sealed class AesDecoderStream : DecoderStream2
mStream = input;
mLimit = limit;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(AesDecoderStream));
#endif
if (((uint)input.Length & 15) != 0)
{
throw new NotSupportedException("AES decoder does not support padding.");
@@ -64,6 +89,9 @@ internal sealed class AesDecoderStream : DecoderStream2
return;
}
isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(AesDecoderStream));
#endif
if (disposing)
{
mStream.Dispose();

View File

@@ -1,11 +1,32 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA;
internal class Bcj2DecoderStream : DecoderStream2
internal class Bcj2DecoderStream : DecoderStream2, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _mMainStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private const int K_NUM_TOP_BITS = 24;
private const uint K_TOP_VALUE = (1 << K_NUM_TOP_BITS);
@@ -109,6 +130,10 @@ internal class Bcj2DecoderStream : DecoderStream2
_mStatusDecoder[i] = new StatusDecoder();
}
#if DEBUG_STREAMS
this.DebugConstruct(typeof(Bcj2DecoderStream));
#endif
_mIter = Run().GetEnumerator();
}
@@ -119,6 +144,9 @@ internal class Bcj2DecoderStream : DecoderStream2
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(Bcj2DecoderStream));
#endif
base.Dispose(disposing);
_mMainStream.Dispose();
_mCallStream.Dispose();

View File

@@ -1,11 +1,12 @@
#nullable disable
using System;
using System.Buffers;
using System.IO;
namespace SharpCompress.Compressors.LZMA.LZ;
internal class OutWindow
internal class OutWindow : IDisposable
{
private byte[] _buffer;
private int _windowSize;
@@ -15,19 +16,22 @@ internal class OutWindow
private int _pendingDist;
private Stream _stream;
public long _total;
public long _limit;
private long _total;
private long _limit;
public long Total => _total;
public void Create(int windowSize)
{
if (_windowSize != windowSize)
{
_buffer = new byte[windowSize];
}
else
{
_buffer[windowSize - 1] = 0;
if (_buffer is not null)
{
ArrayPool<byte>.Shared.Return(_buffer);
}
_buffer = ArrayPool<byte>.Shared.Rent(windowSize);
}
_buffer[windowSize - 1] = 0;
_windowSize = windowSize;
_pos = 0;
_streamPos = 0;
@@ -36,7 +40,22 @@ internal class OutWindow
_limit = 0;
}
public void Reset() => Create(_windowSize);
public void Dispose()
{
ReleaseStream();
if (_buffer is null)
{
return;
}
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null;
}
public void Reset()
{
ReleaseStream();
Create(_windowSize);
}
public void Init(Stream stream)
{
@@ -66,7 +85,7 @@ internal class OutWindow
_stream = null;
}
public void Flush()
private void Flush()
{
if (_stream is null)
{

View File

@@ -15,10 +15,30 @@ namespace SharpCompress.Compressors.LZMA;
/// <summary>
/// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html
/// </summary>
public sealed class LZipStream : Stream
public sealed class LZipStream : 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 Stream _stream;
private readonly CountingWritableSubStream? _countingWritableSubStream;
private readonly SharpCompressStream? _countingWritableSubStream;
private bool _disposed;
private bool _finished;
@@ -44,7 +64,7 @@ public sealed class LZipStream : Stream
var dSize = 104 * 1024;
WriteHeaderSize(stream);
_countingWritableSubStream = new CountingWritableSubStream(stream);
_countingWritableSubStream = new SharpCompressStream(stream, leaveOpen: true);
_stream = new Crc32Stream(
new LzmaStream(
new LzmaEncoderProperties(true, dSize),
@@ -52,6 +72,9 @@ public sealed class LZipStream : Stream
_countingWritableSubStream
)
);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(LZipStream));
#endif
}
}
@@ -64,7 +87,7 @@ public sealed class LZipStream : Stream
var crc32Stream = (Crc32Stream)_stream;
crc32Stream.WrappedStream.Dispose();
crc32Stream.Dispose();
var compressedCount = _countingWritableSubStream.NotNull().Count;
var compressedCount = _countingWritableSubStream.NotNull().InternalPosition;
Span<byte> intBuf = stackalloc byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc);
@@ -74,7 +97,10 @@ public sealed class LZipStream : Stream
_countingWritableSubStream?.Write(intBuf);
//total with headers
BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20);
BinaryPrimitives.WriteUInt64LittleEndian(
intBuf,
(ulong)compressedCount + (ulong)(6 + 20)
);
_countingWritableSubStream?.Write(intBuf);
}
_finished = true;
@@ -90,6 +116,9 @@ public sealed class LZipStream : Stream
return;
}
_disposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(LZipStream));
#endif
if (disposing)
{
Finish();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -28,52 +28,68 @@ internal static class Log
if (NEEDS_INDENT)
{
NEEDS_INDENT = false;
#if DEBUG_LZMA
Debug.Write(INDENT.Peek());
#endif
}
}
public static void Write(object value)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.Write(value);
#endif
}
public static void Write(string text)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.Write(text);
#endif
}
public static void Write(string format, params object[] args)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.Write(string.Format(format, args));
#endif
}
public static void WriteLine()
{
#if DEBUG_LZMA
Debug.WriteLine("");
#endif
NEEDS_INDENT = true;
}
public static void WriteLine(object value)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.WriteLine(value);
#endif
NEEDS_INDENT = true;
}
public static void WriteLine(string text)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.WriteLine(text);
#endif
NEEDS_INDENT = true;
}
public static void WriteLine(string format, params object[] args)
{
EnsureIndent();
#if DEBUG_LZMA
Debug.WriteLine(string.Format(format, args));
#endif
NEEDS_INDENT = true;
}
}

View File

@@ -294,7 +294,7 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
}
else
{
_outWindow.SetLimit(long.MaxValue - _outWindow._total);
_outWindow.SetLimit(long.MaxValue - _outWindow.Total);
}
var rangeDecoder = new RangeCoder.Decoder();
@@ -305,6 +305,7 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
_outWindow.ReleaseStream();
rangeDecoder.ReleaseStream();
_outWindow.Dispose();
_outWindow = null;
}
@@ -316,7 +317,7 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
while (outWindow.HasSpace)
{
var posState = (uint)outWindow._total & _posStateMask;
var posState = (uint)outWindow.Total & _posStateMask;
if (
_isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState]
.Decode(rangeDecoder) == 0
@@ -328,18 +329,14 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
{
b = _literalDecoder.DecodeWithMatchByte(
rangeDecoder,
(uint)outWindow._total,
(uint)outWindow.Total,
prevByte,
outWindow.GetByte((int)_rep0)
);
}
else
{
b = _literalDecoder.DecodeNormal(
rangeDecoder,
(uint)outWindow._total,
prevByte
);
b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow.Total, prevByte);
}
outWindow.PutByte(b);
_state.UpdateChar();
@@ -424,7 +421,7 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
_rep0 = posSlot;
}
}
if (_rep0 >= outWindow._total || _rep0 >= dictionarySizeCheck)
if (_rep0 >= outWindow.Total || _rep0 >= dictionarySizeCheck)
{
if (_rep0 == 0xFFFFFFFF)
{

View File

@@ -4,11 +4,32 @@ using System;
using System.Buffers.Binary;
using System.IO;
using SharpCompress.Compressors.LZMA.LZ;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA;
public class LzmaStream : Stream
public class LzmaStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _inputStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly Stream _inputStream;
private readonly long _inputSize;
private readonly long _outputSize;
@@ -56,6 +77,10 @@ public class LzmaStream : Stream
_outputSize = outputSize;
_isLzma2 = isLzma2;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(LzmaStream));
#endif
if (!isLzma2)
{
_dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
@@ -117,6 +142,11 @@ public class LzmaStream : Stream
Properties = prop;
_encoder.SetStreams(null, outputStream, -1, -1);
#if DEBUG_STREAMS
this.DebugConstruct(typeof(LzmaStream));
#endif
if (presetDictionary != null)
{
_encoder.Train(presetDictionary);
@@ -138,6 +168,9 @@ public class LzmaStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(LzmaStream));
#endif
if (disposing)
{
if (_encoder != null)
@@ -145,6 +178,7 @@ public class LzmaStream : Stream
_position = _encoder.Code(null, true);
}
_inputStream?.Dispose();
_outWindow.Dispose();
}
base.Dispose(disposing);
}

View File

@@ -1,10 +1,31 @@
using System;
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA.Utilites;
internal class CrcBuilderStream : Stream
internal class CrcBuilderStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _mTarget;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly Stream _mTarget;
private uint _mCrc;
private bool _mFinished;
@@ -13,6 +34,9 @@ internal class CrcBuilderStream : Stream
public CrcBuilderStream(Stream target)
{
_mTarget = target;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(CrcBuilderStream));
#endif
_mCrc = Crc.INIT_CRC;
}
@@ -23,6 +47,9 @@ internal class CrcBuilderStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(CrcBuilderStream));
#endif
_mTarget.Dispose();
base.Dispose(disposing);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Lzw
{
@@ -42,8 +43,28 @@ namespace SharpCompress.Compressors.Lzw
/// }
/// </code>
/// </example>
public class LzwStream : Stream
public class LzwStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => baseInputStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
public static bool IsLzwStream(Stream stream)
{
try
@@ -90,6 +111,9 @@ namespace SharpCompress.Compressors.Lzw
public LzwStream(Stream baseInputStream)
{
this.baseInputStream = baseInputStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(LzwStream));
#endif
}
/// <summary>
@@ -539,6 +563,9 @@ namespace SharpCompress.Compressors.Lzw
if (!isClosed)
{
isClosed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(LzwStream));
#endif
if (IsStreamOwner)
{
baseInputStream.Dispose();

View File

@@ -1,15 +1,36 @@
#nullable disable
#nullable disable
using System;
using System.IO;
using SharpCompress.Compressors.LZMA.RangeCoder;
using SharpCompress.Compressors.PPMd.H;
using SharpCompress.Compressors.PPMd.I1;
using SharpCompress.IO;
namespace SharpCompress.Compressors.PPMd;
public class PpmdStream : Stream
public class PpmdStream : 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 PpmdProperties _properties;
private readonly Stream _stream;
private readonly bool _compress;
@@ -25,6 +46,10 @@ public class PpmdStream : Stream
_stream = stream;
_compress = compress;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(PpmdStream));
#endif
if (properties.Version == PpmdVersion.I1)
{
_model = new Model();
@@ -74,6 +99,9 @@ public class PpmdStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(PpmdStream));
#endif
if (isDisposing)
{
if (_compress)

View File

@@ -4,11 +4,32 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.RLE90
{
public class RunLength90Stream : Stream
public class RunLength90Stream : 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 Stream _stream;
private const byte DLE = 0x90;
private int _compressedSize;
@@ -18,6 +39,17 @@ namespace SharpCompress.Compressors.RLE90
{
_stream = stream;
_compressedSize = compressedSize;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(RunLength90Stream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(RunLength90Stream));
#endif
base.Dispose(disposing);
}
public override bool CanRead => true;

View File

@@ -5,11 +5,32 @@ using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Rar;
internal sealed class MultiVolumeReadOnlyStream : Stream
internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => currentStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private long currentPosition;
private long maxPosition;
@@ -31,6 +52,9 @@ internal sealed class MultiVolumeReadOnlyStream : Stream
filePartEnumerator = parts.GetEnumerator();
filePartEnumerator.MoveNext();
InitializeNextFilePart();
#if DEBUG_STREAMS
this.DebugConstruct(typeof(MultiVolumeReadOnlyStream));
#endif
}
protected override void Dispose(bool disposing)
@@ -38,6 +62,10 @@ internal sealed class MultiVolumeReadOnlyStream : Stream
base.Dispose(disposing);
if (disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(MultiVolumeReadOnlyStream));
#endif
if (filePartEnumerator != null)
{
filePartEnumerator.Dispose();

View File

@@ -3,11 +3,31 @@ using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Rar;
internal class RarBLAKE2spStream : RarStream
internal class RarBLAKE2spStream : RarStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => readStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly MultiVolumeReadOnlyStream readStream;
private readonly bool disableCRCCheck;
@@ -91,12 +111,24 @@ internal class RarBLAKE2spStream : RarStream
: base(unpack, fileHeader, readStream)
{
this.readStream = readStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(RarBLAKE2spStream));
#endif
disableCRCCheck = fileHeader.IsEncrypted;
_hash = fileHeader.FileCrc.NotNull();
_blake2sp = new BLAKE2SP();
ResetCrc();
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(RarBLAKE2spStream));
#endif
base.Dispose(disposing);
}
public byte[] GetCrc() => _hash;
internal void ResetCrc(BLAKE2S hash)

View File

@@ -1,11 +1,32 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Rar;
internal class RarCrcStream : RarStream
internal class RarCrcStream : RarStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => readStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly MultiVolumeReadOnlyStream readStream;
private uint currentCrc;
private readonly bool disableCRC;
@@ -18,10 +39,21 @@ internal class RarCrcStream : RarStream
: base(unpack, fileHeader, readStream)
{
this.readStream = readStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(RarCrcStream));
#endif
disableCRC = fileHeader.IsEncrypted;
ResetCrc();
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(RarCrcStream));
#endif
base.Dispose(disposing);
}
public uint GetCrc() => ~currentCrc;
public void ResetCrc() => currentCrc = 0xffffffff;

View File

@@ -4,11 +4,32 @@ using System;
using System.Buffers;
using System.IO;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Rar;
internal class RarStream : Stream
internal class RarStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => readStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly IRarUnpack unpack;
private readonly FileHeader fileHeader;
private readonly Stream readStream;
@@ -31,6 +52,11 @@ internal class RarStream : Stream
this.unpack = unpack;
this.fileHeader = fileHeader;
this.readStream = readStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(RarStream));
#endif
fetch = true;
unpack.DoUnpack(fileHeader, readStream, this);
fetch = false;
@@ -43,6 +69,9 @@ internal class RarStream : Stream
{
if (disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(RarStream));
#endif
ArrayPool<byte>.Shared.Return(this.tmpBuffer);
this.tmpBuffer = null;
}

View File

@@ -13,7 +13,7 @@ using SharpCompress.Compressors.Rar.VM;
namespace SharpCompress.Compressors.Rar.UnpackV1;
internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
internal sealed partial class Unpack : BitInput, IRarUnpack
{
private readonly BitInput Inp;
private bool disposed;
@@ -22,15 +22,17 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
// to ease in porting Unpack50.cs
Inp = this;
public void Dispose()
public override void Dispose()
{
if (!disposed)
{
base.Dispose();
if (!externalWindow)
{
ArrayPool<byte>.Shared.Return(window);
window = null;
}
rarVM.Dispose();
disposed = true;
}
}
@@ -574,104 +576,111 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
var FilteredDataOffset = Prg.FilteredDataOffset;
var FilteredDataSize = Prg.FilteredDataSize;
var FilteredData = new byte[FilteredDataSize];
for (var i = 0; i < FilteredDataSize; i++)
var FilteredData = ArrayPool<byte>.Shared.Rent(FilteredDataSize);
try
{
FilteredData[i] = rarVM.Mem[FilteredDataOffset + i];
Array.Copy(
rarVM.Mem,
FilteredDataOffset,
FilteredData,
0,
FilteredDataSize
);
// Prg.GlobalData.get(FilteredDataOffset
// +
// i);
}
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;
}
// apply several filters to same data block
rarVM.setMemory(0, FilteredData, 0, FilteredDataSize);
// .SetMemory(0,FilteredData,FilteredDataSize);
var pPrg = filters[NextFilter.ParentFilter].Program;
var NextPrg = NextFilter.Program;
if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE)
{
// copy global data from previous script execution
// if any
// NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count);
// memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
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)
{
// save global data for next script execution
if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count)
{
pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count);
}
// memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
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;
FilteredData = new byte[FilteredDataSize];
for (var i = 0; i < FilteredDataSize; i++)
{
FilteredData[i] = NextPrg.GlobalData[FilteredDataOffset + i];
}
I++;
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;
}
// apply several filters to same data block
rarVM.setMemory(0, FilteredData, 0, FilteredDataSize);
// .SetMemory(0,FilteredData,FilteredDataSize);
var pPrg = filters[NextFilter.ParentFilter].Program;
var NextPrg = NextFilter.Program;
if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE)
{
// copy global data from previous script execution
// if any
// NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count);
// memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
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)
{
// save global data for next script execution
if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count)
{
pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count);
}
// memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
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;
}
writeStream.Write(FilteredData, 0, FilteredDataSize);
writtenFileSize += FilteredDataSize;
destUnpSize -= FilteredDataSize;
WrittenBorder = BlockEnd;
WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK;
}
finally
{
ArrayPool<byte>.Shared.Return(FilteredData);
}
writeStream.Write(FilteredData, 0, FilteredDataSize);
unpSomeRead = true;
writtenFileSize += FilteredDataSize;
destUnpSize -= FilteredDataSize;
WrittenBorder = BlockEnd;
WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK;
}
else
{
@@ -695,15 +704,10 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
private void UnpWriteArea(int startPtr, int endPtr)
{
if (endPtr != startPtr)
{
unpSomeRead = true;
}
if (endPtr < startPtr)
{
UnpWriteData(window, startPtr, -startPtr & PackDef.MAXWINMASK);
UnpWriteData(window, 0, endPtr);
unpAllBuf = true;
}
else
{
@@ -757,19 +761,27 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
// System.out.println("copyString(" + length + ", " + distance + ")");
var destPtr = unpPtr - distance;
var safeZone = PackDef.MAXWINSIZE - 260;
// System.out.println(unpPtr+":"+distance);
if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260)
// Fast path: use Array.Copy for bulk operations when in safe zone
if (destPtr >= 0 && destPtr < safeZone && unpPtr < safeZone && distance >= length)
{
window[unpPtr++] = window[destPtr++];
while (--length > 0)
// Non-overlapping copy: can use Array.Copy directly
Array.Copy(window, destPtr, window, unpPtr, length);
unpPtr += length;
}
else if (destPtr >= 0 && destPtr < safeZone && unpPtr < safeZone)
{
// Overlapping copy in safe zone: use byte-by-byte to handle self-referential copies
for (int i = 0; i < length; i++)
{
window[unpPtr++] = window[destPtr++];
window[unpPtr + i] = window[destPtr + i];
}
unpPtr += length;
}
else
{
// Slow path with wraparound mask
while (length-- != 0)
{
window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK];
@@ -1028,7 +1040,7 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
vmCode.Add((byte)(GetBits() >> 8));
AddBits(8);
}
return (AddVMCode(FirstByte, vmCode, Length));
return AddVMCode(FirstByte, vmCode);
}
private bool ReadVMCodePPM()
@@ -1073,12 +1085,12 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
}
vmCode.Add((byte)Ch); // VMCode[I]=Ch;
}
return (AddVMCode(FirstByte, vmCode, Length));
return AddVMCode(FirstByte, vmCode);
}
private bool AddVMCode(int firstByte, List<byte> vmCode, int length)
private bool AddVMCode(int firstByte, List<byte> vmCode)
{
var Inp = new BitInput();
using var Inp = new BitInput();
Inp.InitBitInput();
// memcpy(Inp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize));
@@ -1086,7 +1098,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
{
Inp.InBuf[i] = vmCode[i];
}
rarVM.init();
int FiltPos;
if ((firstByte & 0x80) != 0)
@@ -1199,19 +1210,28 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable
{
return (false);
}
Span<byte> VMCode = stackalloc byte[VMCodeSize];
for (var I = 0; I < VMCodeSize; I++)
{
if (Inp.Overflow(3))
{
return (false);
}
VMCode[I] = (byte)(Inp.GetBits() >> 8);
Inp.AddBits(8);
}
// VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
rarVM.prepare(VMCode, VMCodeSize, Filter.Program);
var VMCode = ArrayPool<byte>.Shared.Rent(VMCodeSize);
try
{
for (var I = 0; I < VMCodeSize; I++)
{
if (Inp.Overflow(3))
{
return (false);
}
VMCode[I] = (byte)(Inp.GetBits() >> 8);
Inp.AddBits(8);
}
// VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
rarVM.prepare(VMCode.AsSpan(0, VMCodeSize), Filter.Program);
}
finally
{
ArrayPool<byte>.Shared.Return(VMCode);
}
}
StackFilter.Program.AltCommands = Filter.Program.Commands; // StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0];
StackFilter.Program.CommandCount = Filter.Program.CommandCount;

View File

@@ -19,14 +19,9 @@ internal partial class Unpack
private bool suspended;
internal bool unpAllBuf;
//private ComprDataIO unpIO;
private Stream readStream;
private Stream writeStream;
internal bool unpSomeRead;
private int readTop;
private long destUnpSize;
@@ -808,15 +803,10 @@ internal partial class Unpack
private void oldUnpWriteBuf()
{
if (unpPtr != wrPtr)
{
unpSomeRead = true;
}
if (unpPtr < wrPtr)
{
writeStream.Write(window, wrPtr, -wrPtr & PackDef.MAXWINMASK);
writeStream.Write(window, 0, unpPtr);
unpAllBuf = true;
}
else
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using SharpCompress.Compressors.Rar.VM;
namespace SharpCompress.Compressors.Rar.UnpackV1;
@@ -9,167 +10,15 @@ internal static class UnpackUtility
internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) =>
(uint)input.decodeNumber(dec);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int decodeNumber(this BitInput input, Decode.Decode dec)
{
int bits;
long bitField = input.GetBits() & 0xfffe;
// if (bitField < dec.getDecodeLen()[8]) {
// if (bitField < dec.getDecodeLen()[4]) {
// if (bitField < dec.getDecodeLen()[2]) {
// if (bitField < dec.getDecodeLen()[1]) {
// bits = 1;
// } else {
// bits = 2;
// }
// } else {
// if (bitField < dec.getDecodeLen()[3]) {
// bits = 3;
// } else {
// bits = 4;
// }
// }
// } else {
// if (bitField < dec.getDecodeLen()[6]) {
// if (bitField < dec.getDecodeLen()[5])
// bits = 5;
// else
// bits = 6;
// } else {
// if (bitField < dec.getDecodeLen()[7]) {
// bits = 7;
// } else {
// bits = 8;
// }
// }
// }
// } else {
// if (bitField < dec.getDecodeLen()[12]) {
// if (bitField < dec.getDecodeLen()[10])
// if (bitField < dec.getDecodeLen()[9])
// bits = 9;
// else
// bits = 10;
// else if (bitField < dec.getDecodeLen()[11])
// bits = 11;
// else
// bits = 12;
// } else {
// if (bitField < dec.getDecodeLen()[14]) {
// if (bitField < dec.getDecodeLen()[13]) {
// bits = 13;
// } else {
// bits = 14;
// }
// } else {
// bits = 15;
// }
// }
// }
// addbits(bits);
// int N = dec.getDecodePos()[bits]
// + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits));
// if (N >= dec.getMaxNum()) {
// N = 0;
// }
// return (dec.getDecodeNum()[N]);
var decodeLen = dec.DecodeLen;
if (bitField < decodeLen[8])
{
if (bitField < decodeLen[4])
{
if (bitField < decodeLen[2])
{
if (bitField < decodeLen[1])
{
bits = 1;
}
else
{
bits = 2;
}
}
else
{
if (bitField < decodeLen[3])
{
bits = 3;
}
else
{
bits = 4;
}
}
}
else
{
if (bitField < decodeLen[6])
{
if (bitField < decodeLen[5])
{
bits = 5;
}
else
{
bits = 6;
}
}
else
{
if (bitField < decodeLen[7])
{
bits = 7;
}
else
{
bits = 8;
}
}
}
}
else
{
if (bitField < decodeLen[12])
{
if (bitField < decodeLen[10])
{
if (bitField < decodeLen[9])
{
bits = 9;
}
else
{
bits = 10;
}
}
else if (bitField < decodeLen[11])
{
bits = 11;
}
else
{
bits = 12;
}
}
else
{
if (bitField < decodeLen[14])
{
if (bitField < decodeLen[13])
{
bits = 13;
}
else
{
bits = 14;
}
}
else
{
bits = 15;
}
}
}
// Binary search to find the bit length - faster than nested ifs
int bits = FindDecodeBits(bitField, decodeLen);
input.AddBits(bits);
var N =
dec.DecodePos[bits]
@@ -181,6 +30,52 @@ internal static class UnpackUtility
return (dec.DecodeNum[N]);
}
/// <summary>
/// Fast binary search to find which bit length matches the bitField.
/// Optimized with cached array access to minimize memory lookups.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindDecodeBits(long bitField, int[] decodeLen)
{
// Cache critical values to reduce array access overhead
long len4 = decodeLen[4];
long len8 = decodeLen[8];
long len12 = decodeLen[12];
if (bitField < len8)
{
if (bitField < len4)
{
long len2 = decodeLen[2];
if (bitField < len2)
{
return bitField < decodeLen[1] ? 1 : 2;
}
return bitField < decodeLen[3] ? 3 : 4;
}
long len6 = decodeLen[6];
if (bitField < len6)
{
return bitField < decodeLen[5] ? 5 : 6;
}
return bitField < decodeLen[7] ? 7 : 8;
}
if (bitField < len12)
{
long len10 = decodeLen[10];
if (bitField < len10)
{
return bitField < decodeLen[9] ? 9 : 10;
}
return bitField < decodeLen[11] ? 11 : 12;
}
long len14 = decodeLen[14];
return bitField < len14 ? (bitField < decodeLen[13] ? 13 : 14) : 15;
}
internal static void makeDecodeTables(
Span<byte> lenTab,
int offset,
@@ -194,8 +89,7 @@ internal static class UnpackUtility
long M,
N;
new Span<int>(dec.DecodeNum).Clear(); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum));
new Span<int>(dec.DecodeNum).Clear();
for (i = 0; i < size; i++)
{
lenCount[lenTab[offset + i] & 0xF]++;

View File

@@ -413,7 +413,7 @@ internal partial class Unpack
else
//x memcpy(Mem,Window+BlockStart,BlockLength);
{
Utility.Copy(Window, BlockStart, Mem, 0, BlockLength);
Buffer.BlockCopy(Window, (int)BlockStart, Mem, 0, (int)BlockLength);
}
}
else
@@ -427,9 +427,21 @@ internal partial class Unpack
else
{
//x memcpy(Mem,Window+BlockStart,FirstPartLength);
Utility.Copy(Window, BlockStart, Mem, 0, FirstPartLength);
Buffer.BlockCopy(
Window,
(int)BlockStart,
Mem,
0,
(int)FirstPartLength
);
//x memcpy(Mem+FirstPartLength,Window,BlockEnd);
Utility.Copy(Window, 0, Mem, FirstPartLength, BlockEnd);
Buffer.BlockCopy(
Window,
0,
Mem,
(int)FirstPartLength,
(int)BlockEnd
);
}
}

View File

@@ -1,6 +1,9 @@
using System;
using System.Buffers;
namespace SharpCompress.Compressors.Rar.VM;
internal class BitInput
internal class BitInput : IDisposable
{
/// <summary> the max size of the input</summary>
internal const int MAX_SIZE = 0x8000;
@@ -20,9 +23,11 @@ internal class BitInput
set => inBit = value;
}
public bool ExternalBuffer;
private byte[] _privateBuffer = ArrayPool<byte>.Shared.Rent(MAX_SIZE);
private bool _disposed;
/// <summary> </summary>
internal BitInput() => InBuf = new byte[MAX_SIZE];
internal BitInput() => InBuf = _privateBuffer;
internal byte[] InBuf { get; }
@@ -87,4 +92,14 @@ internal class BitInput
/// <returns> true if an Oververflow would occur
/// </returns>
internal bool Overflow(int IncPtr) => (inAddr + IncPtr >= MAX_SIZE);
public virtual void Dispose()
{
if (_disposed)
{
return;
}
ArrayPool<byte>.Shared.Return(_privateBuffer);
_disposed = true;
}
}

View File

@@ -1,6 +1,5 @@
#nullable disable
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
@@ -16,7 +15,9 @@ internal sealed class RarVM : BitInput
// Mem.set_Renamed(offset + 3, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 24)) & 0xff)));
//}
internal byte[] Mem { get; private set; }
internal byte[] Mem => _memory.NotNull();
private byte[]? _memory = ArrayPool<byte>.Shared.Rent(VM_MEMSIZE + 4);
public const int VM_MEMSIZE = 0x40000;
@@ -40,11 +41,18 @@ internal sealed class RarVM : BitInput
private int IP;
internal RarVM() =>
//InitBlock();
Mem = null;
internal RarVM() { }
internal void init() => Mem ??= new byte[VM_MEMSIZE + 4];
public override void Dispose()
{
base.Dispose();
if (_memory is null)
{
return;
}
ArrayPool<byte>.Shared.Return(_memory);
_memory = null;
}
private bool IsVMMem(byte[] mem) => Mem == mem;
@@ -776,9 +784,10 @@ internal sealed class RarVM : BitInput
}
}
public void prepare(ReadOnlySpan<byte> code, int codeSize, VMPreparedProgram prg)
public void prepare(ReadOnlySpan<byte> code, VMPreparedProgram prg)
{
InitBitInput();
var codeSize = code.Length;
var cpLength = Math.Min(MAX_SIZE, codeSize);
// memcpy(inBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
@@ -795,7 +804,7 @@ internal sealed class RarVM : BitInput
prg.CommandCount = 0;
if (xorSum == code[0])
{
var filterType = IsStandardFilter(code, codeSize);
var filterType = IsStandardFilter(code);
if (filterType != VMStandardFilters.VMSF_NONE)
{
var curCmd = new VMPreparedCommand();
@@ -1105,7 +1114,7 @@ internal sealed class RarVM : BitInput
}
}
private VMStandardFilters IsStandardFilter(ReadOnlySpan<byte> code, int codeSize)
private VMStandardFilters IsStandardFilter(ReadOnlySpan<byte> code)
{
VMStandardFilterSignature[] stdList =
{
@@ -1130,6 +1139,7 @@ internal sealed class RarVM : BitInput
private void ExecuteStandardFilter(VMStandardFilters filterType)
{
var mem = Mem;
switch (filterType)
{
case VMStandardFilters.VMSF_E8:
@@ -1148,7 +1158,7 @@ internal sealed class RarVM : BitInput
);
for (var curPos = 0; curPos < dataSize - 4; )
{
var curByte = Mem[curPos++];
var curByte = mem[curPos++];
if (curByte == 0xe8 || curByte == cmpByte2)
{
// #ifdef PRESENT_INT32
@@ -1164,19 +1174,19 @@ internal sealed class RarVM : BitInput
// SET_VALUE(false,Data,Addr-Offset);
// #else
var offset = curPos + fileOffset;
long Addr = GetValue(false, Mem, curPos);
long Addr = GetValue(false, mem, curPos);
if ((Addr & unchecked((int)0x80000000)) != 0)
{
if (((Addr + offset) & unchecked((int)0x80000000)) == 0)
{
SetValue(false, Mem, curPos, (int)Addr + fileSize);
SetValue(false, mem, curPos, (int)Addr + fileSize);
}
}
else
{
if (((Addr - fileSize) & unchecked((int)0x80000000)) != 0)
{
SetValue(false, Mem, curPos, (int)(Addr - offset));
SetValue(false, mem, curPos, (int)(Addr - offset));
}
}
@@ -1204,7 +1214,7 @@ internal sealed class RarVM : BitInput
while (curPos < dataSize - 21)
{
var Byte = (Mem[curPos] & 0x1f) - 0x10;
var Byte = (mem[curPos] & 0x1f) - 0x10;
if (Byte >= 0)
{
var cmdMask = Masks[Byte];
@@ -1250,7 +1260,7 @@ internal sealed class RarVM : BitInput
var channels = R[0] & unchecked((int)0xFFffFFff);
var srcPos = 0;
var border = (dataSize * 2) & unchecked((int)0xFFffFFff);
SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize);
SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
if (dataSize >= VM_GLOBALMEMADDR / 2)
{
break;
@@ -1268,7 +1278,7 @@ internal sealed class RarVM : BitInput
destPos += channels
)
{
Mem[destPos] = (PrevByte = (byte)(PrevByte - Mem[srcPos++]));
mem[destPos] = (PrevByte = (byte)(PrevByte - mem[srcPos++]));
}
}
}
@@ -1283,7 +1293,7 @@ internal sealed class RarVM : BitInput
var channels = 3;
var srcPos = 0;
var destDataPos = dataSize;
SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize);
SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
if (dataSize >= VM_GLOBALMEMADDR / 2 || posR < 0)
{
break;
@@ -1299,8 +1309,8 @@ internal sealed class RarVM : BitInput
if (upperPos >= 3)
{
var upperDataPos = destDataPos + upperPos;
var upperByte = Mem[upperDataPos] & 0xff;
var upperLeftByte = Mem[upperDataPos - 3] & 0xff;
var upperByte = mem[upperDataPos] & 0xff;
var upperLeftByte = mem[upperDataPos - 3] & 0xff;
predicted = prevByte + upperByte - upperLeftByte;
var pa = Math.Abs((int)(predicted - prevByte));
var pb = Math.Abs((int)(predicted - upperByte));
@@ -1326,15 +1336,15 @@ internal sealed class RarVM : BitInput
predicted = prevByte;
}
prevByte = ((predicted - Mem[srcPos++]) & 0xff) & 0xff;
Mem[destDataPos + i] = (byte)(prevByte & 0xff);
prevByte = ((predicted - mem[srcPos++]) & 0xff) & 0xff;
mem[destDataPos + i] = (byte)(prevByte & 0xff);
}
}
for (int i = posR, border = dataSize - 2; i < border; i += 3)
{
var G = Mem[destDataPos + i + 1];
Mem[destDataPos + i] = (byte)(Mem[destDataPos + i] + G);
Mem[destDataPos + i + 2] = (byte)(Mem[destDataPos + i + 2] + G);
var G = mem[destDataPos + i + 1];
mem[destDataPos + i] = (byte)(mem[destDataPos + i] + G);
mem[destDataPos + i + 2] = (byte)(mem[destDataPos + i + 2] + G);
}
}
break;
@@ -1347,7 +1357,7 @@ internal sealed class RarVM : BitInput
var destDataPos = dataSize;
//byte *SrcData=Mem,*DestData=SrcData+DataSize;
SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize);
SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
if (dataSize >= VM_GLOBALMEMADDR / 2)
{
break;
@@ -1377,10 +1387,10 @@ internal sealed class RarVM : BitInput
var predicted = (8 * prevByte) + (K1 * D1) + (K2 * D2) + (K3 * D3);
predicted = Utility.URShift(predicted, 3) & 0xff;
long curByte = Mem[srcPos++];
long curByte = mem[srcPos++];
predicted -= curByte;
Mem[destDataPos + i] = (byte)predicted;
mem[destDataPos + i] = (byte)predicted;
prevDelta = (byte)(predicted - prevByte);
//fix java byte
@@ -1480,15 +1490,15 @@ internal sealed class RarVM : BitInput
}
while (srcPos < dataSize)
{
var curByte = Mem[srcPos++];
if (curByte == 2 && (curByte = Mem[srcPos++]) != 2)
var curByte = mem[srcPos++];
if (curByte == 2 && (curByte = mem[srcPos++]) != 2)
{
curByte = (byte)(curByte - 32);
}
Mem[destPos++] = curByte;
mem[destPos++] = curByte;
}
SetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize);
SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize);
SetValue(false, mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize);
SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize);
}
break;
}
@@ -1528,15 +1538,14 @@ internal sealed class RarVM : BitInput
{
if (pos < VM_MEMSIZE)
{
//&& data!=Mem+Pos)
//memmove(Mem+Pos,Data,Min(DataSize,VM_MEMSIZE-Pos));
for (var i = 0; i < Math.Min(data.Length - offset, dataSize); i++)
// Use Array.Copy for fast bulk memory operations instead of byte-by-byte loop
// Calculate how much data can actually fit in VM memory
int copyLength = Math.Min(dataSize, VM_MEMSIZE - pos);
copyLength = Math.Min(copyLength, data.Length - offset);
if (copyLength > 0)
{
if ((VM_MEMSIZE - pos) < i)
{
break;
}
Mem[pos + i] = data[offset + i];
Array.Copy(data, offset, Mem, pos, copyLength);
}
}
}

View File

@@ -1,10 +1,31 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Reduce;
public class ReduceStream : Stream
public class ReduceStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => inStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private readonly long unCompressedSize;
private readonly long compressedSize;
private readonly Stream inStream;
@@ -31,6 +52,10 @@ public class ReduceStream : Stream
inByteCount = 0;
outBytesCount = 0;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ReduceStream));
#endif
this.factor = factor;
distanceMask = (int)mask_bits[factor] << 8;
lengthMask = 0xff >> factor;
@@ -47,6 +72,14 @@ public class ReduceStream : Stream
LoadNextByteTable();
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ReduceStream));
#endif
base.Dispose(disposing);
}
public override void Flush()
{
throw new NotImplementedException();

View File

@@ -1,10 +1,31 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Shrink;
internal class ShrinkStream : Stream
internal class ShrinkStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => inStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
private Stream inStream;
private CompressionMode _compressionMode;
@@ -23,12 +44,24 @@ internal class ShrinkStream : Stream
inStream = stream;
_compressionMode = compressionMode;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ShrinkStream));
#endif
_compressedSize = (ulong)compressedSize;
_uncompressedSize = uncompressedSize;
_byteOut = new byte[_uncompressedSize];
_outBytesCount = 0L;
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ShrinkStream));
#endif
base.Dispose(disposing);
}
public override bool CanRead => true;
public override bool CanSeek => true;

View File

@@ -5,12 +5,32 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Compressors.RLE90;
using ZstdSharp.Unsafe;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Squeezed
{
public class SqueezeStream : Stream
public class SqueezeStream : 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 Stream _stream;
private readonly int _compressedSize;
private const int NUMVALS = 257;
@@ -21,6 +41,17 @@ namespace SharpCompress.Compressors.Squeezed
{
_stream = stream;
_compressedSize = compressedSize;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(SqueezeStream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(SqueezeStream));
#endif
base.Dispose(disposing);
}
public override bool CanRead => true;

View File

@@ -22,9 +22,7 @@ public class XZFooter
public static XZFooter FromStream(Stream stream)
{
var footer = new XZFooter(
new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8)
);
var footer = new XZFooter(new BinaryReader(stream, Encoding.UTF8, true));
footer.Process();
return footer;
}

View File

@@ -18,9 +18,7 @@ public class XZHeader
public static XZHeader FromStream(Stream stream)
{
var header = new XZHeader(
new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8)
);
var header = new XZHeader(new BinaryReader(stream, Encoding.UTF8, true));
header.Process();
return header;
}

View File

@@ -32,7 +32,7 @@ public class XZIndex
public static XZIndex FromStream(Stream stream, bool indexMarkerAlreadyVerified)
{
var index = new XZIndex(
new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8),
new BinaryReader(stream, Encoding.UTF8, true),
indexMarkerAlreadyVerified
);
index.Process();

View File

@@ -1,10 +1,31 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Xz;
public abstract class XZReadOnlyStream : ReadOnlyStream
public abstract class XZReadOnlyStream : ReadOnlyStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => base.BaseStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
public XZReadOnlyStream(Stream stream)
{
BaseStream = stream;
@@ -12,5 +33,16 @@ public abstract class XZReadOnlyStream : ReadOnlyStream
{
throw new InvalidFormatException("Must be able to read from stream");
}
#if DEBUG_STREAMS
this.DebugConstruct(typeof(XZReadOnlyStream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(XZReadOnlyStream));
#endif
base.Dispose(disposing);
}
}

View File

@@ -3,12 +3,49 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Xz;
[CLSCompliant(false)]
public sealed class XZStream : XZReadOnlyStream
public sealed class XZStream : XZReadOnlyStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => _baseStream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
public XZStream(Stream baseStream)
: base(baseStream)
{
_baseStream = baseStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(XZStream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(XZStream));
#endif
base.Dispose(disposing);
}
public static bool IsXZStream(Stream stream)
{
try
@@ -35,6 +72,7 @@ public sealed class XZStream : XZReadOnlyStream
}
}
private readonly Stream _baseStream;
public XZHeader Header { get; private set; }
public XZIndex Index { get; private set; }
public XZFooter Footer { get; private set; }
@@ -43,9 +81,6 @@ public sealed class XZStream : XZReadOnlyStream
private bool _endOfStream;
public XZStream(Stream stream)
: base(stream) { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = 0;

View File

@@ -0,0 +1,65 @@
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.ZStandard;
internal class ZStandardStream : ZstdSharp.DecompressionStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
public int DefaultBufferSize { get; set; }
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
private readonly Stream stream;
Stream IStreamStack.BaseStream() => stream;
void IStreamStack.SetPosition(long position) { }
internal static bool IsZStandard(Stream stream)
{
var br = new BinaryReader(stream);
var magic = br.ReadUInt32();
if (ZstandardConstants.MAGIC != magic)
{
return false;
}
return true;
}
public ZStandardStream(Stream baseInputStream)
: base(baseInputStream)
{
this.stream = baseInputStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ZStandardStream));
#endif
}
/// <summary>
/// The current position within the stream.
/// Throws a NotSupportedException when attempting to set the position
/// </summary>
/// <exception cref="NotSupportedException">Attempting to set the position</exception>
public override long Position
{
get { return stream.Position; }
set { throw new NotSupportedException("InflaterInputStream Position not supported"); }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpCompress.Compressors.ZStandard;
internal class ZstandardConstants
{
/// <summary>
/// Magic number found at start of ZStandard frame: 0xFD 0x2F 0xB5 0x28
/// </summary>
public const uint MAGIC = 0xFD2FB528;
}

View File

@@ -2,22 +2,63 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Crypto;
[CLSCompliant(false)]
public sealed class Crc32Stream(Stream stream, uint polynomial, uint seed) : Stream
public sealed class Crc32Stream : 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 Stream stream;
private readonly uint[] _table;
private uint seed;
public const uint DEFAULT_POLYNOMIAL = 0xedb88320u;
public const uint DEFAULT_SEED = 0xffffffffu;
private static uint[] _defaultTable;
private readonly uint[] _table = InitializeTable(polynomial);
public Crc32Stream(Stream stream)
: this(stream, DEFAULT_POLYNOMIAL, DEFAULT_SEED) { }
public Crc32Stream(Stream stream, uint polynomial, uint seed)
{
this.stream = stream;
_table = InitializeTable(polynomial);
this.seed = seed;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(Crc32Stream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(Crc32Stream));
#endif
base.Dispose(disposing);
}
public Stream WrappedStream => stream;
public override void Flush() => stream.Flush();

View File

@@ -23,7 +23,11 @@ namespace SharpCompress.Factories
yield return "arc";
}
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
//You may have to use some(paranoid) checks to ensure that you actually are
//processing an ARC file, since other archivers also adopted the idea of putting

View File

@@ -34,7 +34,7 @@ public abstract class Factory : IFactory
/// <exception cref="ArgumentNullException"><paramref name="factory"/> must not be null.</exception>
public static void RegisterFactory(Factory factory)
{
factory.CheckNotNull(nameof(factory));
factory.NotNull(nameof(factory));
_factories.Add(factory);
}
@@ -49,23 +49,27 @@ public abstract class Factory : IFactory
public abstract IEnumerable<string> GetSupportedExtensions();
/// <inheritdoc/>
public abstract bool IsArchive(Stream stream, string? password = null);
public abstract bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
);
/// <inheritdoc/>
public virtual FileInfo? GetFilePart(int index, FileInfo part1) => null;
/// <summary>
/// Tries to open an <see cref="IReader"/> from a <see cref="RewindableStream"/>.
/// Tries to open an <see cref="IReader"/> from a <see cref="SharpCompressStream"/>.
/// </summary>
/// <remarks>
/// This method provides extra insight to support loading compressed TAR files.
/// </remarks>
/// <param name="rewindableStream"></param>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="reader"></param>
/// <returns></returns>
internal virtual bool TryOpenReader(
RewindableStream rewindableStream,
SharpCompressStream stream,
ReaderOptions options,
out IReader? reader
)
@@ -74,11 +78,12 @@ public abstract class Factory : IFactory
if (this is IReaderFactory readerFactory)
{
rewindableStream.Rewind(false);
if (IsArchive(rewindableStream, options.Password))
long pos = ((IStreamStack)stream).GetPosition();
if (IsArchive(stream, options.Password, options.BufferSize))
{
rewindableStream.Rewind(true);
reader = readerFactory.OpenReader(rewindableStream, options);
((IStreamStack)stream).StackSeek(pos);
reader = readerFactory.OpenReader(stream, options);
return true;
}
}

View File

@@ -40,8 +40,11 @@ public class GZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
GZipArchive.IsGZipFile(stream);
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => GZipArchive.IsGZipFile(stream);
#endregion
@@ -73,26 +76,27 @@ public class GZipFactory
/// <inheritdoc/>
internal override bool TryOpenReader(
RewindableStream rewindableStream,
SharpCompressStream rewindableStream,
ReaderOptions options,
out IReader? reader
)
{
reader = null;
rewindableStream.Rewind(false);
long pos = ((IStreamStack)rewindableStream).GetPosition();
if (GZipArchive.IsGZipFile(rewindableStream))
{
rewindableStream.Rewind(false);
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress);
if (TarArchive.IsTarFile(testStream))
{
rewindableStream.Rewind(true);
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.GZip);
return true;
}
rewindableStream.Rewind(true);
((IStreamStack)rewindableStream).StackSeek(pos);
reader = OpenReader(rewindableStream, options);
return true;
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.Readers;
namespace SharpCompress.Factories;
@@ -35,7 +36,11 @@ public interface IFactory
/// </summary>
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
/// <param name="password">optional password</param>
bool IsArchive(Stream stream, string? password = null);
bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
);
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.

View File

@@ -29,8 +29,11 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
RarArchive.IsRarFile(stream);
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => RarArchive.IsRarFile(stream);
/// <inheritdoc/>
public override FileInfo? GetFilePart(int index, FileInfo part1) =>

View File

@@ -28,8 +28,11 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
SevenZipArchive.IsSevenZipFile(stream);
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => SevenZipArchive.IsSevenZipFile(stream);
#endregion
@@ -60,7 +63,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
#region reader
internal override bool TryOpenReader(
RewindableStream rewindableStream,
SharpCompressStream rewindableStream,
ReaderOptions options,
out IReader? reader
)

View File

@@ -1,18 +1,23 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Archives;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.Xz;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
using SharpCompress.Writers;
using SharpCompress.Writers.Tar;
using GZipArchive = SharpCompress.Archives.GZip.GZipArchive;
namespace SharpCompress.Factories;
@@ -38,37 +43,21 @@ public class TarFactory
/// <inheritdoc/>
public override IEnumerable<string> GetSupportedExtensions()
{
// from https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
yield return "tar";
// gzip
yield return "taz";
yield return "tgz";
// bzip2
yield return "tb2";
yield return "tbz";
yield return "tbz2";
yield return "tz2";
// lzma
// yield return "tlz"; // unsupported
// xz
// yield return "txz"; // unsupported
// compress
yield return "tZ";
yield return "taZ";
// zstd
// yield return "tzst"; // unsupported
foreach (var testOption in compressionOptions)
{
foreach (var ext in testOption.KnownExtensions)
{
yield return ext;
}
}
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null) =>
TarArchive.IsTarFile(stream);
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => TarArchive.IsTarFile(stream);
#endregion
@@ -98,78 +87,115 @@ public class TarFactory
#region IReaderFactory
protected class TestOption
{
public readonly CompressionType Type;
public readonly Func<Stream, bool> CanHandle;
public readonly bool WrapInSharpCompressStream;
public readonly Func<Stream, Stream> CreateStream;
public readonly IEnumerable<string> KnownExtensions;
public TestOption(
CompressionType Type,
Func<Stream, bool> CanHandle,
Func<Stream, Stream> CreateStream,
IEnumerable<string> KnownExtensions,
bool WrapInSharpCompressStream = true
)
{
this.Type = Type;
this.CanHandle = CanHandle;
this.WrapInSharpCompressStream = WrapInSharpCompressStream;
this.CreateStream = CreateStream;
this.KnownExtensions = KnownExtensions;
}
}
// https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files
protected TestOption[] compressionOptions =
[
new(CompressionType.None, (stream) => true, (stream) => stream, ["tar"], false), // We always do a test for IsTarFile later
new(
CompressionType.BZip2,
BZip2Stream.IsBZip2,
(stream) => new BZip2Stream(stream, CompressionMode.Decompress, false),
["tar.bz2", "tb2", "tbz", "tbz2", "tz2"]
),
new(
CompressionType.GZip,
GZipArchive.IsGZipFile,
(stream) => new GZipStream(stream, CompressionMode.Decompress),
["tar.gz", "taz", "tgz"]
),
new(
CompressionType.ZStandard,
ZStandardStream.IsZStandard,
(stream) => new ZStandardStream(stream),
["tar.zst", "tar.zstd", "tzst", "tzstd"]
),
new(
CompressionType.LZip,
LZipStream.IsLZipFile,
(stream) => new LZipStream(stream, CompressionMode.Decompress),
["tar.lz"]
),
new(
CompressionType.Xz,
XZStream.IsXZStream,
(stream) => new XZStream(stream),
["tar.xz", "txz"],
false
),
new(
CompressionType.Lzw,
LzwStream.IsLzwStream,
(stream) => new LzwStream(stream),
["tar.Z", "tZ", "taZ"],
false
),
];
/// <inheritdoc/>
internal override bool TryOpenReader(
RewindableStream rewindableStream,
SharpCompressStream rewindableStream,
ReaderOptions options,
out IReader? reader
)
{
reader = null;
rewindableStream.Rewind(false);
if (TarArchive.IsTarFile(rewindableStream))
long pos = ((IStreamStack)rewindableStream).GetPosition();
TestOption? testedOption = null;
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
{
rewindableStream.Rewind(true);
reader = OpenReader(rewindableStream, options);
return true;
}
rewindableStream.Rewind(false);
if (BZip2Stream.IsBZip2(rewindableStream))
{
rewindableStream.Rewind(false);
var testStream = new BZip2Stream(
NonDisposingStream.Create(rewindableStream),
CompressionMode.Decompress,
false
testedOption = compressionOptions.FirstOrDefault(a =>
a.KnownExtensions.Contains(
options.ExtensionHint,
StringComparer.CurrentCultureIgnoreCase
)
);
if (TarArchive.IsTarFile(testStream))
if (testedOption != null)
{
rewindableStream.Rewind(true);
reader = new TarReader(rewindableStream, options, CompressionType.BZip2);
return true;
reader = TryOption(rewindableStream, options, pos, testedOption);
if (reader != null)
{
return true;
}
}
}
rewindableStream.Rewind(false);
if (LZipStream.IsLZipFile(rewindableStream))
foreach (var testOption in compressionOptions)
{
rewindableStream.Rewind(false);
var testStream = new LZipStream(
NonDisposingStream.Create(rewindableStream),
CompressionMode.Decompress
);
if (TarArchive.IsTarFile(testStream))
if (testedOption == testOption)
{
rewindableStream.Rewind(true);
reader = new TarReader(rewindableStream, options, CompressionType.LZip);
return true;
continue; // Already tested above
}
}
rewindableStream.Rewind(false);
if (XZStream.IsXZStream(rewindableStream))
{
rewindableStream.Rewind(true);
var testStream = new XZStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
((IStreamStack)rewindableStream).StackSeek(pos);
reader = TryOption(rewindableStream, options, pos, testOption);
if (reader != null)
{
rewindableStream.Rewind(true);
reader = new TarReader(rewindableStream, options, CompressionType.Xz);
return true;
}
}
rewindableStream.Rewind(false);
if (LzwStream.IsLzwStream(rewindableStream))
{
rewindableStream.Rewind(false);
var testStream = new LzwStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
{
rewindableStream.Rewind(true);
reader = new TarReader(rewindableStream, options, CompressionType.Lzw);
return true;
}
}
@@ -177,6 +203,33 @@ public class TarFactory
return false;
}
private static IReader? TryOption(
SharpCompressStream rewindableStream,
ReaderOptions options,
long pos,
TestOption testOption
)
{
if (testOption.CanHandle(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var inStream = rewindableStream;
if (testOption.WrapInSharpCompressStream)
{
inStream = SharpCompressStream.Create(rewindableStream, leaveOpen: true);
}
var testStream = testOption.CreateStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
return new TarReader(rewindableStream, options, testOption.Type);
}
}
return null;
}
/// <inheritdoc/>
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
TarReader.Open(stream, options);

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.Readers;
namespace SharpCompress.Factories;
internal class ZStandardFactory : Factory
{
public override string Name => "ZStandard";
public override IEnumerable<string> GetSupportedExtensions()
{
yield return "zst";
yield return "zstd";
}
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = 65536
) => ZStandardStream.IsZStandard(stream);
}

View File

@@ -3,6 +3,7 @@ using System.IO;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.Zip;
using SharpCompress.Writers;
@@ -38,13 +39,22 @@ public class ZipFactory
}
/// <inheritdoc/>
public override bool IsArchive(Stream stream, string? password = null)
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var startPosition = stream.CanSeek ? stream.Position : -1;
// probe for single volume zip
if (ZipArchive.IsZipFile(stream, password))
if (stream is not SharpCompressStream) // wrap to provide buffer bef
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
if (ZipArchive.IsZipFile(stream, password, bufferSize))
{
return true;
}
@@ -59,11 +69,13 @@ public class ZipFactory
stream.Position = startPosition;
//test the zip (last) file of a multipart zip
if (ZipArchive.IsZipMulti(stream))
if (ZipArchive.IsZipMulti(stream, password, bufferSize))
{
return true;
}
stream.Position = startPosition;
return false;
}

View File

@@ -3,14 +3,39 @@ using System.IO;
namespace SharpCompress.IO;
internal class BufferedSubStream(Stream stream, long origin, long bytesToRead)
: NonDisposingStream(stream, throwOnDispose: false)
internal class BufferedSubStream : SharpCompressStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => base.Stream;
public BufferedSubStream(Stream stream, long origin, long bytesToRead)
: base(stream, leaveOpen: true, throwOnDispose: false)
{
#if DEBUG_STREAMS
this.DebugConstruct(typeof(BufferedSubStream));
#endif
this.origin = origin;
this.BytesLeftToRead = bytesToRead;
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(BufferedSubStream));
#endif
if (disposing) { }
base.Dispose(disposing);
}
private int _cacheOffset;
private int _cacheLength;
private readonly byte[] _cache = new byte[32 << 10];
private long origin;
private long BytesLeftToRead { get; set; } = bytesToRead;
private long BytesLeftToRead { get; set; }
public override bool CanRead => true;

View File

@@ -1,47 +0,0 @@
using System;
using System.IO;
namespace SharpCompress.IO;
internal class CountingWritableSubStream : NonDisposingStream
{
internal CountingWritableSubStream(Stream stream)
: base(stream, throwOnDispose: false) { }
public ulong Count { get; private set; }
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override void Flush() => Stream.Flush();
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count) =>
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)
{
Stream.Write(buffer, offset, count);
Count += (uint)count;
}
public override void WriteByte(byte value)
{
Stream.WriteByte(value);
++Count;
}
}

View File

@@ -3,8 +3,28 @@ using System.IO;
namespace SharpCompress.IO;
public class DataDescriptorStream : Stream
public class DataDescriptorStream : 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 { return; }
}
int IStreamStack.BufferPosition
{
get => 0;
set { return; }
}
void IStreamStack.SetPosition(long position) { }
private readonly Stream _stream;
private long _start;
private int _searchPosition;
@@ -20,6 +40,10 @@ public class DataDescriptorStream : Stream
_start = _stream.Position;
_searchPosition = 0;
_done = false;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(DataDescriptorStream));
#endif
}
internal bool IsRecording { get; private set; }
@@ -31,6 +55,9 @@ public class DataDescriptorStream : Stream
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(DataDescriptorStream));
#endif
base.Dispose(disposing);
if (disposing)
{

View File

@@ -0,0 +1,364 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace SharpCompress.IO
{
public interface IStreamStack
{
/// <summary>
/// Gets or sets the default buffer size to be applied when buffering is enabled for this stream stack.
/// This value is used by the SetBuffer extension method to configure buffering on the appropriate stream
/// in the stack hierarchy. A value of 0 indicates no default buffer size is set.
/// </summary>
int DefaultBufferSize { get; set; }
/// <summary>
/// Returns the immediate underlying stream in the stack.
/// </summary>
Stream BaseStream();
/// <summary>
/// Gets or sets the size of the buffer if the stream supports buffering; otherwise, returns 0.
/// This property must not throw.
/// </summary>
int BufferSize { get; set; }
/// <summary>
/// Gets or sets the current position within the buffer if the stream supports buffering; otherwise, returns 0.
/// This property must not throw.
/// </summary>
int BufferPosition { get; set; }
/// <summary>
/// Updates the internal position state of the stream. This should not perform seeking on the underlying stream,
/// but should update any internal position or buffer state as appropriate for the stream implementation.
/// </summary>
/// <param name="position">The absolute position to set within the stream stack.</param>
void SetPosition(long position);
#if DEBUG_STREAMS
/// <summary>
/// Gets or sets the unique instance identifier for debugging purposes.
/// </summary>
long InstanceId { get; set; }
#endif
}
internal static class StackStreamExtensions
{
/// <summary>
/// Gets the logical position of the first buffering stream in the stack, or 0 if none exist.
/// </summary>
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
/// <returns>The position of the first buffering stream, or 0 if not found.</returns>
internal static long GetPosition(this IStreamStack stream)
{
IStreamStack? current = stream;
while (current != null)
{
if (current.BufferSize != 0 && current is Stream st)
{
return st.Position;
}
current = current?.BaseStream() as IStreamStack;
}
return 0;
}
/// <summary>
/// Rewinds the buffer of the outermost buffering stream in the stack by the specified count, if supported.
/// Only the most derived buffering stream is affected.
/// </summary>
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
/// <param name="count">The number of bytes to rewind within the buffer.</param>
internal static void Rewind(this IStreamStack stream, int count)
{
Stream baseStream = stream.BaseStream();
Stream thisStream = (Stream)stream;
IStreamStack? buffStream = null;
IStreamStack? current = stream;
while (buffStream == null && current != null)
{
if (current.BufferSize != 0)
{
buffStream = current;
buffStream.BufferPosition -= Math.Min(buffStream.BufferPosition, count);
}
current = current?.BaseStream() as IStreamStack;
}
}
/// <summary>
/// Sets the buffer size on the first buffering stream in the stack, or on the outermost stream if none exist.
/// If <paramref name="force"/> is true, sets the buffer size regardless of current value.
/// </summary>
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
/// <param name="bufferSize">The buffer size to set.</param>
/// <param name="force">If true, forces the buffer size to be set even if already set.</param>
internal static void SetBuffer(this IStreamStack stream, int bufferSize, bool force)
{
if (bufferSize == 0 || stream == null)
return;
IStreamStack? current = stream;
IStreamStack defaultBuffer = stream;
IStreamStack? buffer = null;
// First pass: find the deepest IStreamStack
while (current != null)
{
defaultBuffer = current;
if (buffer == null && ((current.BufferSize != 0 && bufferSize != 0) || force))
buffer = current;
if (defaultBuffer.DefaultBufferSize != 0)
break;
current = current.BaseStream() as IStreamStack;
}
if (defaultBuffer.DefaultBufferSize == 0)
defaultBuffer.DefaultBufferSize = bufferSize;
(buffer ?? stream).BufferSize = bufferSize;
}
/// <summary>
/// Attempts to set the position in the stream stack. If a buffering stream is present and the position is within its buffer,
/// BufferPosition is set on the outermost buffering stream and all intermediate streams update their internal state via SetPosition.
/// If no buffering stream is present, seeks as close to the root stream as possible and updates all intermediate streams' state via SetPosition.
/// Seeking is never performed if any intermediate stream in the stack is buffering.
/// Throws if the position cannot be set.
/// </summary>
/// <param name="stream">
/// The most derived (outermost) stream in the stack. The method traverses up the stack via BaseStream() until a stream can satisfy the buffer or seek request.
/// </param>
/// <param name="position">The absolute position to set.</param>
/// <returns>The position that was set.</returns>
internal static long StackSeek(this IStreamStack stream, long position)
{
var stack = new List<IStreamStack>();
Stream? current = stream as Stream;
int lastBufferingIndex = -1;
int firstSeekableIndex = -1;
Stream? firstSeekableStream = null;
// Traverse the stack, collecting info
while (current is IStreamStack stackStream)
{
stack.Add(stackStream);
if (stackStream.BufferSize > 0)
{
lastBufferingIndex = stack.Count - 1;
break;
}
current = stackStream.BaseStream();
}
// Find the first seekable stream (closest to the root)
if (current != null && current.CanSeek)
{
firstSeekableIndex = stack.Count;
firstSeekableStream = current;
}
// If any buffering stream exists, try to set BufferPosition on the outermost one
if (lastBufferingIndex != -1)
{
var bufferingStream = stack[lastBufferingIndex];
if (position >= 0 && position < bufferingStream.BufferSize)
{
bufferingStream.BufferPosition = (int)position;
return position;
}
else
{
// 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
}
// If no buffering, or buffer was reset, seek at the first seekable stream (closest to the root)
if (firstSeekableStream != null)
{
firstSeekableStream.Seek(position, SeekOrigin.Begin);
return firstSeekableStream.Position;
}
throw new NotSupportedException(
"Cannot set position on this stream stack (no seekable or buffering stream supports the requested position)."
);
}
/// <summary>
/// Reads bytes from the stream, using the position to observe how much was actually consumed and rewind the buffer to ensure further reads are correct.
/// This is required to prevent buffered reads from skipping data, while also benefiting from buffering and reduced stream IO reads.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="offset">The offset in the buffer to start writing data.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="buffStream">Returns the buffering stream found in the stack, or null if none exists.</param>
/// <param name="baseReadCount">Returns the number of bytes actually read from the base stream, or -1 if no buffering stream was found.</param>
/// <returns>The number of bytes read into the buffer.</returns>
internal static int Read(
this IStreamStack stream,
byte[] buffer,
int offset,
int count,
out IStreamStack? buffStream,
out int baseReadCount
)
{
Stream baseStream = stream.BaseStream();
Stream thisStream = (Stream)stream;
IStreamStack? current = stream;
buffStream = null;
baseReadCount = -1;
while (buffStream == null && (current = current?.BaseStream() as IStreamStack) != null)
{
if (current.BufferSize != 0)
{
buffStream = current;
}
}
long buffPos = buffStream == null ? -1 : ((Stream)buffStream).Position;
int read = baseStream.Read(buffer, offset, count); //amount read in to buffer
if (buffPos != -1)
{
baseReadCount = (int)(((Stream)buffStream!).Position - buffPos);
}
return read;
}
#if DEBUG_STREAMS
private static long _instanceCounter = 0;
private static string cleansePos(long pos)
{
if (pos < 0)
return "";
return "Px" + pos.ToString("x");
}
/// <summary>
/// Gets or creates a unique instance ID for the stream stack for debugging purposes.
/// </summary>
/// <param name="stream">The stream stack.</param>
/// <param name="instanceId">Reference to the instance ID field.</param>
/// <param name="construct">Whether this is being called during construction.</param>
/// <returns>The instance ID.</returns>
public static long GetInstanceId(
this IStreamStack stream,
ref long instanceId,
bool construct
)
{
if (instanceId == 0) //will not be equal to 0 when inherited IStackStream types are being used
instanceId = System.Threading.Interlocked.Increment(ref _instanceCounter);
return instanceId;
}
/// <summary>
/// Writes a debug message for stream construction.
/// </summary>
/// <param name="stream">The stream stack.</param>
/// <param name="constructing">The type being constructed.</param>
public static void DebugConstruct(this IStreamStack stream, Type constructing)
{
long id = stream.InstanceId;
stream.InstanceId = GetInstanceId(stream, ref id, true);
var frame = (new StackTrace()).GetFrame(3);
string parentInfo =
frame != null
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
: "Unknown";
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
Debug.WriteLine(
$"{GetStreamStackString(stream, true)} : Constructed by [{parentInfo}]"
);
}
/// <summary>
/// Writes a debug message for stream disposal.
/// </summary>
/// <param name="stream">The stream stack.</param>
/// <param name="constructing">The type being disposed.</param>
public static void DebugDispose(this IStreamStack stream, Type constructing)
{
var frame = (new StackTrace()).GetFrame(3);
string parentInfo =
frame != null
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
: "Unknown";
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
Debug.WriteLine($"{GetStreamStackString(stream, false)} : Disposed by [{parentInfo}]");
}
/// <summary>
/// Writes a debug trace message for the stream.
/// </summary>
/// <param name="stream">The stream stack.</param>
/// <param name="message">The debug message to write.</param>
public static void DebugTrace(this IStreamStack stream, string message)
{
Debug.WriteLine(
$"{GetStreamStackString(stream, false)} : [{stream.GetType().Name}]{message}"
);
}
/// <summary>
/// Returns the full stream chain as a string, including instance IDs and positions.
/// </summary>
/// <param name="stream">The stream stack to represent.</param>
/// <param name="construct">Whether this is being called during construction.</param>
/// <returns>A string representation of the entire stream stack.</returns>
public static string GetStreamStackString(this IStreamStack stream, bool construct)
{
var sb = new StringBuilder();
Stream? current = stream as Stream;
while (current != null)
{
IStreamStack? sStack = current as IStreamStack;
string id = sStack != null ? "#" + sStack.InstanceId.ToString() : "";
string buffSize = sStack != null ? "Bx" + sStack.BufferSize.ToString("x") : "";
string defBuffSize =
sStack != null ? "Dx" + sStack.DefaultBufferSize.ToString("x") : "";
if (sb.Length > 0)
sb.Insert(0, "/");
try
{
sb.Insert(
0,
$"{current.GetType().Name}{id}[{cleansePos(current.Position)}:{buffSize}:{defBuffSize}]"
);
}
catch
{
if (current is SharpCompressStream scs)
sb.Insert(
0,
$"{current.GetType().Name}{id}[{cleansePos(scs.InternalPosition)}:{buffSize}:{defBuffSize}]"
);
else
sb.Insert(0, $"{current.GetType().Name}{id}[:{buffSize}]");
}
if (sStack != null)
current = sStack.BaseStream(); //current may not be a IStreamStack, allow one more loop
else
break;
}
return sb.ToString();
}
#endif
}
}

View File

@@ -1,10 +1,30 @@
using System.IO;
using System.IO;
using SharpCompress.Common;
namespace SharpCompress.IO;
internal class ListeningStream : Stream
internal class ListeningStream : 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 { return; }
}
int IStreamStack.BufferPosition
{
get => 0;
set { return; }
}
void IStreamStack.SetPosition(long position) { }
private long _currentEntryTotalReadBytes;
private readonly IExtractionListener _listener;
@@ -12,10 +32,16 @@ internal class ListeningStream : Stream
{
Stream = stream;
this._listener = listener;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ListeningStream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ListeningStream));
#endif
if (disposing)
{
Stream.Dispose();

View File

@@ -1,73 +0,0 @@
using System;
using System.IO;
namespace SharpCompress.IO;
public class NonDisposingStream : Stream
{
public static NonDisposingStream Create(Stream stream, bool throwOnDispose = false)
{
if (
stream is NonDisposingStream nonDisposingStream
&& nonDisposingStream.ThrowOnDispose == throwOnDispose
)
{
return nonDisposingStream;
}
return new NonDisposingStream(stream, throwOnDispose);
}
protected NonDisposingStream(Stream stream, bool throwOnDispose = false)
{
Stream = stream;
ThrowOnDispose = throwOnDispose;
}
public bool ThrowOnDispose { get; set; }
protected override void Dispose(bool disposing)
{
if (ThrowOnDispose)
{
throw new InvalidOperationException(
$"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}"
);
}
}
protected Stream Stream { get; }
public override bool CanRead => Stream.CanRead;
public override bool CanSeek => Stream.CanSeek;
public override bool CanWrite => Stream.CanWrite;
public override void Flush() => Stream.Flush();
public override long Length => Stream.Length;
public override long Position
{
get => Stream.Position;
set => Stream.Position = value;
}
public override int Read(byte[] buffer, int offset, int count) =>
Stream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => Stream.Seek(offset, origin);
public override void SetLength(long value) => Stream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
Stream.Write(buffer, offset, count);
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override int Read(Span<byte> buffer) => Stream.Read(buffer);
public override void Write(ReadOnlySpan<byte> buffer) => Stream.Write(buffer);
#endif
}

View File

@@ -1,17 +1,24 @@
using System;
using System.Diagnostics;
using System.IO;
namespace SharpCompress.IO;
internal class ReadOnlySubStream : NonDisposingStream
internal class ReadOnlySubStream : SharpCompressStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
Stream IStreamStack.BaseStream() => base.Stream;
private long _position;
public ReadOnlySubStream(Stream stream, long bytesToRead)
: this(stream, null, bytesToRead) { }
public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead)
: base(stream, throwOnDispose: false)
: base(stream, leaveOpen: true, throwOnDispose: false)
{
if (origin != null && stream.Position != origin.Value)
{
@@ -19,6 +26,9 @@ internal class ReadOnlySubStream : NonDisposingStream
}
BytesLeftToRead = bytesToRead;
_position = 0;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ReadOnlySubStream));
#endif
}
private long BytesLeftToRead { get; set; }
@@ -89,4 +99,12 @@ internal class ReadOnlySubStream : NonDisposingStream
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(ReadOnlySubStream));
#endif
base.Dispose(disposing);
}
}

Some files were not shown because too many files have changed in this diff Show More