mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-04 05:25:00 +00:00
Compare commits
353 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fa7e854d3 | ||
|
|
0f5c080be6 | ||
|
|
871009ef8d | ||
|
|
89a565c6c4 | ||
|
|
6ed60e4d5f | ||
|
|
4dab1df3ae | ||
|
|
7b7aa2cdf0 | ||
|
|
d1e1a65f32 | ||
|
|
9d332b4ac5 | ||
|
|
fc2462e281 | ||
|
|
fb2cddabf0 | ||
|
|
33481a9465 | ||
|
|
45de87cb97 | ||
|
|
553c533ada | ||
|
|
634e562b93 | ||
|
|
c789ead590 | ||
|
|
d23560a441 | ||
|
|
9f306966db | ||
|
|
8eea2cef97 | ||
|
|
3abbb89c2e | ||
|
|
76de7d54c7 | ||
|
|
35aee6e066 | ||
|
|
8a0152fe7c | ||
|
|
7769435fc8 | ||
|
|
ae311ea66e | ||
|
|
d15816f162 | ||
|
|
5cbb31c559 | ||
|
|
b7f5b36f2b | ||
|
|
e9dd413de6 | ||
|
|
c5de3d8cc1 | ||
|
|
624c385e27 | ||
|
|
893890c985 | ||
|
|
e12efc8b52 | ||
|
|
015dfa41b1 | ||
|
|
5e72ce5fbe | ||
|
|
236e653176 | ||
|
|
3946c3ba03 | ||
|
|
5bdd39e6eb | ||
|
|
46267c0531 | ||
|
|
7b96eb7704 | ||
|
|
60d5955101 | ||
|
|
587675e488 | ||
|
|
c0ae319c63 | ||
|
|
d810f8d8db | ||
|
|
a88390a546 | ||
|
|
8575e5615d | ||
|
|
5fe9516c09 | ||
|
|
938775789d | ||
|
|
f37bebe51f | ||
|
|
21f14cd3f2 | ||
|
|
4e7baeb2c9 | ||
|
|
d78a682dd8 | ||
|
|
44021b7abc | ||
|
|
7f9e543213 | ||
|
|
c489e5a59b | ||
|
|
8de30db7f9 | ||
|
|
415f0c6774 | ||
|
|
aa9cf195a5 | ||
|
|
6412fc8135 | ||
|
|
8b1ba9a00c | ||
|
|
b02584ef9e | ||
|
|
88cd6bfd1a | ||
|
|
89a3da14c9 | ||
|
|
619d987492 | ||
|
|
744e410a1a | ||
|
|
6e51967993 | ||
|
|
7989ab2e28 | ||
|
|
a3570a568d | ||
|
|
c0cd998836 | ||
|
|
1a452acd1c | ||
|
|
6c54083b08 | ||
|
|
76105ebdaf | ||
|
|
2c452201fa | ||
|
|
9fc7fc73f9 | ||
|
|
e7417e35ba | ||
|
|
19eb4c7cba | ||
|
|
1f39a0c9da | ||
|
|
a480a8893c | ||
|
|
d3a9e341a5 | ||
|
|
95caffe607 | ||
|
|
fdeca61284 | ||
|
|
45dc653191 | ||
|
|
6e48302b7b | ||
|
|
a7918d7b11 | ||
|
|
ec21253af9 | ||
|
|
97cdda8663 | ||
|
|
35ac2b9d71 | ||
|
|
14affd8ffa | ||
|
|
c48409c903 | ||
|
|
227f66f299 | ||
|
|
c2d9bf94d1 | ||
|
|
8f03841161 | ||
|
|
344a1ed912 | ||
|
|
ff71993b31 | ||
|
|
18bb773b2c | ||
|
|
7f905c7940 | ||
|
|
8a4ba6fc56 | ||
|
|
e0b275c01c | ||
|
|
5926db85df | ||
|
|
a4715a10e7 | ||
|
|
de0f5c0fcb | ||
|
|
88d85ce6ac | ||
|
|
131c171d3e | ||
|
|
c5ddef6ef7 | ||
|
|
f36486d006 | ||
|
|
eaf466c5c3 | ||
|
|
b41bcc349e | ||
|
|
825c61bdcd | ||
|
|
f13f49bd71 | ||
|
|
88f5d4544b | ||
|
|
b7432a20f0 | ||
|
|
ab57c85e66 | ||
|
|
c7c41bc0d8 | ||
|
|
c66f9b06a4 | ||
|
|
a3ac7e7ca7 | ||
|
|
187b762f8a | ||
|
|
3a7fbdfa52 | ||
|
|
bbeb46b37f | ||
|
|
5450b9a700 | ||
|
|
353b28647c | ||
|
|
2411f4f870 | ||
|
|
34cd059423 | ||
|
|
4b4ec12c87 | ||
|
|
441147c0dc | ||
|
|
08ceac9f46 | ||
|
|
6bc690b50b | ||
|
|
c41888b338 | ||
|
|
6d3c980b39 | ||
|
|
eb5db176fa | ||
|
|
a77c03fcaf | ||
|
|
04b25011e9 | ||
|
|
96b34aec10 | ||
|
|
d560b46c85 | ||
|
|
94789ce455 | ||
|
|
55797e5873 | ||
|
|
675cab3074 | ||
|
|
8d63ab646e | ||
|
|
37a2fa1cdc | ||
|
|
79ed9650b3 | ||
|
|
b6dc58164e | ||
|
|
f9a974c1fe | ||
|
|
91e672befb | ||
|
|
c14d18b9df | ||
|
|
d2cfc1844c | ||
|
|
2fb3243a1a | ||
|
|
6f3124a84f | ||
|
|
59eb5bd9c4 | ||
|
|
ad2b6bb4f7 | ||
|
|
51d64ee10e | ||
|
|
1159efc6cc | ||
|
|
3cfb76a56f | ||
|
|
7a1ad6688a | ||
|
|
4f4af6e3fd | ||
|
|
2736b79097 | ||
|
|
8da2ffa433 | ||
|
|
5bf3d6dc32 | ||
|
|
30d4380afe | ||
|
|
c8b5aad482 | ||
|
|
fa1d440947 | ||
|
|
a89fc3a276 | ||
|
|
3875f62453 | ||
|
|
23e1447ab6 | ||
|
|
91364c6a7c | ||
|
|
a070493fba | ||
|
|
ca0a6ab72c | ||
|
|
10e0562a82 | ||
|
|
44998a2a2b | ||
|
|
f8e033e560 | ||
|
|
5842da84af | ||
|
|
10cc270170 | ||
|
|
f51bdd56aa | ||
|
|
7f79c49f93 | ||
|
|
e4920255f0 | ||
|
|
f8e8273d39 | ||
|
|
ceddab98d1 | ||
|
|
0faa176791 | ||
|
|
9b0e0ee536 | ||
|
|
881d2756db | ||
|
|
ab5af40999 | ||
|
|
6aeef8dcf9 | ||
|
|
5d62196dde | ||
|
|
7fe27ac310 | ||
|
|
1e300349ce | ||
|
|
6b01a7b08e | ||
|
|
34d948df18 | ||
|
|
27091c4f1d | ||
|
|
970a3d7f2a | ||
|
|
2bedbbfc54 | ||
|
|
8de33f0db3 | ||
|
|
df4eab67dc | ||
|
|
2d13bc0046 | ||
|
|
704a0cb35d | ||
|
|
06a983e445 | ||
|
|
2d10df8b87 | ||
|
|
baf66db9dc | ||
|
|
3545693999 | ||
|
|
84fb99c2c8 | ||
|
|
21e2983ae1 | ||
|
|
004e0941d5 | ||
|
|
188a426dde | ||
|
|
6fcfae8bfe | ||
|
|
9515350f52 | ||
|
|
6b88f82656 | ||
|
|
e42d953f47 | ||
|
|
471a3f63fe | ||
|
|
9c257faf26 | ||
|
|
d18cad6d76 | ||
|
|
061273be22 | ||
|
|
b89de6caad | ||
|
|
9bc0a1d7c7 | ||
|
|
eee518b7fa | ||
|
|
b7b78edaa3 | ||
|
|
3eaac68ab4 | ||
|
|
a7672190e9 | ||
|
|
4e4e89b6eb | ||
|
|
33dd519f56 | ||
|
|
5c1149aa8b | ||
|
|
9061e92af6 | ||
|
|
49f5ceaa9b | ||
|
|
525b309d37 | ||
|
|
bdb3a787fc | ||
|
|
a9601ef848 | ||
|
|
6fc4b045fd | ||
|
|
446852c7d0 | ||
|
|
c635f00899 | ||
|
|
1393629bc5 | ||
|
|
49ce17b759 | ||
|
|
74888021c8 | ||
|
|
9483856439 | ||
|
|
dbbc7c8132 | ||
|
|
5d9c99508d | ||
|
|
e4d5b56951 | ||
|
|
e31238d121 | ||
|
|
a283d99e1b | ||
|
|
8cb621ebed | ||
|
|
b203d165f5 | ||
|
|
c695e1136d | ||
|
|
d847202308 | ||
|
|
9d24e0a4b8 | ||
|
|
745fe1eb9f | ||
|
|
3e52b85e9d | ||
|
|
d26f020b50 | ||
|
|
095b5f702c | ||
|
|
9622853b8d | ||
|
|
b94e75fabe | ||
|
|
23dd041e2e | ||
|
|
c73ca21b4d | ||
|
|
7ebdc85ad2 | ||
|
|
99e2c8c90d | ||
|
|
f24bfdf945 | ||
|
|
7963233702 | ||
|
|
b550df2038 | ||
|
|
fb55624f5f | ||
|
|
e96366f489 | ||
|
|
900190cf54 | ||
|
|
2af744b474 | ||
|
|
11153084e2 | ||
|
|
4b9c814bfc | ||
|
|
1b5d3a3b6e | ||
|
|
373637e6a7 | ||
|
|
cb223217c1 | ||
|
|
eab97a3f8b | ||
|
|
fdfaa8ab45 | ||
|
|
2321d9dbee | ||
|
|
bf74dd887a | ||
|
|
3612035894 | ||
|
|
6553e9b0cd | ||
|
|
09f2410170 | ||
|
|
fb73d8c0a7 | ||
|
|
f2b0368078 | ||
|
|
02301ecf6d | ||
|
|
bcb61ee3e4 | ||
|
|
6a824429d0 | ||
|
|
6a52f9097f | ||
|
|
3fa85fc516 | ||
|
|
498d132d8a | ||
|
|
b6340f1458 | ||
|
|
4afc7ae2e4 | ||
|
|
95975a4c33 | ||
|
|
198a0673a2 | ||
|
|
94d1503c64 | ||
|
|
5f13e245f0 | ||
|
|
2715ae645d | ||
|
|
0299232cb5 | ||
|
|
93e181cfd9 | ||
|
|
8072eb1212 | ||
|
|
226ce340f2 | ||
|
|
ab5535eba3 | ||
|
|
8da2499495 | ||
|
|
c057ffb153 | ||
|
|
fe13d29549 | ||
|
|
225aaab4f4 | ||
|
|
14c973558b | ||
|
|
f515ff36b6 | ||
|
|
ed57cfd2f9 | ||
|
|
d69559e9c7 | ||
|
|
396717efd1 | ||
|
|
284fa24464 | ||
|
|
0a20b9179a | ||
|
|
a0d5037885 | ||
|
|
4477833b1d | ||
|
|
e0a5ed4bdb | ||
|
|
46d4b26eba | ||
|
|
f7c6edf849 | ||
|
|
6c157def4b | ||
|
|
741712f89f | ||
|
|
4f749da628 | ||
|
|
8b02795d69 | ||
|
|
f8a0069a5d | ||
|
|
388bbe047e | ||
|
|
2d4ce30e58 | ||
|
|
d4fb17cf66 | ||
|
|
372a2c8375 | ||
|
|
8f27121f21 | ||
|
|
b986bf675f | ||
|
|
80718a461b | ||
|
|
2d14ecf58b | ||
|
|
32aa9877c0 | ||
|
|
cee3a9c11d | ||
|
|
b78643f2d8 | ||
|
|
30a31de45b | ||
|
|
e4c4db534c | ||
|
|
4f7a0d3ad0 | ||
|
|
ea3a96eead | ||
|
|
c0e01ac132 | ||
|
|
28ea50bca4 | ||
|
|
619e44b30f | ||
|
|
d678275dee | ||
|
|
08eed53595 | ||
|
|
ff40f7d262 | ||
|
|
3c1ae51dae | ||
|
|
8a59fc9aaf | ||
|
|
b7ea9dd841 | ||
|
|
0320db6b4a | ||
|
|
18c7f58093 | ||
|
|
7f6f7b1436 | ||
|
|
ca49176b97 | ||
|
|
67be0cd9d7 | ||
|
|
902fadef83 | ||
|
|
2777b6411f | ||
|
|
e3235d7f04 | ||
|
|
dc89c8858e | ||
|
|
d28a278d63 | ||
|
|
7080c2abd0 | ||
|
|
43f86bcab8 | ||
|
|
7d9c875c4d | ||
|
|
ed4099eb12 | ||
|
|
632b83f75d | ||
|
|
66c92637f9 | ||
|
|
6bcaebc471 | ||
|
|
7feee1027c | ||
|
|
4fd8c77fa9 | ||
|
|
bc3bb2d323 |
@@ -3,10 +3,11 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "0.25.0",
|
||||
"version": "1.1.2",
|
||||
"commands": [
|
||||
"dotnet-csharpier"
|
||||
]
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ indent_style = tab
|
||||
|
||||
[*.{cs,csx,cake,vb,vbx}]
|
||||
# Default Severity for all .NET Code Style rules below
|
||||
dotnet_analyzer_diagnostic.severity = warning
|
||||
dotnet_analyzer_diagnostic.severity = silent
|
||||
|
||||
##########################################
|
||||
# File Header (Uncomment to support file headers)
|
||||
@@ -269,6 +269,8 @@ dotnet_diagnostic.CA1305.severity = suggestion
|
||||
dotnet_diagnostic.CA1307.severity = suggestion
|
||||
dotnet_diagnostic.CA1309.severity = suggestion
|
||||
dotnet_diagnostic.CA1310.severity = error
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
dotnet_diagnostic.CA1513.severity = suggestion
|
||||
dotnet_diagnostic.CA1707.severity = suggestion
|
||||
dotnet_diagnostic.CA1708.severity = suggestion
|
||||
dotnet_diagnostic.CA1711.severity = suggestion
|
||||
@@ -286,6 +288,7 @@ dotnet_diagnostic.CA1834.severity = error
|
||||
dotnet_diagnostic.CA1845.severity = suggestion
|
||||
dotnet_diagnostic.CA1848.severity = suggestion
|
||||
dotnet_diagnostic.CA1852.severity = suggestion
|
||||
dotnet_diagnostic.CA1860.severity = silent
|
||||
dotnet_diagnostic.CA2016.severity = suggestion
|
||||
dotnet_diagnostic.CA2201.severity = error
|
||||
dotnet_diagnostic.CA2206.severity = error
|
||||
@@ -303,13 +306,12 @@ dotnet_diagnostic.CS1998.severity = error
|
||||
dotnet_diagnostic.CS8602.severity = error
|
||||
dotnet_diagnostic.CS8604.severity = error
|
||||
dotnet_diagnostic.CS8618.severity = error
|
||||
dotnet_diagnostic.CS0618.severity = error
|
||||
dotnet_diagnostic.CS0618.severity = suggestion
|
||||
dotnet_diagnostic.CS1998.severity = error
|
||||
dotnet_diagnostic.CS4014.severity = error
|
||||
dotnet_diagnostic.CS8600.severity = error
|
||||
dotnet_diagnostic.CS8603.severity = error
|
||||
dotnet_diagnostic.CS8625.severity = error
|
||||
dotnet_diagnostic.CS8981.severity = suggestion
|
||||
|
||||
dotnet_diagnostic.BL0005.severity = suggestion
|
||||
|
||||
@@ -318,7 +320,7 @@ dotnet_diagnostic.MVC1000.severity = suggestion
|
||||
dotnet_diagnostic.RZ10012.severity = error
|
||||
|
||||
dotnet_diagnostic.IDE0004.severity = error # redundant cast
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
dotnet_diagnostic.IDE0005.severity = suggestion
|
||||
dotnet_diagnostic.IDE0007.severity = error # Use var
|
||||
dotnet_diagnostic.IDE0011.severity = error # Use braces on if statements
|
||||
dotnet_diagnostic.IDE0010.severity = silent # populate switch
|
||||
@@ -329,7 +331,7 @@ dotnet_diagnostic.IDE0023.severity = suggestion # use expression body for operat
|
||||
dotnet_diagnostic.IDE0024.severity = silent # expression body for operators
|
||||
dotnet_diagnostic.IDE0025.severity = suggestion # use expression body for properties
|
||||
dotnet_diagnostic.IDE0027.severity = suggestion # Use expression body for accessors
|
||||
dotnet_diagnostic.IDE0028.severity = silent
|
||||
dotnet_diagnostic.IDE0028.severity = silent # expression body for accessors
|
||||
dotnet_diagnostic.IDE0032.severity = suggestion # Use auto property
|
||||
dotnet_diagnostic.IDE0033.severity = error # prefer tuple name
|
||||
dotnet_diagnostic.IDE0037.severity = suggestion # simplify anonymous type
|
||||
@@ -337,7 +339,7 @@ dotnet_diagnostic.IDE0040.severity = error # modifiers required
|
||||
dotnet_diagnostic.IDE0041.severity = error # simplify null
|
||||
dotnet_diagnostic.IDE0042.severity = error # deconstruct variable
|
||||
dotnet_diagnostic.IDE0044.severity = suggestion # make field only when possible
|
||||
dotnet_diagnostic.IDE0047.severity = suggestion # paratemeter name
|
||||
dotnet_diagnostic.IDE0047.severity = suggestion # parameter name
|
||||
dotnet_diagnostic.IDE0051.severity = error # unused field
|
||||
dotnet_diagnostic.IDE0052.severity = error # unused member
|
||||
dotnet_diagnostic.IDE0053.severity = suggestion # lambda not needed
|
||||
@@ -351,11 +353,20 @@ dotnet_diagnostic.IDE0066.severity = suggestion # switch expression
|
||||
dotnet_diagnostic.IDE0072.severity = suggestion # Populate switch - forces population of all cases even when default specified
|
||||
dotnet_diagnostic.IDE0078.severity = suggestion # use pattern matching
|
||||
dotnet_diagnostic.IDE0090.severity = suggestion # new can be simplified
|
||||
dotnet_diagnostic.IDE0130.severity = error # namespace folder structure
|
||||
dotnet_diagnostic.IDE0130.severity = suggestion # namespace folder structure
|
||||
dotnet_diagnostic.IDE0160.severity = silent # Use block namespaces ARE NOT required
|
||||
dotnet_diagnostic.IDE0161.severity = error # Please use file namespaces
|
||||
dotnet_diagnostic.IDE0200.severity = suggestion # lambda not needed
|
||||
dotnet_diagnostic.IDE1006.severity = suggestion # Naming rule violation: These words cannot contain lower case characters
|
||||
dotnet_diagnostic.IDE0260.severity = suggestion # Use pattern matching
|
||||
dotnet_diagnostic.IDE0270.severity = suggestion # Null check simplifcation
|
||||
dotnet_diagnostic.IDE0290.severity = error # Primary Constructor
|
||||
dotnet_diagnostic.IDE0300.severity = suggestion # Collection
|
||||
dotnet_diagnostic.IDE0305.severity = suggestion # Collection ToList
|
||||
|
||||
dotnet_diagnostic.NX0001.severity = error
|
||||
dotnet_diagnostic.NX0002.severity = silent
|
||||
dotnet_diagnostic.NX0003.severity = silent
|
||||
|
||||
##########################################
|
||||
# Styles
|
||||
|
||||
13
.github/workflows/dotnetcore.yml
vendored
13
.github/workflows/dotnetcore.yml
vendored
@@ -15,18 +15,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-dotnet@v3
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: NuGet Caching
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('packages.lock.json', '*/packages.lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
dotnet-version: 8.0.x
|
||||
- run: dotnet run --project build/build.csproj
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-sharpcompress.nupkg
|
||||
path: artifacts/*
|
||||
|
||||
64
AGENTS.md
Normal file
64
AGENTS.md
Normal 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.
|
||||
@@ -10,5 +10,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RunAnalyzersDuringLiveAnalysis>False</RunAnalyzersDuringLiveAnalysis>
|
||||
<RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
20
Directory.Packages.props
Normal file
20
Directory.Packages.props
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Bullseye" Version="6.0.0" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.2.0" />
|
||||
<PackageVersion Include="Glob" Version="1.1.9" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.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.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.SkippableFact" Version="1.5.23" />
|
||||
<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>
|
||||
@@ -11,10 +11,11 @@
|
||||
| Archive Format | Compression Format(s) | Compress/Decompress | Archive API | Reader API | Writer API |
|
||||
| ---------------------- | ------------------------------------------------- | ------------------- | --------------- | ---------- | ------------- |
|
||||
| Rar | Rar | Decompress (1) | RarArchive | RarReader | N/A |
|
||||
| Zip (2) | None, DEFLATE, Deflate64, BZip2, LZMA/LZMA2, PPMd | Both | ZipArchive | ZipReader | ZipWriter |
|
||||
| Zip (2) | None, Shrink, Reduce, Implode, DEFLATE, Deflate64, BZip2, LZMA/LZMA2, PPMd | Both | ZipArchive | ZipReader | ZipWriter |
|
||||
| Tar | None | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.GZip | DEFLATE | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.Zstandard | ZStandard | Decompress | TarArchive | TarReader | N/A |
|
||||
| Tar.LZip | LZMA | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | 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
|
||||
|
||||
|
||||
9
NuGet.config
Normal file
9
NuGet.config
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSourceMapping>
|
||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
11
README.md
11
README.md
@@ -1,12 +1,12 @@
|
||||
# SharpCompress
|
||||
|
||||
SharpCompress is a compression library in pure C# for .NET Standard 2.0, 2.1, .NET Core 3.1 and .NET 5.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).
|
||||
|
||||
GitHub Actions Build -
|
||||
[](https://github.com/adamhathcock/sharpcompress/actions/workflows/dotnetcore.yml)
|
||||
[](https://www.robiniadocs.com/d/sharpcompress/api/SharpCompress.html)
|
||||
[](https://dndocs.com/d/sharpcompress/api/index.html)
|
||||
|
||||
## Need Help?
|
||||
|
||||
@@ -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!
|
||||
@@ -36,10 +38,11 @@ I'm always looking for help or ideas. Please submit code or email with ideas. Un
|
||||
|
||||
## TODOs (always lots)
|
||||
|
||||
* RAR 5 decryption support
|
||||
* RAR 5 decryption crc check support
|
||||
* 7Zip writing
|
||||
* Zip64 (Need writing and extend Reading)
|
||||
* Multi-volume Zip support.
|
||||
* ZStandard writing
|
||||
|
||||
## Version Log
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{CDB425
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Directory.Build.props = Directory.Build.props
|
||||
global.json = global.json
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
NuGet.config = NuGet.config
|
||||
.github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
||||
@@ -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,18 +67,22 @@
|
||||
<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"><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue"><Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
@@ -118,6 +122,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></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>
|
||||
@@ -127,6 +132,7 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=6af8f80e_002D9fdd_002D4223_002D8e02_002D473db916f9b2/@EntryIndexedValue"><SessionState ContinuousTestingIsOn="False" ContinuousTestingMode="0" FrameworkVersion="{x:Null}" IsLocked="False" Name="All tests from Solution" PlatformMonoPreference="{x:Null}" PlatformType="{x:Null}" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Solution />
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
||||
|
||||
30
USAGE.md
30
USAGE.md
@@ -27,7 +27,7 @@ To deal with the "correct" rules as well as the expectations of users, I've deci
|
||||
|
||||
To be explicit though, consider always using the overloads that use `ReaderOptions` or `WriterOptions` and explicitly set `LeaveStreamOpen` the way you want.
|
||||
|
||||
If using Compression Stream classes directly and you don't want the wrapped stream to be closed. Use the `NonDisposingStream` as a wrapped to prevent the stream being disposed. The change in 0.21 simplified a lot even though the usage is a bit more convoluted.
|
||||
If using Compression Stream classes directly and you don't want the wrapped stream to be closed. Use the `NonDisposingStream` as a wrapper to prevent the stream being disposed. The change in 0.21 simplified a lot even though the usage is a bit more convoluted.
|
||||
|
||||
## Samples
|
||||
|
||||
@@ -71,18 +71,34 @@ using (var archive = ZipArchive.Create())
|
||||
memoryStream.Position = 0;
|
||||
```
|
||||
|
||||
### Extract all files from a Rar file to a directory using RarArchive
|
||||
### Extract all files from a rar file to a directory using RarArchive
|
||||
|
||||
Note: Extracting a solid rar or 7z file needs to be done in sequential order to get acceptable decompression speed.
|
||||
It is explicitly recommended to use `ExtractAllEntries` when extracting an entire `IArchive` instead of iterating over all its `Entries`.
|
||||
Alternatively, use `IArchive.WriteToDirectory`.
|
||||
|
||||
```C#
|
||||
using (var archive = RarArchive.Open("Test.rar"))
|
||||
{
|
||||
using (var reader = archive.ExtractAllEntries())
|
||||
{
|
||||
reader.WriteAllToDirectory(@"D:\temp", new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Iterate over all files from a Rar file using RarArchive
|
||||
|
||||
```C#
|
||||
using (var archive = RarArchive.Open("Test.rar"))
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
{
|
||||
entry.WriteToDirectory("D:\\temp", new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
Console.WriteLine($"{entry.Key}: {entry.Size} bytes");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ const string Publish = "publish";
|
||||
|
||||
Target(
|
||||
Clean,
|
||||
ForEach("**/bin", "**/obj"),
|
||||
["**/bin", "**/obj"],
|
||||
dir =>
|
||||
{
|
||||
IEnumerable<string> GetDirectories(string d)
|
||||
@@ -44,14 +44,14 @@ Target(
|
||||
() =>
|
||||
{
|
||||
Run("dotnet", "tool restore");
|
||||
Run("dotnet", "csharpier --check .");
|
||||
Run("dotnet", "csharpier check .");
|
||||
}
|
||||
);
|
||||
Target(Restore, DependsOn(Format), () => Run("dotnet", "restore"));
|
||||
Target(Restore, [Format], () => Run("dotnet", "restore"));
|
||||
|
||||
Target(
|
||||
Build,
|
||||
DependsOn(Restore),
|
||||
[Restore],
|
||||
() =>
|
||||
{
|
||||
Run("dotnet", "build src/SharpCompress/SharpCompress.csproj -c Release --no-restore");
|
||||
@@ -60,8 +60,8 @@ Target(
|
||||
|
||||
Target(
|
||||
Test,
|
||||
DependsOn(Build),
|
||||
ForEach("net7.0", "net462"),
|
||||
[Build],
|
||||
["net8.0", "net48"],
|
||||
framework =>
|
||||
{
|
||||
IEnumerable<string> GetFiles(string d)
|
||||
@@ -69,7 +69,7 @@ Target(
|
||||
return Glob.Files(".", d);
|
||||
}
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net462")
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net48")
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -83,13 +83,13 @@ Target(
|
||||
|
||||
Target(
|
||||
Publish,
|
||||
DependsOn(Test),
|
||||
[Test],
|
||||
() =>
|
||||
{
|
||||
Run("dotnet", "pack src/SharpCompress/SharpCompress.csproj -c Release -o artifacts/");
|
||||
}
|
||||
);
|
||||
|
||||
Target("default", DependsOn(Publish), () => Console.WriteLine("Done!"));
|
||||
Target("default", [Publish], () => Console.WriteLine("Done!"));
|
||||
|
||||
await RunTargetsAndExitAsync(args);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bullseye" Version="4.2.1" />
|
||||
<PackageReference Include="Glob" Version="1.1.9" />
|
||||
<PackageReference Include="SimpleExec" Version="11.0.0" />
|
||||
<PackageReference Include="Bullseye" />
|
||||
<PackageReference Include="Glob" />
|
||||
<PackageReference Include="SimpleExec" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
25
build/packages.lock.json
Normal file
25
build/packages.lock.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Bullseye": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "vgwwXfzs7jJrskWH7saHRMgPzziq/e86QZNWY1MnMxd7e+De7E7EX4K3C7yrvaK9y02SJoLxNxcLG/q5qUAghw=="
|
||||
},
|
||||
"Glob": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.9, )",
|
||||
"resolved": "1.1.9",
|
||||
"contentHash": "AfK5+ECWYTP7G3AAdnU8IfVj+QpGjrh9GC2mpdcJzCvtQ4pnerAGwHsxJ9D4/RnhDUz2DSzd951O/lQjQby2Sw=="
|
||||
},
|
||||
"SimpleExec": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.0.0, )",
|
||||
"resolved": "12.0.0",
|
||||
"contentHash": "ptxlWtxC8vM6Y6e3h9ZTxBBkOWnWrm/Sa1HT+2i1xcXY3Hx2hmKDZP5RShPf8Xr9D+ivlrXNy57ktzyH8kyt+Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.101",
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
public static int ReduceSum(Vector256<int> accumulator)
|
||||
{
|
||||
// Add upper lane to lower lane.
|
||||
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper());
|
||||
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper());
|
||||
|
||||
// Add odd to even.
|
||||
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01));
|
||||
@@ -81,7 +81,7 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int EvenReduceSum(Vector256<int> accumulator)
|
||||
{
|
||||
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
|
||||
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
|
||||
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
|
||||
|
||||
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
|
||||
@@ -141,7 +141,7 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1 // tap2
|
||||
1, // tap2
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -189,29 +189,29 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
|
||||
private static unsafe uint CalculateSse(uint adler, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
uint s1 = adler & 0xFFFF;
|
||||
uint s2 = (adler >> 16) & 0xFFFF;
|
||||
var s1 = adler & 0xFFFF;
|
||||
var s2 = (adler >> 16) & 0xFFFF;
|
||||
|
||||
// Process the data in blocks.
|
||||
uint length = (uint)buffer.Length;
|
||||
uint blocks = length / BlockSize;
|
||||
var length = (uint)buffer.Length;
|
||||
var blocks = length / BlockSize;
|
||||
length -= blocks * BlockSize;
|
||||
|
||||
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
|
||||
{
|
||||
byte* localBufferPtr = bufferPtr;
|
||||
var localBufferPtr = bufferPtr;
|
||||
|
||||
// _mm_setr_epi8 on x86
|
||||
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
|
||||
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
|
||||
Vector128<byte> zero = Vector128<byte>.Zero;
|
||||
var tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
|
||||
var tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
|
||||
var zero = Vector128<byte>.Zero;
|
||||
var ones = Vector128.Create((short)1);
|
||||
|
||||
while (blocks > 0)
|
||||
{
|
||||
uint n = NMAX / BlockSize; /* The NMAX constraint. */
|
||||
var n = NMAX / BlockSize; /* The NMAX constraint. */
|
||||
if (n > blocks)
|
||||
{
|
||||
n = blocks;
|
||||
@@ -221,15 +221,15 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
|
||||
// Process n blocks of data. At most NMAX data bytes can be
|
||||
// processed before s2 must be reduced modulo BASE.
|
||||
Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
|
||||
Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
|
||||
Vector128<uint> v_s1 = Vector128<uint>.Zero;
|
||||
var v_ps = Vector128.CreateScalar(s1 * n);
|
||||
var v_s2 = Vector128.CreateScalar(s2);
|
||||
var v_s1 = Vector128<uint>.Zero;
|
||||
|
||||
do
|
||||
{
|
||||
// Load 32 input bytes.
|
||||
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr);
|
||||
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
|
||||
var bytes1 = Sse3.LoadDquVector128(localBufferPtr);
|
||||
var bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
|
||||
|
||||
// Add previous block byte sum to v_ps.
|
||||
v_ps = Sse2.Add(v_ps, v_s1);
|
||||
@@ -237,11 +237,11 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
// Horizontally add the bytes for s1, multiply-adds the
|
||||
// bytes by [ 32, 31, 30, ... ] for s2.
|
||||
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
|
||||
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
|
||||
var mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
|
||||
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
|
||||
|
||||
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
|
||||
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
|
||||
var mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
|
||||
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
|
||||
|
||||
localBufferPtr += BlockSize;
|
||||
@@ -281,15 +281,15 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
|
||||
public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
uint s1 = adler & 0xFFFF;
|
||||
uint s2 = (adler >> 16) & 0xFFFF;
|
||||
uint length = (uint)buffer.Length;
|
||||
var s1 = adler & 0xFFFF;
|
||||
var s2 = (adler >> 16) & 0xFFFF;
|
||||
var length = (uint)buffer.Length;
|
||||
|
||||
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
byte* localBufferPtr = bufferPtr;
|
||||
var localBufferPtr = bufferPtr;
|
||||
|
||||
Vector256<byte> zero = Vector256<byte>.Zero;
|
||||
var zero = Vector256<byte>.Zero;
|
||||
var dot3v = Vector256.Create((short)1);
|
||||
var dot2v = Vector256.Create(
|
||||
32,
|
||||
@@ -333,29 +333,29 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
|
||||
while (length >= 32)
|
||||
{
|
||||
int k = length < NMAX ? (int)length : (int)NMAX;
|
||||
var k = length < NMAX ? (int)length : (int)NMAX;
|
||||
k -= k % 32;
|
||||
length -= (uint)k;
|
||||
|
||||
Vector256<uint> vs10 = vs1;
|
||||
Vector256<uint> vs3 = Vector256<uint>.Zero;
|
||||
var vs10 = vs1;
|
||||
var vs3 = Vector256<uint>.Zero;
|
||||
|
||||
while (k >= 32)
|
||||
{
|
||||
// Load 32 input bytes.
|
||||
Vector256<byte> block = Avx.LoadVector256(localBufferPtr);
|
||||
var block = Avx.LoadVector256(localBufferPtr);
|
||||
|
||||
// Sum of abs diff, resulting in 2 x int32's
|
||||
Vector256<ushort> vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
|
||||
var vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
|
||||
|
||||
vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
|
||||
vs3 = Avx2.Add(vs3, vs10);
|
||||
|
||||
// sum 32 uint8s to 16 shorts.
|
||||
Vector256<short> vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
|
||||
var vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
|
||||
|
||||
// sum 16 shorts to 8 uint32s.
|
||||
Vector256<int> vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
|
||||
var vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
|
||||
|
||||
vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
|
||||
vs10 = vs1;
|
||||
@@ -434,14 +434,14 @@ internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/bl
|
||||
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
|
||||
private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
uint s1 = adler & 0xFFFF;
|
||||
uint s2 = (adler >> 16) & 0xFFFF;
|
||||
var s1 = adler & 0xFFFF;
|
||||
var s2 = (adler >> 16) & 0xFFFF;
|
||||
uint k;
|
||||
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
var localBufferPtr = bufferPtr;
|
||||
uint length = (uint)buffer.Length;
|
||||
var length = (uint)buffer.Length;
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
|
||||
@@ -12,39 +12,35 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
|
||||
where TEntry : IArchiveEntry
|
||||
where TVolume : IVolume
|
||||
{
|
||||
private readonly LazyReadOnlyCollection<TVolume> lazyVolumes;
|
||||
private readonly LazyReadOnlyCollection<TEntry> lazyEntries;
|
||||
private readonly LazyReadOnlyCollection<TVolume> _lazyVolumes;
|
||||
private readonly LazyReadOnlyCollection<TEntry> _lazyEntries;
|
||||
private bool _disposed;
|
||||
private readonly SourceStream? _sourceStream;
|
||||
|
||||
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionBegin;
|
||||
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionEnd;
|
||||
|
||||
public event EventHandler<CompressedBytesReadEventArgs>? CompressedBytesRead;
|
||||
public event EventHandler<FilePartExtractionBeginEventArgs>? FilePartExtractionBegin;
|
||||
|
||||
protected ReaderOptions ReaderOptions { get; }
|
||||
|
||||
private bool disposed;
|
||||
protected SourceStream SrcStream;
|
||||
|
||||
internal AbstractArchive(ArchiveType type, SourceStream srcStream)
|
||||
internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
|
||||
{
|
||||
Type = type;
|
||||
ReaderOptions = srcStream.ReaderOptions;
|
||||
SrcStream = srcStream;
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(SrcStream));
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
|
||||
ReaderOptions = sourceStream.ReaderOptions;
|
||||
_sourceStream = sourceStream;
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(_sourceStream));
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal AbstractArchive(ArchiveType type)
|
||||
{
|
||||
Type = type;
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
ReaderOptions = new();
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
|
||||
public ArchiveType Type { get; }
|
||||
|
||||
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) =>
|
||||
@@ -57,7 +53,7 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
|
||||
{
|
||||
if (!stream.CanSeek || !stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException("Archive streams must be Readable and Seekable");
|
||||
throw new ArchiveException("Archive streams must be Readable and Seekable");
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
@@ -65,12 +61,12 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
public virtual ICollection<TEntry> Entries => lazyEntries;
|
||||
public virtual ICollection<TEntry> Entries => _lazyEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
public ICollection<TVolume> Volumes => lazyVolumes;
|
||||
public ICollection<TVolume> Volumes => _lazyVolumes;
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files compressed in the archive.
|
||||
@@ -84,29 +80,29 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
|
||||
public virtual long TotalUncompressSize =>
|
||||
Entries.Aggregate(0L, (total, cf) => total + cf.Size);
|
||||
|
||||
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream srcStream);
|
||||
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream sourceStream);
|
||||
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
|
||||
|
||||
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
|
||||
|
||||
IEnumerable<IVolume> IArchive.Volumes => lazyVolumes.Cast<IVolume>();
|
||||
IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
if (!_disposed)
|
||||
{
|
||||
lazyVolumes.ForEach(v => v.Dispose());
|
||||
lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
|
||||
SrcStream?.Dispose();
|
||||
_lazyVolumes.ForEach(v => v.Dispose());
|
||||
_lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
|
||||
_sourceStream?.Dispose();
|
||||
|
||||
disposed = true;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IArchiveExtractionListener.EnsureEntriesLoaded()
|
||||
{
|
||||
lazyEntries.EnsureFullyLoaded();
|
||||
lazyVolumes.EnsureFullyLoaded();
|
||||
_lazyEntries.EnsureFullyLoaded();
|
||||
_lazyVolumes.EnsureFullyLoaded();
|
||||
}
|
||||
|
||||
void IExtractionListener.FireCompressedBytesRead(
|
||||
|
||||
@@ -31,18 +31,18 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<TEntry> newEntries = new List<TEntry>();
|
||||
private readonly List<TEntry> removedEntries = new List<TEntry>();
|
||||
private readonly List<TEntry> newEntries = new();
|
||||
private readonly List<TEntry> removedEntries = new();
|
||||
|
||||
private readonly List<TEntry> modifiedEntries = new List<TEntry>();
|
||||
private readonly List<TEntry> modifiedEntries = new();
|
||||
private bool hasModifications;
|
||||
private bool pauseRebuilding;
|
||||
|
||||
internal AbstractWritableArchive(ArchiveType type)
|
||||
: base(type) { }
|
||||
|
||||
internal AbstractWritableArchive(ArchiveType type, SourceStream srcStream)
|
||||
: base(type, srcStream) { }
|
||||
internal AbstractWritableArchive(ArchiveType type, SourceStream sourceStream)
|
||||
: base(type, sourceStream) { }
|
||||
|
||||
public override ICollection<TEntry> Entries
|
||||
{
|
||||
@@ -120,6 +120,10 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
{
|
||||
foreach (var path in Entries.Select(x => x.Key))
|
||||
{
|
||||
if (path is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var p = path.Replace('/', '\\');
|
||||
if (p.Length > 0 && p[0] == '\\')
|
||||
{
|
||||
@@ -147,7 +151,7 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
{
|
||||
if (!source.CanRead || !source.CanSeek)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
throw new ArchiveException(
|
||||
"Streams must be readable and seekable to use the Writing Archive API"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,14 +20,14 @@ 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);
|
||||
}
|
||||
|
||||
public static IWritableArchive Create(ArchiveType type)
|
||||
{
|
||||
var factory = Factory.Factories
|
||||
.OfType<IWriteableArchiveFactory>()
|
||||
var factory = Factory
|
||||
.Factories.OfType<IWriteableArchiveFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == type);
|
||||
|
||||
if (factory != null)
|
||||
@@ -122,10 +123,7 @@ public static class ArchiveFactory
|
||||
)
|
||||
{
|
||||
using var archive = Open(sourceArchive);
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
entry.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
archive.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
|
||||
private static T FindFactory<T>(FileInfo finfo)
|
||||
@@ -168,14 +166,22 @@ 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));
|
||||
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));
|
||||
@@ -189,9 +195,10 @@ public static class ArchiveFactory
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
var isArchive = factory.IsArchive(stream);
|
||||
stream.Position = startPosition;
|
||||
|
||||
if (factory.IsArchive(stream, null))
|
||||
if (isArchive)
|
||||
{
|
||||
type = factory.KnownArchiveType;
|
||||
return true;
|
||||
@@ -239,4 +246,6 @@ public static class ArchiveFactory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ internal abstract class ArchiveVolumeFactory
|
||||
FileInfo? item = null;
|
||||
|
||||
//split 001, 002 ...
|
||||
Match m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase);
|
||||
var m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase);
|
||||
if (m.Success)
|
||||
item = new FileInfo(
|
||||
Path.Combine(
|
||||
|
||||
30
src/SharpCompress/Archives/AutoArchiveFactory.cs
Normal file
30
src/SharpCompress/Archives/AutoArchiveFactory.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
class AutoArchiveFactory : IArchiveFactory
|
||||
{
|
||||
public string Name => nameof(AutoArchiveFactory);
|
||||
|
||||
public ArchiveType? KnownArchiveType => null;
|
||||
|
||||
public IEnumerable<string> GetSupportedExtensions() => 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();
|
||||
|
||||
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
|
||||
ArchiveFactory.Open(stream, readerOptions);
|
||||
|
||||
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
|
||||
ArchiveFactory.Open(fileInfo, readerOptions);
|
||||
}
|
||||
@@ -89,26 +89,30 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
|
||||
return new GZipArchive(
|
||||
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
);
|
||||
}
|
||||
|
||||
public static GZipArchive Create() => new GZipArchive();
|
||||
public static GZipArchive Create() => new();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a SourceStream able to handle FileInfo and Streams.
|
||||
/// </summary>
|
||||
/// <param name="srcStream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal GZipArchive(SourceStream srcStream)
|
||||
: base(ArchiveType.Tar, srcStream) { }
|
||||
/// <param name="sourceStream"></param>
|
||||
private GZipArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.GZip, sourceStream) { }
|
||||
|
||||
protected override IEnumerable<GZipVolume> LoadVolumes(SourceStream srcStream)
|
||||
protected override IEnumerable<GZipVolume> LoadVolumes(SourceStream sourceStream)
|
||||
{
|
||||
srcStream.LoadAllParts();
|
||||
var idx = 0;
|
||||
return srcStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, idx++));
|
||||
sourceStream.LoadAllParts();
|
||||
return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0));
|
||||
}
|
||||
|
||||
public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath));
|
||||
@@ -164,7 +168,7 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
{
|
||||
if (Entries.Any())
|
||||
{
|
||||
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
|
||||
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
}
|
||||
@@ -178,13 +182,17 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
{
|
||||
if (Entries.Count > 1)
|
||||
{
|
||||
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
|
||||
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime);
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace SharpCompress.Archives.GZip;
|
||||
|
||||
public class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
{
|
||||
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart part)
|
||||
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part)
|
||||
: base(part) => Archive = archive;
|
||||
|
||||
public virtual Stream OpenEntryStream()
|
||||
@@ -17,7 +17,7 @@ public class GZipArchiveEntry : GZipEntry, IArchiveEntry
|
||||
{
|
||||
part.GetRawStream().Position = part.EntryStartPosition;
|
||||
}
|
||||
return Parts.Single().GetCompressedStream();
|
||||
return Parts.Single().GetCompressedStream().NotNull();
|
||||
}
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -32,7 +30,7 @@ internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArch
|
||||
|
||||
public override long Crc => 0;
|
||||
|
||||
public override string Key { get; }
|
||||
public override string? Key { get; }
|
||||
|
||||
public override long CompressedSize => 0;
|
||||
|
||||
@@ -60,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()
|
||||
|
||||
@@ -17,15 +17,11 @@ public static class IArchiveEntryExtensions
|
||||
streamListener.EnsureEntriesLoaded();
|
||||
streamListener.FireEntryExtractionBegin(archiveEntry);
|
||||
streamListener.FireFilePartExtractionBegin(
|
||||
archiveEntry.Key,
|
||||
archiveEntry.Key ?? "Key",
|
||||
archiveEntry.Size,
|
||||
archiveEntry.CompressedSize
|
||||
);
|
||||
var entryStream = archiveEntry.OpenEntryStream();
|
||||
if (entryStream is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (entryStream)
|
||||
{
|
||||
using Stream s = new ListeningStream(streamListener, entryStream);
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
@@ -19,10 +19,8 @@ public static class IArchiveExtensions
|
||||
ExtractionOptions? options = null
|
||||
)
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
entry.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
using var reader = archive.ExtractAllEntries();
|
||||
reader.WriteAllToDirectory(destinationDirectory, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,14 +53,26 @@ public static class IArchiveExtensions
|
||||
var entry = entries.Entry;
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
var dirPath = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
|
||||
if (
|
||||
Path.GetDirectoryName(dirPath + "/") is { } emptyDirectory
|
||||
&& seenDirectories.Add(dirPath)
|
||||
)
|
||||
{
|
||||
Directory.CreateDirectory(emptyDirectory);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create each directory
|
||||
var path = Path.Combine(destination, entry.Key);
|
||||
if (Path.GetDirectoryName(path) is { } directory && seenDirectories.Add(path))
|
||||
// Create each directory if not already created
|
||||
var path = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
|
||||
if (Path.GetDirectoryName(path) is { } directory)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
if (!Directory.Exists(directory) && !seenDirectories.Contains(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
seenDirectories.Add(directory);
|
||||
}
|
||||
}
|
||||
|
||||
// Write file
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Common.Rar;
|
||||
@@ -13,7 +14,7 @@ namespace SharpCompress.Archives.Rar;
|
||||
/// </summary>
|
||||
internal class FileInfoRarArchiveVolume : RarVolume
|
||||
{
|
||||
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index = 0)
|
||||
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
|
||||
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -13,45 +14,69 @@ namespace SharpCompress.Archives.Rar;
|
||||
|
||||
public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
|
||||
{
|
||||
private bool _disposed;
|
||||
internal Lazy<IRarUnpack> UnpackV2017 { get; } =
|
||||
new Lazy<IRarUnpack>(() => new Compressors.Rar.UnpackV2017.Unpack());
|
||||
internal Lazy<IRarUnpack> UnpackV1 { get; } =
|
||||
new Lazy<IRarUnpack>(() => new Compressors.Rar.UnpackV1.Unpack());
|
||||
new(() => new Compressors.Rar.UnpackV2017.Unpack());
|
||||
internal Lazy<IRarUnpack> UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack());
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a SourceStream able to handle FileInfo and Streams.
|
||||
/// </summary>
|
||||
/// <param name="srcStream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal RarArchive(SourceStream srcStream)
|
||||
: base(ArchiveType.Rar, srcStream) { }
|
||||
/// <param name="sourceStream"></param>
|
||||
private RarArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.Rar, sourceStream) { }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (UnpackV1.IsValueCreated && UnpackV1.Value is IDisposable unpackV1)
|
||||
{
|
||||
unpackV1.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<RarArchiveEntry> LoadEntries(IEnumerable<RarVolume> volumes) =>
|
||||
RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions);
|
||||
|
||||
protected override IEnumerable<RarVolume> LoadVolumes(SourceStream srcStream)
|
||||
protected override IEnumerable<RarVolume> LoadVolumes(SourceStream sourceStream)
|
||||
{
|
||||
SrcStream.LoadAllParts(); //request all streams
|
||||
var streams = SrcStream.Streams.ToArray();
|
||||
var idx = 0;
|
||||
sourceStream.LoadAllParts(); //request all streams
|
||||
var streams = sourceStream.Streams.ToArray();
|
||||
var i = 0;
|
||||
if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split
|
||||
{
|
||||
SrcStream.IsVolumes = true;
|
||||
sourceStream.IsVolumes = true;
|
||||
streams[1].Position = 0;
|
||||
SrcStream.Position = 0;
|
||||
sourceStream.Position = 0;
|
||||
|
||||
return srcStream.Streams.Select(
|
||||
a => new StreamRarArchiveVolume(a, ReaderOptions, idx++)
|
||||
);
|
||||
}
|
||||
else //split mode or single file
|
||||
{
|
||||
return new StreamRarArchiveVolume(SrcStream, ReaderOptions, idx++).AsEnumerable();
|
||||
return sourceStream.Streams.Select(a => new StreamRarArchiveVolume(
|
||||
a,
|
||||
ReaderOptions,
|
||||
i++
|
||||
));
|
||||
}
|
||||
|
||||
//split mode or single file
|
||||
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
|
||||
}
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
{
|
||||
if (this.IsMultipartVolume())
|
||||
{
|
||||
var streams = Volumes.Select(volume =>
|
||||
{
|
||||
volume.Stream.Position = 0;
|
||||
return volume.Stream;
|
||||
});
|
||||
return RarReader.Open(streams, ReaderOptions);
|
||||
}
|
||||
|
||||
var stream = Volumes.First().Stream;
|
||||
stream.Position = 0;
|
||||
return RarReader.Open(stream, ReaderOptions);
|
||||
@@ -106,7 +131,13 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
|
||||
public static RarArchive Open(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new RarArchive(new SourceStream(stream, i => null, options ?? new ReaderOptions()));
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -40,7 +41,10 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
get
|
||||
{
|
||||
CheckIncomplete();
|
||||
return parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc;
|
||||
return BitConverter.ToUInt32(
|
||||
parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc.NotNull(),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ internal static class RarArchiveVolumeFactory
|
||||
FileInfo? item = null;
|
||||
|
||||
//new style rar - ..part1 | /part01 | part001 ....
|
||||
Match m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase);
|
||||
var m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase);
|
||||
if (m.Success)
|
||||
item = new FileInfo(
|
||||
Path.Combine(
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace SharpCompress.Archives.Rar;
|
||||
|
||||
internal class SeekableFilePart : RarFilePart
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private readonly string? password;
|
||||
private readonly Stream _stream;
|
||||
private readonly string? _password;
|
||||
|
||||
internal SeekableFilePart(
|
||||
MarkHeader mh,
|
||||
@@ -18,18 +18,27 @@ internal class SeekableFilePart : RarFilePart
|
||||
)
|
||||
: base(mh, fh, index)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.password = password;
|
||||
_stream = stream;
|
||||
_password = password;
|
||||
}
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
stream.Position = FileHeader.DataStartPosition;
|
||||
_stream.Position = FileHeader.DataStartPosition;
|
||||
|
||||
if (FileHeader.R4Salt != null)
|
||||
{
|
||||
return new RarCryptoWrapper(stream, password!, FileHeader.R4Salt);
|
||||
var cryptKey = new CryptKey3(_password!);
|
||||
return new RarCryptoWrapper(_stream, FileHeader.R4Salt, cryptKey);
|
||||
}
|
||||
return stream;
|
||||
|
||||
if (FileHeader.Rar5CryptoInfo != null)
|
||||
{
|
||||
var cryptKey = new CryptKey5(_password!, FileHeader.Rar5CryptoInfo);
|
||||
return new RarCryptoWrapper(_stream, FileHeader.Rar5CryptoInfo.Salt, cryptKey);
|
||||
}
|
||||
|
||||
return _stream;
|
||||
}
|
||||
|
||||
internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace SharpCompress.Archives.Rar;
|
||||
|
||||
internal class StreamRarArchiveVolume : RarVolume
|
||||
{
|
||||
internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index = 0)
|
||||
internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index)
|
||||
: base(StreamingMode.Seekable, stream, options, index) { }
|
||||
|
||||
internal override IEnumerable<RarFilePart> ReadFileParts() => GetVolumeFileParts();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -14,14 +12,14 @@ namespace SharpCompress.Archives.SevenZip;
|
||||
|
||||
public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVolume>
|
||||
{
|
||||
private ArchiveDatabase database;
|
||||
private ArchiveDatabase? _database;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor expects a filepath to an existing file.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static SevenZipArchive Open(string filePath, ReaderOptions readerOptions = null)
|
||||
public static SevenZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
filePath.CheckNotNullOrEmpty("filePath");
|
||||
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
@@ -32,7 +30,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions readerOptions = null)
|
||||
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.CheckNotNull("fileInfo");
|
||||
return new SevenZipArchive(
|
||||
@@ -51,7 +49,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
/// <param name="readerOptions"></param>
|
||||
public static SevenZipArchive Open(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
ReaderOptions readerOptions = null
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.CheckNotNull(nameof(fileInfos));
|
||||
@@ -72,7 +70,7 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
/// <param name="readerOptions"></param>
|
||||
public static SevenZipArchive Open(
|
||||
IEnumerable<Stream> streams,
|
||||
ReaderOptions readerOptions = null
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.CheckNotNull(nameof(streams));
|
||||
@@ -91,27 +89,31 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static SevenZipArchive Open(Stream stream, ReaderOptions readerOptions = null)
|
||||
public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull("stream");
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a SourceStream able to handle FileInfo and Streams.
|
||||
/// </summary>
|
||||
/// <param name="srcStream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal SevenZipArchive(SourceStream srcStream)
|
||||
: base(ArchiveType.SevenZip, srcStream) { }
|
||||
/// <param name="sourceStream"></param>
|
||||
private SevenZipArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.SevenZip, sourceStream) { }
|
||||
|
||||
protected override IEnumerable<SevenZipVolume> LoadVolumes(SourceStream srcStream)
|
||||
protected override IEnumerable<SevenZipVolume> LoadVolumes(SourceStream sourceStream)
|
||||
{
|
||||
SrcStream.LoadAllParts(); //request all streams
|
||||
var idx = 0;
|
||||
return new SevenZipVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported
|
||||
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
|
||||
return new SevenZipVolume(sourceStream, ReaderOptions, 0).AsEnumerable(); //simple single volume or split, multivolume not supported
|
||||
}
|
||||
|
||||
public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath));
|
||||
@@ -135,13 +137,17 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
{
|
||||
var stream = volumes.Single().Stream;
|
||||
LoadFactory(stream);
|
||||
var entries = new SevenZipArchiveEntry[database._files.Count];
|
||||
for (var i = 0; i < database._files.Count; i++)
|
||||
if (_database is null)
|
||||
{
|
||||
var file = database._files[i];
|
||||
return Enumerable.Empty<SevenZipArchiveEntry>();
|
||||
}
|
||||
var entries = new SevenZipArchiveEntry[_database._files.Count];
|
||||
for (var i = 0; i < _database._files.Count; i++)
|
||||
{
|
||||
var file = _database._files[i];
|
||||
entries[i] = new SevenZipArchiveEntry(
|
||||
this,
|
||||
new SevenZipFilePart(stream, database, i, file, ReaderOptions.ArchiveEncoding)
|
||||
new SevenZipFilePart(stream, _database, i, file, ReaderOptions.ArchiveEncoding)
|
||||
);
|
||||
}
|
||||
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
|
||||
@@ -159,12 +165,12 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
|
||||
private void LoadFactory(Stream stream)
|
||||
{
|
||||
if (database is null)
|
||||
if (_database is null)
|
||||
{
|
||||
stream.Position = 0;
|
||||
var reader = new ArchiveReader();
|
||||
reader.Open(stream);
|
||||
database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password));
|
||||
reader.Open(stream, lookForHeader: ReaderOptions.LookForHeader);
|
||||
_database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,46 +186,43 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> SIGNATURE =>
|
||||
private static ReadOnlySpan<byte> Signature =>
|
||||
new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C };
|
||||
|
||||
private static bool SignatureMatch(Stream stream)
|
||||
{
|
||||
var reader = new BinaryReader(stream);
|
||||
ReadOnlySpan<byte> signatureBytes = reader.ReadBytes(6);
|
||||
return signatureBytes.SequenceEqual(SIGNATURE);
|
||||
return signatureBytes.SequenceEqual(Signature);
|
||||
}
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction() =>
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
var i = Entries.Count;
|
||||
return database._packSizes.Aggregate(0L, (total, packSize) => total + packSize);
|
||||
}
|
||||
}
|
||||
public override long TotalSize =>
|
||||
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;
|
||||
|
||||
private sealed class SevenZipReader : AbstractReader<SevenZipEntry, SevenZipVolume>
|
||||
{
|
||||
private readonly SevenZipArchive archive;
|
||||
private CFolder currentFolder;
|
||||
private Stream currentStream;
|
||||
private CFileItem currentItem;
|
||||
private readonly SevenZipArchive _archive;
|
||||
private CFolder? _currentFolder;
|
||||
private Stream? _currentStream;
|
||||
private CFileItem? _currentItem;
|
||||
|
||||
internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive)
|
||||
: base(readerOptions, ArchiveType.SevenZip) => this.archive = archive;
|
||||
: base(readerOptions, ArchiveType.SevenZip) => this._archive = archive;
|
||||
|
||||
public override SevenZipVolume Volume => archive.Volumes.Single();
|
||||
public override SevenZipVolume Volume => _archive.Volumes.Single();
|
||||
|
||||
protected override IEnumerable<SevenZipEntry> GetEntries(Stream stream)
|
||||
{
|
||||
var entries = archive.Entries.ToList();
|
||||
var entries = _archive.Entries.ToList();
|
||||
stream.Position = 0;
|
||||
foreach (var dir in entries.Where(x => x.IsDirectory))
|
||||
{
|
||||
@@ -229,37 +232,42 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder)
|
||||
)
|
||||
{
|
||||
currentFolder = group.Key;
|
||||
_currentFolder = group.Key;
|
||||
if (group.Key is null)
|
||||
{
|
||||
currentStream = Stream.Null;
|
||||
_currentStream = Stream.Null;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStream = archive.database.GetFolderStream(
|
||||
_currentStream = _archive._database?.GetFolderStream(
|
||||
stream,
|
||||
currentFolder,
|
||||
_currentFolder,
|
||||
new PasswordProvider(Options.Password)
|
||||
);
|
||||
}
|
||||
foreach (var entry in group)
|
||||
{
|
||||
currentItem = entry.FilePart.Header;
|
||||
_currentItem = entry.FilePart.Header;
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override EntryStream GetEntryStream() =>
|
||||
CreateEntryStream(new ReadOnlySubStream(currentStream, currentItem.Size));
|
||||
CreateEntryStream(
|
||||
new ReadOnlySubStream(
|
||||
_currentStream.NotNull("currentStream is not null"),
|
||||
_currentItem?.Size ?? 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private class PasswordProvider : IPasswordProvider
|
||||
{
|
||||
private readonly string _password;
|
||||
private readonly string? _password;
|
||||
|
||||
public PasswordProvider(string password) => _password = password;
|
||||
public PasswordProvider(string? password) => _password = password;
|
||||
|
||||
public string CryptoGetTextPassword() => _password;
|
||||
public string? CryptoGetTextPassword() => _password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,12 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(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())
|
||||
);
|
||||
@@ -114,7 +120,7 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
var tarHeader = new TarHeader(new ArchiveEncoding());
|
||||
var readSucceeded = tarHeader.Read(new BinaryReader(stream));
|
||||
var isEmptyArchive =
|
||||
tarHeader.Name.Length == 0
|
||||
tarHeader.Name?.Length == 0
|
||||
&& tarHeader.Size == 0
|
||||
&& Enum.IsDefined(typeof(EntryType), tarHeader.EntryType);
|
||||
return readSucceeded || isEmptyArchive;
|
||||
@@ -123,22 +129,20 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream srcStream)
|
||||
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
|
||||
{
|
||||
SrcStream.LoadAllParts(); //request all streams
|
||||
var idx = 0;
|
||||
return new TarVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported
|
||||
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
|
||||
return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); //simple single volume or split, multivolume not supported
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a SourceStream able to handle FileInfo and Streams.
|
||||
/// </summary>
|
||||
/// <param name="srcStream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal TarArchive(SourceStream srcStream)
|
||||
: base(ArchiveType.Tar, srcStream) { }
|
||||
/// <param name="sourceStream"></param>
|
||||
private TarArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.Tar, sourceStream) { }
|
||||
|
||||
internal TarArchive()
|
||||
private TarArchive()
|
||||
: base(ArchiveType.Tar) { }
|
||||
|
||||
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
|
||||
@@ -192,10 +196,14 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IncompleteArchiveException("Failed to read TAR header");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TarArchive Create() => new TarArchive();
|
||||
public static TarArchive Create() => new();
|
||||
|
||||
protected override TarArchiveEntry CreateEntryInternal(
|
||||
string filePath,
|
||||
@@ -225,7 +233,12 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime, entry.Size);
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace SharpCompress.Archives.Tar;
|
||||
|
||||
public class TarArchiveEntry : TarEntry, IArchiveEntry
|
||||
{
|
||||
internal TarArchiveEntry(TarArchive archive, TarFilePart part, CompressionType compressionType)
|
||||
internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType compressionType)
|
||||
: base(part, compressionType) => Archive = archive;
|
||||
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream();
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -60,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()
|
||||
|
||||
@@ -16,10 +16,7 @@ namespace SharpCompress.Archives.Zip;
|
||||
|
||||
public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
{
|
||||
#nullable disable
|
||||
private readonly SeekableZipHeaderFactory headerFactory;
|
||||
|
||||
#nullable enable
|
||||
private readonly SeekableZipHeaderFactory? headerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression level applied to files added to the archive,
|
||||
@@ -30,13 +27,13 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
/// <summary>
|
||||
/// Constructor with a SourceStream able to handle FileInfo and Streams.
|
||||
/// </summary>
|
||||
/// <param name="srcStream"></param>
|
||||
/// <param name="sourceStream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal ZipArchive(SourceStream srcStream)
|
||||
: base(ArchiveType.Zip, srcStream) =>
|
||||
internal ZipArchive(SourceStream sourceStream)
|
||||
: base(ArchiveType.Zip, sourceStream) =>
|
||||
headerFactory = new SeekableZipHeaderFactory(
|
||||
srcStream.ReaderOptions.Password,
|
||||
srcStream.ReaderOptions.ArchiveEncoding
|
||||
sourceStream.ReaderOptions.Password,
|
||||
sourceStream.ReaderOptions.ArchiveEncoding
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
@@ -114,29 +111,51 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(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);
|
||||
@@ -156,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);
|
||||
@@ -189,21 +217,21 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<ZipVolume> LoadVolumes(SourceStream srcStream)
|
||||
protected override IEnumerable<ZipVolume> LoadVolumes(SourceStream stream)
|
||||
{
|
||||
SrcStream.LoadAllParts(); //request all streams
|
||||
SrcStream.Position = 0;
|
||||
stream.LoadAllParts(); //request all streams
|
||||
stream.Position = 0;
|
||||
|
||||
var streams = SrcStream.Streams.ToList();
|
||||
var streams = stream.Streams.ToList();
|
||||
var idx = 0;
|
||||
if (streams.Count > 1) //test part 2 - true = multipart not split
|
||||
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)
|
||||
{
|
||||
SrcStream.IsVolumes = true;
|
||||
stream.IsVolumes = true;
|
||||
|
||||
var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end
|
||||
streams.RemoveAt(0);
|
||||
@@ -215,7 +243,7 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
}
|
||||
|
||||
//split mode or single file
|
||||
return new ZipVolume(SrcStream, ReaderOptions, idx++).AsEnumerable();
|
||||
return new ZipVolume(stream, ReaderOptions, idx++).AsEnumerable();
|
||||
}
|
||||
|
||||
internal ZipArchive()
|
||||
@@ -224,14 +252,13 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
|
||||
{
|
||||
var vols = volumes.ToArray();
|
||||
foreach (var h in headerFactory.ReadSeekableHeader(vols.Last().Stream))
|
||||
foreach (var h in headerFactory.NotNull().ReadSeekableHeader(vols.Last().Stream))
|
||||
{
|
||||
if (h != null)
|
||||
{
|
||||
switch (h.ZipHeaderType)
|
||||
{
|
||||
case ZipHeaderType.DirectoryEntry:
|
||||
|
||||
{
|
||||
var deh = (DirectoryEntryHeader)h;
|
||||
Stream s;
|
||||
@@ -254,14 +281,14 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
|
||||
yield return new ZipArchiveEntry(
|
||||
this,
|
||||
new SeekableZipFilePart(headerFactory, deh, s)
|
||||
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ZipHeaderType.DirectoryEnd:
|
||||
{
|
||||
var bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
|
||||
volumes.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
vols.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +309,11 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime);
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,12 +325,12 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
bool closeStream
|
||||
) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
|
||||
public static ZipArchive Create() => new ZipArchive();
|
||||
public static ZipArchive Create() => new();
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
{
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
((IStreamStack)stream).StackSeek(0);
|
||||
return ZipReader.Open(stream, ReaderOptions, Entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry
|
||||
internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)
|
||||
: base(part) => Archive = archive;
|
||||
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream();
|
||||
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
@@ -18,6 +18,4 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry
|
||||
public bool IsComplete => true;
|
||||
|
||||
#endregion
|
||||
|
||||
public string? Comment => ((SeekableZipFilePart)Parts.Single()).Comment;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ internal static class ZipArchiveVolumeFactory
|
||||
|
||||
//load files with zip/zipx first. Swapped to end once loaded in ZipArchive
|
||||
//new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc
|
||||
Match m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase);
|
||||
var m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase);
|
||||
if (m.Success)
|
||||
item = new FileInfo(
|
||||
Path.Combine(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: CLSCompliant(true)]
|
||||
[assembly: InternalsVisibleTo(
|
||||
"SharpCompress.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100158bebf1433f76dffc356733c138babea7a47536c65ed8009b16372c6f4edbb20554db74a62687f56b97c20a6ce8c4b123280279e33c894e7b3aa93ab3c573656fde4db576cfe07dba09619ead26375b25d2c4a8e43f7be257d712b0dd2eb546f67adb09281338618a58ac834fc038dd7e2740a7ab3591826252e4f4516306dc"
|
||||
)]
|
||||
|
||||
60
src/SharpCompress/Common/Arc/ArcEntry.cs
Normal file
60
src/SharpCompress/Common/Arc/ArcEntry.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.GZip;
|
||||
using SharpCompress.Common.Tar;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
{
|
||||
public class ArcEntry : Entry
|
||||
{
|
||||
private readonly ArcFilePart? _filePart;
|
||||
|
||||
internal ArcEntry(ArcFilePart? filePart)
|
||||
{
|
||||
_filePart = filePart;
|
||||
}
|
||||
|
||||
public override long Crc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePart == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _filePart.Header.Crc16;
|
||||
}
|
||||
}
|
||||
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
|
||||
|
||||
public override CompressionType CompressionType =>
|
||||
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;
|
||||
|
||||
public override long Size => throw new NotImplementedException();
|
||||
|
||||
public override DateTime? LastModifiedTime => null;
|
||||
|
||||
public override DateTime? CreatedTime => null;
|
||||
|
||||
public override DateTime? LastAccessedTime => null;
|
||||
|
||||
public override DateTime? ArchivedTime => null;
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
}
|
||||
}
|
||||
76
src/SharpCompress/Common/Arc/ArcEntryHeader.cs
Normal file
76
src/SharpCompress/Common/Arc/ArcEntryHeader.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
{
|
||||
public class ArcEntryHeader
|
||||
{
|
||||
public ArchiveEncoding ArchiveEncoding { get; }
|
||||
public CompressionType CompressionMethod { get; private set; }
|
||||
public string? Name { get; private set; }
|
||||
public long CompressedSize { get; private set; }
|
||||
public DateTime DateTime { get; private set; }
|
||||
public int Crc16 { get; private set; }
|
||||
public long OriginalSize { get; private set; }
|
||||
public long DataStartPosition { get; private set; }
|
||||
|
||||
public ArcEntryHeader(ArchiveEncoding archiveEncoding)
|
||||
{
|
||||
this.ArchiveEncoding = archiveEncoding;
|
||||
}
|
||||
|
||||
public ArcEntryHeader? ReadHeader(Stream stream)
|
||||
{
|
||||
byte[] headerBytes = new byte[29];
|
||||
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DataStartPosition = stream.Position;
|
||||
return LoadFrom(headerBytes);
|
||||
}
|
||||
|
||||
public ArcEntryHeader LoadFrom(byte[] headerBytes)
|
||||
{
|
||||
CompressionMethod = GetCompressionType(headerBytes[1]);
|
||||
|
||||
// Read name
|
||||
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
|
||||
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);
|
||||
|
||||
int offset = 15;
|
||||
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
offset += 4;
|
||||
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
|
||||
DateTime = ConvertToDateTime(rawDateTime);
|
||||
offset += 4;
|
||||
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
|
||||
offset += 2;
|
||||
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
private CompressionType GetCompressionType(byte value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
1 or 2 => CompressionType.None,
|
||||
3 => CompressionType.RLE90,
|
||||
4 => CompressionType.Squeezed,
|
||||
5 or 6 or 7 or 8 => CompressionType.Crunched,
|
||||
9 => CompressionType.Squashed,
|
||||
10 => CompressionType.Crushed,
|
||||
11 => CompressionType.Distilled,
|
||||
_ => CompressionType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static DateTime ConvertToDateTime(long rawDateTime)
|
||||
{
|
||||
// Convert Unix timestamp to DateTime (UTC)
|
||||
return DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/SharpCompress/Common/Arc/ArcFilePart.cs
Normal file
75
src/SharpCompress/Common/Arc/ArcFilePart.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.GZip;
|
||||
using SharpCompress.Common.Tar;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors.Lzw;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.Compressors.Squeezed;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
{
|
||||
public class ArcFilePart : FilePart
|
||||
{
|
||||
private readonly Stream? _stream;
|
||||
|
||||
internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
|
||||
: base(localArcHeader.ArchiveEncoding)
|
||||
{
|
||||
_stream = seekableStream;
|
||||
Header = localArcHeader;
|
||||
}
|
||||
|
||||
internal ArcEntryHeader Header { get; set; }
|
||||
|
||||
internal override string? FilePartName => Header.Name;
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
Stream compressedStream;
|
||||
switch (Header.CompressionMethod)
|
||||
{
|
||||
case CompressionType.None:
|
||||
compressedStream = new ReadOnlySubStream(
|
||||
_stream,
|
||||
Header.DataStartPosition,
|
||||
Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.RLE90:
|
||||
compressedStream = new RunLength90Stream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.Squeezed:
|
||||
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
|
||||
break;
|
||||
case CompressionType.Crunched:
|
||||
compressedStream = new ArcLzwStream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize,
|
||||
true
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"CompressionMethod: " + Header.CompressionMethod
|
||||
);
|
||||
}
|
||||
return compressedStream;
|
||||
}
|
||||
return _stream.NotNull();
|
||||
}
|
||||
|
||||
internal override Stream? GetRawStream() => _stream;
|
||||
}
|
||||
}
|
||||
16
src/SharpCompress/Common/Arc/ArcVolume.cs
Normal file
16
src/SharpCompress/Common/Arc/ArcVolume.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
{
|
||||
public class ArcVolume : Volume
|
||||
{
|
||||
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
: base(stream, readerOptions, index) { }
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ public class ArchiveEncoding
|
||||
/// <summary>
|
||||
/// Default encoding to use when archive format doesn't specify one.
|
||||
/// </summary>
|
||||
public Encoding Default { get; set; }
|
||||
public Encoding? Default { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ArchiveEncoding used by encryption schemes which don't comply with RFC 2898.
|
||||
/// </summary>
|
||||
public Encoding Password { get; set; }
|
||||
public Encoding? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this encoding when you want to force it for all encoding operations.
|
||||
@@ -50,6 +50,8 @@ public class ArchiveEncoding
|
||||
|
||||
public Encoding GetEncoding() => Forced ?? Default ?? Encoding.UTF8;
|
||||
|
||||
public Encoding GetPasswordEncoding() => Password ?? Encoding.UTF8;
|
||||
|
||||
public Func<byte[], int, int, string> GetDecoder() =>
|
||||
CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count));
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class ArchiveException : Exception
|
||||
{
|
||||
public ArchiveException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
@@ -6,5 +6,6 @@ public enum ArchiveType
|
||||
Zip,
|
||||
Tar,
|
||||
SevenZip,
|
||||
GZip
|
||||
GZip,
|
||||
Arc,
|
||||
}
|
||||
|
||||
@@ -14,5 +14,19 @@ public enum CompressionType
|
||||
LZip,
|
||||
Xz,
|
||||
Unknown,
|
||||
Deflate64
|
||||
Deflate64,
|
||||
Shrink,
|
||||
Lzw,
|
||||
Reduce1,
|
||||
Reduce2,
|
||||
Reduce3,
|
||||
Reduce4,
|
||||
Explode,
|
||||
Squeezed,
|
||||
RLE90,
|
||||
Crunched,
|
||||
Squashed,
|
||||
Crushed,
|
||||
Distilled,
|
||||
ZStandard,
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class CryptographicException : Exception
|
||||
{
|
||||
public CryptographicException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public abstract class Entry : IEntry
|
||||
/// <summary>
|
||||
/// The string key of the file internal to the Archive.
|
||||
/// </summary>
|
||||
public abstract string Key { get; }
|
||||
public abstract string? Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The target of a symlink entry internal to the Archive. Will be null if not a symlink.
|
||||
@@ -71,11 +71,11 @@ public abstract class Entry : IEntry
|
||||
/// </summary>
|
||||
public abstract bool IsSplitAfter { get; }
|
||||
|
||||
public int VolumeIndexFirst => Parts?.FirstOrDefault()?.Index ?? 0;
|
||||
public int VolumeIndexLast => Parts?.LastOrDefault()?.Index ?? 0;
|
||||
public int VolumeIndexFirst => Parts.FirstOrDefault()?.Index ?? 0;
|
||||
public int VolumeIndexLast => Parts.LastOrDefault()?.Index ?? 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => Key;
|
||||
public override string ToString() => Key ?? "Entry";
|
||||
|
||||
internal abstract IEnumerable<FilePart> Parts { get; }
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class ExtractionException : Exception
|
||||
{
|
||||
public ExtractionException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public ExtractionException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal static class ExtractionMethods
|
||||
)
|
||||
{
|
||||
string destinationFileName;
|
||||
string fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
|
||||
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
|
||||
|
||||
//check for trailing slash.
|
||||
if (
|
||||
@@ -36,11 +36,13 @@ internal static class ExtractionMethods
|
||||
|
||||
options ??= new ExtractionOptions() { Overwrite = true };
|
||||
|
||||
string file = Path.GetFileName(entry.Key);
|
||||
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
|
||||
file = Utility.ReplaceInvalidFileNameChars(file);
|
||||
if (options.ExtractFullPath)
|
||||
{
|
||||
string folder = Path.GetDirectoryName(entry.Key)!;
|
||||
string destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
|
||||
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
|
||||
.NotNull("Directory is null");
|
||||
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
@@ -102,7 +104,7 @@ internal static class ExtractionMethods
|
||||
}
|
||||
else
|
||||
{
|
||||
FileMode fm = FileMode.Create;
|
||||
var fm = FileMode.Create;
|
||||
options ??= new ExtractionOptions() { Overwrite = true };
|
||||
|
||||
if (!options.Overwrite)
|
||||
|
||||
@@ -8,10 +8,10 @@ public abstract class FilePart
|
||||
|
||||
internal ArchiveEncoding ArchiveEncoding { get; }
|
||||
|
||||
internal abstract string FilePartName { get; }
|
||||
internal abstract string? FilePartName { get; }
|
||||
public int Index { get; set; }
|
||||
|
||||
internal abstract Stream GetCompressedStream();
|
||||
internal abstract Stream? GetCompressedStream();
|
||||
internal abstract Stream? GetRawStream();
|
||||
internal bool Skipped { get; set; }
|
||||
}
|
||||
|
||||
@@ -6,23 +6,23 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
public class GZipEntry : Entry
|
||||
{
|
||||
private readonly GZipFilePart _filePart;
|
||||
private readonly GZipFilePart? _filePart;
|
||||
|
||||
internal GZipEntry(GZipFilePart filePart) => _filePart = filePart;
|
||||
internal GZipEntry(GZipFilePart? filePart) => _filePart = filePart;
|
||||
|
||||
public override CompressionType CompressionType => CompressionType.GZip;
|
||||
|
||||
public override long Crc => _filePart.Crc ?? 0;
|
||||
public override long Crc => _filePart?.Crc ?? 0;
|
||||
|
||||
public override string Key => _filePart.FilePartName;
|
||||
public override string? Key => _filePart?.FilePartName;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
public override long CompressedSize => 0;
|
||||
|
||||
public override long Size => _filePart.UncompressedSize ?? 0;
|
||||
public override long Size => _filePart?.UncompressedSize ?? 0;
|
||||
|
||||
public override DateTime? LastModifiedTime => _filePart.DateModified;
|
||||
public override DateTime? LastModifiedTime => _filePart?.DateModified;
|
||||
|
||||
public override DateTime? CreatedTime => null;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GZipEntry : Entry
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.AsEnumerable<FilePart>();
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
|
||||
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed class GZipFilePart : FilePart
|
||||
internal uint? Crc { get; private set; }
|
||||
internal uint? UncompressedSize { get; private set; }
|
||||
|
||||
internal override string FilePartName => _name!;
|
||||
internal override string? FilePartName => _name;
|
||||
|
||||
internal override Stream GetCompressedStream() =>
|
||||
new DeflateStream(_stream, CompressionMode.Decompress, CompressionLevel.Default);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
public class GZipVolume : Volume
|
||||
{
|
||||
public GZipVolume(Stream stream, ReaderOptions options, int index = 0)
|
||||
public GZipVolume(Stream stream, ReaderOptions? options, int index)
|
||||
: base(stream, options, index) { }
|
||||
|
||||
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
|
||||
|
||||
@@ -9,7 +9,7 @@ public interface IEntry
|
||||
long CompressedSize { get; }
|
||||
long Crc { get; }
|
||||
DateTime? CreatedTime { get; }
|
||||
string Key { get; }
|
||||
string? Key { get; }
|
||||
string? LinkTarget { get; }
|
||||
bool IsDirectory { get; }
|
||||
bool IsEncrypted { get; }
|
||||
|
||||
@@ -6,5 +6,5 @@ public interface IVolume : IDisposable
|
||||
{
|
||||
int Index { get; }
|
||||
|
||||
string FileName { get; }
|
||||
string? FileName { get; }
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class IncompleteArchiveException : ArchiveException
|
||||
{
|
||||
public IncompleteArchiveException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class InvalidFormatException : ExtractionException
|
||||
{
|
||||
public InvalidFormatException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public InvalidFormatException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class MultiVolumeExtractionException : ExtractionException
|
||||
{
|
||||
public MultiVolumeExtractionException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public MultiVolumeExtractionException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class MultipartStreamRequiredException : ExtractionException
|
||||
{
|
||||
public MultipartStreamRequiredException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ public class OptionsBase
|
||||
/// </summary>
|
||||
public bool LeaveStreamOpen { get; set; } = true;
|
||||
|
||||
public ArchiveEncoding ArchiveEncoding { get; set; } = new ArchiveEncoding();
|
||||
public ArchiveEncoding ArchiveEncoding { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class PasswordProtectedException : ExtractionException
|
||||
{
|
||||
public PasswordProtectedException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public PasswordProtectedException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
84
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
84
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class CryptKey3 : ICryptKey
|
||||
{
|
||||
const int AES_128 = 128;
|
||||
|
||||
private string _password;
|
||||
|
||||
public CryptKey3(string password) => _password = password ?? "";
|
||||
|
||||
public ICryptoTransform Transformer(byte[] salt)
|
||||
{
|
||||
var aesIV = new byte[EncryptionConstV5.SIZE_INITV];
|
||||
|
||||
var rawLength = 2 * _password.Length;
|
||||
var rawPassword = new byte[rawLength + EncryptionConstV5.SIZE_SALT30];
|
||||
var passwordBytes = Encoding.UTF8.GetBytes(_password);
|
||||
for (var i = 0; i < _password.Length; i++)
|
||||
{
|
||||
rawPassword[i * 2] = passwordBytes[i];
|
||||
rawPassword[(i * 2) + 1] = 0;
|
||||
}
|
||||
|
||||
for (var i = 0; i < salt.Length; i++)
|
||||
{
|
||||
rawPassword[i + rawLength] = salt[i];
|
||||
}
|
||||
|
||||
var msgDigest = SHA1.Create();
|
||||
const int noOfRounds = (1 << 18);
|
||||
const int iblock = 3;
|
||||
|
||||
byte[] digest;
|
||||
var data = new byte[(rawPassword.Length + iblock) * noOfRounds];
|
||||
|
||||
//TODO slow code below, find ways to optimize
|
||||
for (var i = 0; i < noOfRounds; i++)
|
||||
{
|
||||
rawPassword.CopyTo(data, i * (rawPassword.Length + iblock));
|
||||
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i;
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8);
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)(i >> 16);
|
||||
|
||||
if (i % (noOfRounds / EncryptionConstV5.SIZE_INITV) == 0)
|
||||
{
|
||||
digest = msgDigest.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock));
|
||||
aesIV[i / (noOfRounds / EncryptionConstV5.SIZE_INITV)] = digest[19];
|
||||
}
|
||||
}
|
||||
digest = msgDigest.ComputeHash(data);
|
||||
//slow code ends
|
||||
|
||||
var aesKey = new byte[EncryptionConstV5.SIZE_INITV];
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
aesKey[(i * 4) + j] = (byte)(
|
||||
(
|
||||
((digest[i * 4] * 0x1000000) & 0xff000000)
|
||||
| (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000)
|
||||
| (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00)
|
||||
| (uint)(digest[(i * 4) + 3] & 0xff)
|
||||
) >> (j * 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var aes = Aes.Create();
|
||||
aes.KeySize = AES_128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = aesKey;
|
||||
aes.IV = aesIV;
|
||||
return aes.CreateDecryptor();
|
||||
}
|
||||
}
|
||||
95
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
95
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class CryptKey5 : ICryptKey
|
||||
{
|
||||
const int AES_256 = 256;
|
||||
const int DERIVED_KEY_LENGTH = 0x10;
|
||||
const int SHA256_DIGEST_SIZE = 32;
|
||||
|
||||
private string _password;
|
||||
private Rar5CryptoInfo _cryptoInfo;
|
||||
private byte[] _pswCheck = { };
|
||||
private byte[] _hashKey = { };
|
||||
|
||||
public CryptKey5(string? password, Rar5CryptoInfo rar5CryptoInfo)
|
||||
{
|
||||
_password = password ?? "";
|
||||
_cryptoInfo = rar5CryptoInfo;
|
||||
}
|
||||
|
||||
public byte[] PswCheck => _pswCheck;
|
||||
|
||||
public byte[] HashKey => _hashKey;
|
||||
|
||||
private static List<byte[]> GenerateRarPBKDF2Key(
|
||||
string password,
|
||||
byte[] salt,
|
||||
int iterations,
|
||||
int keyLength
|
||||
)
|
||||
{
|
||||
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password));
|
||||
var block = hmac.ComputeHash(salt);
|
||||
var finalHash = (byte[])block.Clone();
|
||||
|
||||
var loop = new int[] { iterations, 17, 17 };
|
||||
var res = new List<byte[]> { };
|
||||
|
||||
for (var x = 0; x < 3; x++)
|
||||
{
|
||||
for (var i = 1; i < loop[x]; i++)
|
||||
{
|
||||
block = hmac.ComputeHash(block);
|
||||
for (var j = 0; j < finalHash.Length; j++)
|
||||
{
|
||||
finalHash[j] ^= block[j];
|
||||
}
|
||||
}
|
||||
|
||||
res.Add((byte[])finalHash.Clone());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public ICryptoTransform Transformer(byte[] salt)
|
||||
{
|
||||
var iterations = (1 << _cryptoInfo.LG2Count); // Adjust the number of iterations as needed
|
||||
|
||||
var salt_rar5 = salt.Concat(new byte[] { 0, 0, 0, 1 });
|
||||
var derivedKey = GenerateRarPBKDF2Key(
|
||||
_password,
|
||||
salt_rar5.ToArray(),
|
||||
iterations,
|
||||
DERIVED_KEY_LENGTH
|
||||
);
|
||||
|
||||
_hashKey = derivedKey[1];
|
||||
|
||||
_pswCheck = new byte[EncryptionConstV5.SIZE_PSWCHECK];
|
||||
|
||||
for (var i = 0; i < SHA256_DIGEST_SIZE; i++)
|
||||
{
|
||||
_pswCheck[i % EncryptionConstV5.SIZE_PSWCHECK] ^= derivedKey[2][i];
|
||||
}
|
||||
|
||||
if (_cryptoInfo.UsePswCheck && !_cryptoInfo.PswCheck.SequenceEqual(_pswCheck))
|
||||
{
|
||||
throw new CryptographicException("The password did not match.");
|
||||
}
|
||||
|
||||
var aes = Aes.Create();
|
||||
aes.KeySize = AES_256;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = derivedKey[0];
|
||||
aes.IV = _cryptoInfo.InitV;
|
||||
return aes.CreateDecryptor();
|
||||
}
|
||||
}
|
||||
@@ -6,45 +6,11 @@ namespace SharpCompress.Common.Rar.Headers;
|
||||
|
||||
internal class ArchiveCryptHeader : RarHeader
|
||||
{
|
||||
private const int CRYPT_VERSION = 0; // Supported encryption version.
|
||||
private const int SIZE_SALT50 = 16;
|
||||
private const int SIZE_PSWCHECK = 8;
|
||||
private const int SIZE_PSWCHECK_CSUM = 4;
|
||||
private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count.
|
||||
|
||||
private bool _usePswCheck;
|
||||
private uint _lg2Count; // Log2 of PBKDF2 repetition count.
|
||||
private byte[] _salt;
|
||||
private byte[] _pswCheck;
|
||||
private byte[] _pswCheckCsm;
|
||||
|
||||
public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader)
|
||||
: base(header, reader, HeaderType.Crypt) { }
|
||||
|
||||
protected override void ReadFinish(MarkingBinaryReader reader)
|
||||
{
|
||||
var cryptVersion = reader.ReadRarVIntUInt32();
|
||||
if (cryptVersion > CRYPT_VERSION)
|
||||
{
|
||||
//error?
|
||||
return;
|
||||
}
|
||||
var encryptionFlags = reader.ReadRarVIntUInt32();
|
||||
_usePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
|
||||
_lg2Count = reader.ReadRarVIntByte(1);
|
||||
public Rar5CryptoInfo CryptInfo = new();
|
||||
|
||||
//UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
|
||||
if (_lg2Count > CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
//error?
|
||||
return;
|
||||
}
|
||||
|
||||
_salt = reader.ReadBytes(SIZE_SALT50);
|
||||
if (_usePswCheck)
|
||||
{
|
||||
_pswCheck = reader.ReadBytes(SIZE_PSWCHECK);
|
||||
_pswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM);
|
||||
}
|
||||
}
|
||||
protected override void ReadFinish(MarkingBinaryReader reader) =>
|
||||
CryptInfo = new Rar5CryptoInfo(reader, false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SharpCompress.IO;
|
||||
#if !Rar2017_64bit
|
||||
using size_t = System.UInt32;
|
||||
#else
|
||||
@@ -8,16 +11,11 @@ using nuint = System.UInt64;
|
||||
using size_t = System.UInt64;
|
||||
#endif
|
||||
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Rar.Headers;
|
||||
|
||||
internal class FileHeader : RarHeader
|
||||
{
|
||||
private uint _fileCrc;
|
||||
private byte[]? _hash;
|
||||
|
||||
public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
|
||||
: base(header, reader, headerType) { }
|
||||
@@ -52,7 +50,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (HasFlag(FileFlagsV5.HAS_CRC32))
|
||||
{
|
||||
FileCrc = reader.ReadUInt32();
|
||||
FileCrc = reader.ReadBytes(4);
|
||||
}
|
||||
|
||||
var compressionInfo = reader.ReadRarVIntUInt16();
|
||||
@@ -104,7 +102,13 @@ internal class FileHeader : RarHeader
|
||||
throw new InvalidFormatException("rar5 header size / extra size inconsistency");
|
||||
}
|
||||
|
||||
isEncryptedRar5 = false;
|
||||
const ushort FHEXTRA_CRYPT = 0x01;
|
||||
const ushort FHEXTRA_HASH = 0x02;
|
||||
const ushort FHEXTRA_HTIME = 0x03;
|
||||
// const ushort FHEXTRA_VERSION = 0x04;
|
||||
const ushort FHEXTRA_REDIR = 0x05;
|
||||
// const ushort FHEXTRA_UOWNER = 0x06;
|
||||
// const ushort FHEXTRA_SUBDATA = 0x07;
|
||||
|
||||
while (RemainingHeaderBytes(reader) > 0)
|
||||
{
|
||||
@@ -113,23 +117,30 @@ internal class FileHeader : RarHeader
|
||||
var type = reader.ReadRarVIntUInt16();
|
||||
switch (type)
|
||||
{
|
||||
//TODO
|
||||
case 1: // file encryption
|
||||
|
||||
case FHEXTRA_CRYPT: // file encryption
|
||||
{
|
||||
isEncryptedRar5 = true;
|
||||
Rar5CryptoInfo = new Rar5CryptoInfo(reader, true);
|
||||
|
||||
//var version = reader.ReadRarVIntByte();
|
||||
//if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version);
|
||||
if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0))
|
||||
{
|
||||
Rar5CryptoInfo = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// case 2: // file hash
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
case 3: // file time
|
||||
|
||||
case FHEXTRA_HASH:
|
||||
{
|
||||
const uint FHEXTRA_HASH_BLAKE2 = 0x0;
|
||||
// const uint HASH_BLAKE2 = 0x03;
|
||||
const int BLAKE2_DIGEST_SIZE = 0x20;
|
||||
if ((uint)reader.ReadRarVInt() == FHEXTRA_HASH_BLAKE2)
|
||||
{
|
||||
// var hash = HASH_BLAKE2;
|
||||
_hash = reader.ReadBytes(BLAKE2_DIGEST_SIZE);
|
||||
}
|
||||
// enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2};
|
||||
}
|
||||
break;
|
||||
case FHEXTRA_HTIME: // file time
|
||||
{
|
||||
var flags = reader.ReadRarVIntUInt16();
|
||||
var isWindowsTime = (flags & 1) == 0;
|
||||
@@ -148,22 +159,27 @@ internal class FileHeader : RarHeader
|
||||
}
|
||||
break;
|
||||
//TODO
|
||||
// case 4: // file version
|
||||
// case FHEXTRA_VERSION: // file version
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 5: // file system redirection
|
||||
case FHEXTRA_REDIR: // file system redirection
|
||||
{
|
||||
RedirType = reader.ReadRarVIntByte();
|
||||
RedirFlags = reader.ReadRarVIntByte();
|
||||
var nn = reader.ReadRarVIntUInt16();
|
||||
var bb = reader.ReadBytes(nn);
|
||||
RedirTargetName = ConvertPathV5(Encoding.UTF8.GetString(bb, 0, bb.Length));
|
||||
}
|
||||
break;
|
||||
//TODO
|
||||
// case FHEXTRA_UOWNER: // unix owner
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 6: // unix owner
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 7: // service data
|
||||
// case FHEXTRA_SUBDATA: // service data
|
||||
// {
|
||||
//
|
||||
// }
|
||||
@@ -171,6 +187,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
default:
|
||||
// skip unknown record types to allow new record types to be added in the future
|
||||
//Console.WriteLine($"unhandled rar header field type {type}");
|
||||
break;
|
||||
}
|
||||
// drain any trailing bytes of extra record
|
||||
@@ -222,7 +239,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
HostOs = reader.ReadByte();
|
||||
|
||||
FileCrc = reader.ReadUInt32();
|
||||
FileCrc = reader.ReadBytes(4);
|
||||
|
||||
FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
|
||||
|
||||
@@ -255,13 +272,11 @@ internal class FileHeader : RarHeader
|
||||
|
||||
var fileNameBytes = reader.ReadBytes(nameSize);
|
||||
|
||||
const int saltSize = 8;
|
||||
const int newLhdSize = 32;
|
||||
|
||||
switch (HeaderCode)
|
||||
{
|
||||
case HeaderCodeV.RAR4_FILE_HEADER:
|
||||
|
||||
{
|
||||
if (HasFlag(FileFlagsV4.UNICODE))
|
||||
{
|
||||
@@ -288,12 +303,11 @@ internal class FileHeader : RarHeader
|
||||
}
|
||||
break;
|
||||
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
|
||||
|
||||
{
|
||||
var datasize = HeaderSize - newLhdSize - nameSize;
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
datasize -= saltSize;
|
||||
datasize -= EncryptionConstV5.SIZE_SALT30;
|
||||
}
|
||||
if (datasize > 0)
|
||||
{
|
||||
@@ -302,6 +316,10 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
|
||||
{
|
||||
if (SubData is null)
|
||||
{
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
RecoverySectors =
|
||||
SubData[8]
|
||||
+ (SubData[9] << 8)
|
||||
@@ -314,7 +332,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
R4Salt = reader.ReadBytes(saltSize);
|
||||
R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
}
|
||||
if (HasFlag(FileFlagsV4.EXT_TIME))
|
||||
{
|
||||
@@ -323,12 +341,16 @@ internal class FileHeader : RarHeader
|
||||
if (RemainingHeaderBytes(reader) >= 2)
|
||||
{
|
||||
var extendedFlags = reader.ReadUInt16();
|
||||
FileLastModifiedTime = ProcessExtendedTimeV4(
|
||||
extendedFlags,
|
||||
FileLastModifiedTime,
|
||||
reader,
|
||||
0
|
||||
);
|
||||
if (FileLastModifiedTime is not null)
|
||||
{
|
||||
FileLastModifiedTime = ProcessExtendedTimeV4(
|
||||
extendedFlags,
|
||||
FileLastModifiedTime,
|
||||
reader,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1);
|
||||
FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2);
|
||||
FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3);
|
||||
@@ -360,7 +382,7 @@ internal class FileHeader : RarHeader
|
||||
var dosTime = reader.ReadUInt32();
|
||||
time = Utility.DosDateToDateTime(dosTime);
|
||||
}
|
||||
if ((rmode & 4) == 0)
|
||||
if ((rmode & 4) == 0 && time is not null)
|
||||
{
|
||||
time = time.Value.AddSeconds(1);
|
||||
}
|
||||
@@ -373,7 +395,11 @@ internal class FileHeader : RarHeader
|
||||
}
|
||||
|
||||
//10^-7 to 10^-3
|
||||
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
|
||||
if (time is not null)
|
||||
{
|
||||
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ConvertPathV4(string path)
|
||||
@@ -389,24 +415,16 @@ internal class FileHeader : RarHeader
|
||||
return path;
|
||||
}
|
||||
|
||||
public override string ToString() => FileName;
|
||||
public override string ToString() => FileName ?? "FileHeader";
|
||||
|
||||
private ushort Flags { get; set; }
|
||||
|
||||
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
|
||||
|
||||
internal uint FileCrc
|
||||
internal byte[]? FileCrc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32))
|
||||
{
|
||||
//!!! rar5:
|
||||
throw new InvalidOperationException("TODO rar5");
|
||||
}
|
||||
return _fileCrc;
|
||||
}
|
||||
private set => _fileCrc = value;
|
||||
get => _hash;
|
||||
private set => _hash = value;
|
||||
}
|
||||
|
||||
// 0 - storing
|
||||
@@ -428,20 +446,26 @@ internal class FileHeader : RarHeader
|
||||
|
||||
public bool IsSolid { get; private set; }
|
||||
|
||||
public byte RedirType { get; private set; }
|
||||
public bool IsRedir => RedirType != 0;
|
||||
public byte RedirFlags { get; private set; }
|
||||
public bool IsRedirDirectory => (RedirFlags & RedirFlagV5.DIRECTORY) != 0;
|
||||
public string? RedirTargetName { get; private set; }
|
||||
|
||||
// unused for UnpackV1 implementation (limitation)
|
||||
internal size_t WindowSize { get; private set; }
|
||||
|
||||
internal byte[] R4Salt { get; private set; }
|
||||
|
||||
internal byte[]? R4Salt { get; private set; }
|
||||
internal Rar5CryptoInfo? Rar5CryptoInfo { get; private set; }
|
||||
private byte HostOs { get; set; }
|
||||
internal uint FileAttributes { get; private set; }
|
||||
internal long CompressedSize { get; private set; }
|
||||
internal long UncompressedSize { get; private set; }
|
||||
internal string FileName { get; private set; }
|
||||
internal byte[] SubData { get; private set; }
|
||||
internal string? FileName { get; private set; }
|
||||
internal byte[]? SubData { get; private set; }
|
||||
internal int RecoverySectors { get; private set; }
|
||||
internal long DataStartPosition { get; set; }
|
||||
public Stream PackedStream { get; set; }
|
||||
public Stream? PackedStream { get; set; }
|
||||
|
||||
public bool IsSplitBefore =>
|
||||
IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE);
|
||||
@@ -450,8 +474,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
|
||||
|
||||
private bool isEncryptedRar5 = false;
|
||||
public bool IsEncrypted => IsRar5 ? isEncryptedRar5 : HasFlag(FileFlagsV4.PASSWORD);
|
||||
public bool IsEncrypted => IsRar5 ? Rar5CryptoInfo != null : HasFlag(FileFlagsV4.PASSWORD);
|
||||
|
||||
internal DateTime? FileLastModifiedTime { get; private set; }
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public enum HeaderType : byte
|
||||
Sign,
|
||||
NewSub,
|
||||
EndArchive,
|
||||
Crypt
|
||||
Crypt,
|
||||
}
|
||||
|
||||
internal static class HeaderCodeV
|
||||
@@ -50,6 +50,17 @@ internal static class EncryptionFlagsV5
|
||||
public const uint FHEXTRA_CRYPT_HASHMAC = 0x02;
|
||||
}
|
||||
|
||||
internal static class EncryptionConstV5
|
||||
{
|
||||
public const int VERSION = 0;
|
||||
public const uint CRYPT5_KDF_LG2_COUNT_MAX = 0x24;
|
||||
public const int SIZE_SALT30 = 0x08;
|
||||
public const int SIZE_SALT50 = 0x10;
|
||||
public const int SIZE_INITV = 0x10;
|
||||
public const int SIZE_PSWCHECK = 0x08;
|
||||
public const int SIZE_PSWCHECK_CSUM = 0x04;
|
||||
}
|
||||
|
||||
internal static class HeaderFlagsV5
|
||||
{
|
||||
public const ushort HAS_EXTRA = 0x0001;
|
||||
@@ -146,3 +157,17 @@ internal static class EndArchiveFlagsV5
|
||||
{
|
||||
public const ushort HAS_NEXT_VOLUME = 0x0001;
|
||||
}
|
||||
|
||||
internal static class RedirTypeV5
|
||||
{
|
||||
public const byte UNIX_SYMLINK = 0x0001;
|
||||
public const byte WIN_SYMLINK = 0x0002;
|
||||
public const byte WIN_JUNCTION = 0x0003;
|
||||
public const byte HARD_LINK = 0x0004;
|
||||
public const byte FILE_COPY = 0x0005;
|
||||
}
|
||||
|
||||
internal static class RedirFlagV5
|
||||
{
|
||||
public const byte DIRECTORY = 0x0001;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar.Headers;
|
||||
@@ -21,7 +20,7 @@ internal class RarHeader : IRarHeader
|
||||
{
|
||||
return new RarHeader(reader, isRar5, archiveEncoding);
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
catch (InvalidFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ public class RarHeaderFactory
|
||||
{
|
||||
private bool _isRar5;
|
||||
|
||||
private Rar5CryptoInfo? _cryptInfo;
|
||||
|
||||
public RarHeaderFactory(StreamingMode mode, ReaderOptions options)
|
||||
{
|
||||
StreamingMode = mode;
|
||||
@@ -53,7 +55,19 @@ public class RarHeaderFactory
|
||||
"Encrypted Rar archive has no password specified."
|
||||
);
|
||||
}
|
||||
reader = new RarCryptoBinaryReader(stream, Options.Password);
|
||||
|
||||
if (_isRar5 && _cryptInfo != null)
|
||||
{
|
||||
_cryptInfo.ReadInitV(new MarkingBinaryReader(stream));
|
||||
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
|
||||
|
||||
reader = new RarCryptoBinaryReader(stream, _headerKey, _cryptInfo.Salt);
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = new CryptKey3(Options.Password);
|
||||
reader = new RarCryptoBinaryReader(stream, key);
|
||||
}
|
||||
}
|
||||
|
||||
var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding);
|
||||
@@ -82,13 +96,11 @@ public class RarHeaderFactory
|
||||
switch (StreamingMode)
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
|
||||
{
|
||||
reader.BaseStream.Position += ph.DataSize;
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
|
||||
{
|
||||
reader.BaseStream.Skip(ph.DataSize);
|
||||
}
|
||||
@@ -105,7 +117,14 @@ public class RarHeaderFactory
|
||||
case HeaderCodeV.RAR5_SERVICE_HEADER:
|
||||
{
|
||||
var fh = new FileHeader(header, reader, HeaderType.Service);
|
||||
SkipData(fh, reader);
|
||||
if (fh.FileName == "CMT")
|
||||
{
|
||||
fh.PackedStream = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipData(fh, reader);
|
||||
}
|
||||
return fh;
|
||||
}
|
||||
|
||||
@@ -123,17 +142,15 @@ public class RarHeaderFactory
|
||||
switch (StreamingMode)
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
|
||||
{
|
||||
fh.DataStartPosition = reader.BaseStream.Position;
|
||||
reader.BaseStream.Position += fh.CompressedSize;
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
|
||||
{
|
||||
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
|
||||
if (fh.R4Salt is null)
|
||||
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
|
||||
{
|
||||
fh.PackedStream = ms;
|
||||
}
|
||||
@@ -141,8 +158,15 @@ public class RarHeaderFactory
|
||||
{
|
||||
fh.PackedStream = new RarCryptoWrapper(
|
||||
ms,
|
||||
Options.Password!,
|
||||
fh.R4Salt
|
||||
fh.R4Salt is null
|
||||
? fh.Rar5CryptoInfo.NotNull().Salt
|
||||
: fh.R4Salt,
|
||||
fh.R4Salt is null
|
||||
? new CryptKey5(
|
||||
Options.Password,
|
||||
fh.Rar5CryptoInfo.NotNull()
|
||||
)
|
||||
: new CryptKey3(Options.Password)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -161,9 +185,11 @@ public class RarHeaderFactory
|
||||
}
|
||||
case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER:
|
||||
{
|
||||
var ch = new ArchiveCryptHeader(header, reader);
|
||||
var cryptoHeader = new ArchiveCryptHeader(header, reader);
|
||||
IsEncrypted = true;
|
||||
return ch;
|
||||
_cryptInfo = cryptoHeader.CryptInfo;
|
||||
|
||||
return cryptoHeader;
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -177,14 +203,12 @@ public class RarHeaderFactory
|
||||
switch (StreamingMode)
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
|
||||
{
|
||||
fh.DataStartPosition = reader.BaseStream.Position;
|
||||
reader.BaseStream.Position += fh.CompressedSize;
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
|
||||
{
|
||||
//skip the data because it's useless?
|
||||
reader.BaseStream.Skip(fh.CompressedSize);
|
||||
|
||||
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal interface ICryptKey
|
||||
{
|
||||
ICryptoTransform Transformer(byte[] salt);
|
||||
}
|
||||
57
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
57
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class Rar5CryptoInfo
|
||||
{
|
||||
public Rar5CryptoInfo() { }
|
||||
|
||||
public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV)
|
||||
{
|
||||
var cryptVersion = reader.ReadRarVIntUInt32();
|
||||
if (cryptVersion > EncryptionConstV5.VERSION)
|
||||
{
|
||||
throw new CryptographicException($"Unsupported crypto version of {cryptVersion}");
|
||||
}
|
||||
var encryptionFlags = reader.ReadRarVIntUInt32();
|
||||
UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
|
||||
LG2Count = reader.ReadRarVIntByte(1);
|
||||
|
||||
if (LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
throw new CryptographicException($"Unsupported LG2 count of {LG2Count}.");
|
||||
}
|
||||
|
||||
Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT50);
|
||||
|
||||
if (readInitV) // File header needs to read IV here
|
||||
{
|
||||
ReadInitV(reader);
|
||||
}
|
||||
|
||||
if (UsePswCheck)
|
||||
{
|
||||
PswCheck = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK);
|
||||
var _pswCheckCsm = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK_CSUM);
|
||||
|
||||
var sha = SHA256.Create();
|
||||
UsePswCheck = sha.ComputeHash(PswCheck).AsSpan().StartsWith(_pswCheckCsm.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadInitV(MarkingBinaryReader reader) =>
|
||||
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
|
||||
|
||||
public bool UsePswCheck = false;
|
||||
|
||||
public int LG2Count = 0;
|
||||
|
||||
public byte[] InitV = { };
|
||||
|
||||
public byte[] Salt = { };
|
||||
|
||||
public byte[] PswCheck = { };
|
||||
}
|
||||
@@ -1,29 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
|
||||
{
|
||||
private RarRijndael _rijndael;
|
||||
private byte[] _salt;
|
||||
private readonly string _password;
|
||||
private readonly Queue<byte> _data = new Queue<byte>();
|
||||
private BlockTransformer _rijndael;
|
||||
private readonly Queue<byte> _data = new();
|
||||
private long _readCount;
|
||||
|
||||
public RarCryptoBinaryReader(Stream stream, string password)
|
||||
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey)
|
||||
: base(stream)
|
||||
{
|
||||
_password = password;
|
||||
|
||||
// coderb: not sure why this was being done at this logical point
|
||||
//SkipQueue();
|
||||
var salt = ReadBytes(8);
|
||||
|
||||
_salt = salt;
|
||||
_rijndael = RarRijndael.InitializeFrom(_password, salt);
|
||||
var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
_readCount += EncryptionConstV5.SIZE_SALT30;
|
||||
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
|
||||
}
|
||||
|
||||
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey, byte[] salt)
|
||||
: base(stream) => _rijndael = new BlockTransformer(cryptKey.Transformer(salt));
|
||||
|
||||
// track read count ourselves rather than using the underlying stream since we buffer
|
||||
public override long CurrentReadByteCount
|
||||
{
|
||||
@@ -36,29 +36,9 @@ internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
|
||||
|
||||
public override void Mark() => _readCount = 0;
|
||||
|
||||
private bool UseEncryption => _salt != null;
|
||||
public override byte ReadByte() => ReadAndDecryptBytes(1)[0];
|
||||
|
||||
public override byte ReadByte()
|
||||
{
|
||||
if (UseEncryption)
|
||||
{
|
||||
return ReadAndDecryptBytes(1)[0];
|
||||
}
|
||||
|
||||
_readCount++;
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override byte[] ReadBytes(int count)
|
||||
{
|
||||
if (UseEncryption)
|
||||
{
|
||||
return ReadAndDecryptBytes(count);
|
||||
}
|
||||
|
||||
_readCount += count;
|
||||
return base.ReadBytes(count);
|
||||
}
|
||||
public override byte[] ReadBytes(int count) => ReadAndDecryptBytes(count);
|
||||
|
||||
private byte[] ReadAndDecryptBytes(int count)
|
||||
{
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal sealed class RarCryptoWrapper : Stream
|
||||
{
|
||||
private readonly Stream _actualStream;
|
||||
private readonly byte[] _salt;
|
||||
private RarRijndael _rijndael;
|
||||
private readonly Queue<byte> _data = new Queue<byte>();
|
||||
private BlockTransformer _rijndael;
|
||||
private readonly Queue<byte> _data = new();
|
||||
|
||||
public RarCryptoWrapper(Stream actualStream, string password, byte[] salt)
|
||||
public RarCryptoWrapper(Stream actualStream, byte[] salt, ICryptKey key)
|
||||
{
|
||||
_actualStream = actualStream;
|
||||
_salt = salt;
|
||||
_rijndael = RarRijndael.InitializeFrom(password ?? "", salt);
|
||||
_rijndael = new BlockTransformer(key.Transformer(salt));
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_salt is null)
|
||||
{
|
||||
return _actualStream.Read(buffer, offset, count);
|
||||
}
|
||||
return ReadAndDecrypt(buffer, offset, count);
|
||||
}
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
ReadAndDecrypt(buffer, offset, count);
|
||||
|
||||
public int ReadAndDecrypt(byte[] buffer, int offset, int count)
|
||||
{
|
||||
@@ -41,7 +34,7 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
if (sizeToRead > 0)
|
||||
{
|
||||
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
|
||||
Span<byte> cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE];
|
||||
Span<byte> cipherText = stackalloc byte[16];
|
||||
for (var i = 0; i < alignedSize / 16; i++)
|
||||
{
|
||||
//long ax = System.currentTimeMillis();
|
||||
@@ -77,11 +70,11 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_rijndael != null)
|
||||
if (disposing)
|
||||
{
|
||||
_rijndael.Dispose();
|
||||
_rijndael = null!;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ public abstract class RarEntry : Entry
|
||||
/// <summary>
|
||||
/// The File's 32 bit CRC Hash
|
||||
/// </summary>
|
||||
public override long Crc => FileHeader.FileCrc;
|
||||
public override long Crc => BitConverter.ToUInt32(FileHeader.FileCrc.NotNull(), 0);
|
||||
|
||||
/// <summary>
|
||||
/// The path of the file internal to the Rar Archive.
|
||||
/// </summary>
|
||||
public override string Key => FileHeader.FileName;
|
||||
public override string? Key => FileHeader.FileName;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
@@ -55,12 +55,21 @@ public abstract class RarEntry : Entry
|
||||
public override bool IsEncrypted => FileHeader.IsEncrypted;
|
||||
|
||||
/// <summary>
|
||||
/// Entry is password protected and encrypted and cannot be extracted.
|
||||
/// Entry Windows file attributes
|
||||
/// </summary>
|
||||
public override int? Attrib => (int)FileHeader.FileAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entry is a directory
|
||||
/// </summary>
|
||||
public override bool IsDirectory => FileHeader.IsDirectory;
|
||||
|
||||
public override bool IsSplitAfter => FileHeader.IsSplitAfter;
|
||||
|
||||
public bool IsRedir => FileHeader.IsRedir;
|
||||
|
||||
public string? RedirTargetName => FileHeader.RedirTargetName;
|
||||
|
||||
public override string ToString() =>
|
||||
string.Format(
|
||||
"Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}",
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class RarRijndael : IDisposable
|
||||
{
|
||||
internal const int CRYPTO_BLOCK_SIZE = 16;
|
||||
|
||||
private readonly string _password;
|
||||
private readonly byte[] _salt;
|
||||
private byte[] _aesInitializationVector;
|
||||
private RijndaelEngine _rijndael;
|
||||
|
||||
private RarRijndael(string password, byte[] salt)
|
||||
{
|
||||
_password = password;
|
||||
_salt = salt;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_rijndael = new RijndaelEngine();
|
||||
_aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE];
|
||||
var rawLength = 2 * _password.Length;
|
||||
var rawPassword = new byte[rawLength + 8];
|
||||
var passwordBytes = Encoding.UTF8.GetBytes(_password);
|
||||
for (var i = 0; i < _password.Length; i++)
|
||||
{
|
||||
rawPassword[i * 2] = passwordBytes[i];
|
||||
rawPassword[(i * 2) + 1] = 0;
|
||||
}
|
||||
for (var i = 0; i < _salt.Length; i++)
|
||||
{
|
||||
rawPassword[i + rawLength] = _salt[i];
|
||||
}
|
||||
|
||||
const int noOfRounds = (1 << 18);
|
||||
const int iblock = 3;
|
||||
byte[] digest;
|
||||
var data = new byte[(rawPassword.Length + iblock) * noOfRounds];
|
||||
|
||||
//TODO slow code below, find ways to optimize
|
||||
for (var i = 0; i < noOfRounds; i++)
|
||||
{
|
||||
rawPassword.CopyTo(data, i * (rawPassword.Length + iblock));
|
||||
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i;
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8);
|
||||
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)(
|
||||
i >> CRYPTO_BLOCK_SIZE
|
||||
);
|
||||
|
||||
if (i % (noOfRounds / CRYPTO_BLOCK_SIZE) == 0)
|
||||
{
|
||||
digest = SHA1.Create()
|
||||
.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock));
|
||||
_aesInitializationVector[i / (noOfRounds / CRYPTO_BLOCK_SIZE)] = digest[19];
|
||||
}
|
||||
}
|
||||
digest = SHA1.Create().ComputeHash(data);
|
||||
//slow code ends
|
||||
|
||||
var aesKey = new byte[CRYPTO_BLOCK_SIZE];
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
aesKey[(i * 4) + j] = (byte)(
|
||||
(
|
||||
((digest[i * 4] * 0x1000000) & 0xff000000)
|
||||
| (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000)
|
||||
| (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00)
|
||||
| (uint)(digest[(i * 4) + 3] & 0xff)
|
||||
) >> (j * 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_rijndael.Init(false, new KeyParameter(aesKey));
|
||||
}
|
||||
|
||||
public static RarRijndael InitializeFrom(string password, byte[] salt)
|
||||
{
|
||||
var rijndael = new RarRijndael(password, salt);
|
||||
rijndael.Initialize();
|
||||
return rijndael;
|
||||
}
|
||||
|
||||
public byte[] ProcessBlock(ReadOnlySpan<byte> cipherText)
|
||||
{
|
||||
Span<byte> plainText = stackalloc byte[CRYPTO_BLOCK_SIZE]; // 16 bytes
|
||||
var decryptedBytes = new byte[CRYPTO_BLOCK_SIZE];
|
||||
_rijndael.ProcessBlock(cipherText, plainText);
|
||||
|
||||
for (var j = 0; j < CRYPTO_BLOCK_SIZE; j++)
|
||||
{
|
||||
decryptedBytes[j] = (byte)(plainText[j] ^ _aesInitializationVector[j % 16]); //32:114, 33:101
|
||||
}
|
||||
|
||||
for (var j = 0; j < _aesInitializationVector.Length; j++)
|
||||
{
|
||||
_aesInitializationVector[j] = cipherText[j];
|
||||
}
|
||||
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
@@ -14,17 +15,14 @@ namespace SharpCompress.Common.Rar;
|
||||
public abstract class RarVolume : Volume
|
||||
{
|
||||
private readonly RarHeaderFactory _headerFactory;
|
||||
internal int _maxCompressionAlgorithm;
|
||||
private int _maxCompressionAlgorithm;
|
||||
|
||||
internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index = 0)
|
||||
internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index)
|
||||
: base(stream, options, index) => _headerFactory = new RarHeaderFactory(mode, options);
|
||||
|
||||
#nullable disable
|
||||
internal ArchiveHeader ArchiveHeader { get; private set; }
|
||||
private ArchiveHeader? ArchiveHeader { get; set; }
|
||||
|
||||
#nullable enable
|
||||
|
||||
internal StreamingMode Mode => _headerFactory.StreamingMode;
|
||||
private StreamingMode Mode => _headerFactory.StreamingMode;
|
||||
|
||||
internal abstract IEnumerable<RarFilePart> ReadFileParts();
|
||||
|
||||
@@ -38,19 +36,16 @@ public abstract class RarVolume : Volume
|
||||
switch (header.HeaderType)
|
||||
{
|
||||
case HeaderType.Mark:
|
||||
|
||||
{
|
||||
lastMarkHeader = (MarkHeader)header;
|
||||
}
|
||||
break;
|
||||
case HeaderType.Archive:
|
||||
|
||||
{
|
||||
ArchiveHeader = (ArchiveHeader)header;
|
||||
}
|
||||
break;
|
||||
case HeaderType.File:
|
||||
|
||||
{
|
||||
var fh = (FileHeader)header;
|
||||
if (_maxCompressionAlgorithm < fh.CompressionAlgorithm)
|
||||
@@ -62,19 +57,13 @@ public abstract class RarVolume : Volume
|
||||
}
|
||||
break;
|
||||
case HeaderType.Service:
|
||||
|
||||
{
|
||||
var fh = (FileHeader)header;
|
||||
if (fh.FileName == "CMT")
|
||||
{
|
||||
var part = CreateFilePart(lastMarkHeader!, fh);
|
||||
var buffer = new byte[fh.CompressedSize];
|
||||
part.GetCompressedStream().Read(buffer, 0, buffer.Length);
|
||||
Comment = System.Text.Encoding.UTF8.GetString(
|
||||
buffer,
|
||||
0,
|
||||
buffer.Length - 1
|
||||
);
|
||||
fh.PackedStream.NotNull().ReadFully(buffer);
|
||||
Comment = Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -108,7 +97,7 @@ public abstract class RarVolume : Volume
|
||||
get
|
||||
{
|
||||
EnsureArchiveHeaderLoaded();
|
||||
return ArchiveHeader.IsFirstVolume;
|
||||
return ArchiveHeader?.IsFirstVolume ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +109,7 @@ public abstract class RarVolume : Volume
|
||||
get
|
||||
{
|
||||
EnsureArchiveHeaderLoaded();
|
||||
return ArchiveHeader.IsVolume;
|
||||
return ArchiveHeader?.IsVolume ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +122,7 @@ public abstract class RarVolume : Volume
|
||||
get
|
||||
{
|
||||
EnsureArchiveHeaderLoaded();
|
||||
return ArchiveHeader.IsSolid;
|
||||
return ArchiveHeader?.IsSolid ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@ internal class ArchiveDatabase
|
||||
internal long _startPositionAfterHeader;
|
||||
internal long _dataStartPosition;
|
||||
|
||||
internal List<long> _packSizes = new List<long>();
|
||||
internal List<uint?> _packCrCs = new List<uint?>();
|
||||
internal List<CFolder> _folders = new List<CFolder>();
|
||||
internal List<long> _packSizes = new();
|
||||
internal List<uint?> _packCrCs = new();
|
||||
internal List<CFolder> _folders = new();
|
||||
internal List<int> _numUnpackStreamsVector;
|
||||
internal List<CFileItem> _files = new List<CFileItem>();
|
||||
internal List<CFileItem> _files = new();
|
||||
|
||||
internal List<long> _packStreamStartPositions = new List<long>();
|
||||
internal List<int> _folderStartFileIndex = new List<int>();
|
||||
internal List<int> _fileIndexToFolderIndexMap = new List<int>();
|
||||
internal List<long> _packStreamStartPositions = new();
|
||||
internal List<int> _folderStartFileIndex = new();
|
||||
internal List<int> _fileIndexToFolderIndexMap = new();
|
||||
|
||||
internal IPasswordProvider PasswordProvider { get; }
|
||||
|
||||
@@ -35,7 +35,7 @@ internal class ArchiveDatabase
|
||||
_packSizes.Clear();
|
||||
_packCrCs.Clear();
|
||||
_folders.Clear();
|
||||
_numUnpackStreamsVector = null!;
|
||||
_numUnpackStreamsVector = null;
|
||||
_files.Clear();
|
||||
|
||||
_packStreamStartPositions.Clear();
|
||||
|
||||
@@ -14,13 +14,13 @@ namespace SharpCompress.Common.SevenZip;
|
||||
internal class ArchiveReader
|
||||
{
|
||||
internal Stream _stream;
|
||||
internal Stack<DataReader> _readerStack = new Stack<DataReader>();
|
||||
internal Stack<DataReader> _readerStack = new();
|
||||
internal DataReader _currentReader;
|
||||
internal long _streamOrigin;
|
||||
internal long _streamEnding;
|
||||
internal byte[] _header;
|
||||
|
||||
private readonly Dictionary<int, Stream> _cachedStreams = new Dictionary<int, Stream>();
|
||||
private readonly Dictionary<int, Stream> _cachedStreams = new();
|
||||
|
||||
internal void AddByteStream(byte[] buffer, int offset, int length)
|
||||
{
|
||||
@@ -784,7 +784,7 @@ internal class ArchiveReader
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -843,7 +843,7 @@ internal class ArchiveReader
|
||||
outStream.ReadExact(data, 0, data.Length);
|
||||
if (outStream.ReadByte() >= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Decoded stream is longer than expected.");
|
||||
throw new InvalidFormatException("Decoded stream is longer than expected.");
|
||||
}
|
||||
dataVector.Add(data);
|
||||
|
||||
@@ -854,7 +854,7 @@ internal class ArchiveReader
|
||||
!= folder._unpackCrc
|
||||
)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
throw new InvalidFormatException(
|
||||
"Decoded stream does not match expected CRC."
|
||||
);
|
||||
}
|
||||
@@ -996,6 +996,11 @@ internal class ArchiveReader
|
||||
numFiles,
|
||||
delegate(int i, uint? attr)
|
||||
{
|
||||
// Keep the original attribute value because it could potentially get
|
||||
// modified in the logic that follows. Some callers of the library may
|
||||
// find the original value useful.
|
||||
db._files[i].ExtendedAttrib = attr;
|
||||
|
||||
// Some third party implementations established an unofficial extension
|
||||
// of the 7z archive format by placing posix file attributes in the high
|
||||
// bits of the windows file attributes. This makes use of the fact that
|
||||
@@ -1220,23 +1225,46 @@ internal class ArchiveReader
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Open(Stream stream)
|
||||
public void Open(Stream stream, bool lookForHeader)
|
||||
{
|
||||
Close();
|
||||
|
||||
_streamOrigin = stream.Position;
|
||||
_streamEnding = stream.Length;
|
||||
|
||||
// TODO: Check Signature!
|
||||
_header = new byte[0x20];
|
||||
for (var offset = 0; offset < 0x20; )
|
||||
var canScan = lookForHeader ? 0x80000 - 20 : 0;
|
||||
while (true)
|
||||
{
|
||||
var delta = stream.Read(_header, offset, 0x20 - offset);
|
||||
if (delta == 0)
|
||||
// TODO: Check Signature!
|
||||
_header = new byte[0x20];
|
||||
for (var offset = 0; offset < 0x20; )
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
var delta = stream.Read(_header, offset, 0x20 - offset);
|
||||
if (delta == 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
offset += delta;
|
||||
}
|
||||
offset += delta;
|
||||
|
||||
if (
|
||||
!lookForHeader
|
||||
|| _header
|
||||
.AsSpan(0, length: 6)
|
||||
.SequenceEqual<byte>([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (canScan == 0)
|
||||
{
|
||||
throw new InvalidFormatException("Unable to find 7z signature");
|
||||
}
|
||||
|
||||
canScan--;
|
||||
stream.Position = ++_streamOrigin;
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
@@ -1359,7 +1387,7 @@ internal class ArchiveReader
|
||||
{
|
||||
internal int _fileIndex;
|
||||
internal int _folderIndex;
|
||||
internal List<bool> _extractStatuses = new List<bool>();
|
||||
internal List<bool> _extractStatuses = new();
|
||||
|
||||
internal CExtractFolderInfo(int fileIndex, int folderIndex)
|
||||
{
|
||||
@@ -1393,7 +1421,7 @@ internal class ArchiveReader
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
@@ -1435,7 +1463,7 @@ internal class ArchiveReader
|
||||
#if DEBUG
|
||||
Log.WriteLine(_db._files[index].Name);
|
||||
#endif
|
||||
if (_db._files[index].CrcDefined)
|
||||
if (_db._files[index].Crc.HasValue)
|
||||
{
|
||||
_stream = new CrcCheckStream(_db._files[index].Crc.Value);
|
||||
}
|
||||
|
||||
@@ -8,18 +8,13 @@ internal class CFileItem
|
||||
{
|
||||
public long Size { get; internal set; }
|
||||
public uint? Attrib { get; internal set; }
|
||||
public uint? ExtendedAttrib { get; internal set; }
|
||||
public uint? Crc { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
|
||||
public bool HasStream { get; internal set; }
|
||||
public bool IsDir { get; internal set; }
|
||||
|
||||
public bool CrcDefined => Crc != null;
|
||||
|
||||
public bool AttribDefined => Attrib != null;
|
||||
|
||||
public void SetAttrib(uint attrib) => Attrib = attrib;
|
||||
|
||||
public DateTime? CTime { get; internal set; }
|
||||
public DateTime? ATime { get; internal set; }
|
||||
public DateTime? MTime { get; internal set; }
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace SharpCompress.Common.SevenZip;
|
||||
|
||||
internal class CFolder
|
||||
{
|
||||
internal List<CCoderInfo> _coders = new List<CCoderInfo>();
|
||||
internal List<CBindPair> _bindPairs = new List<CBindPair>();
|
||||
internal List<int> _packStreams = new List<int>();
|
||||
internal List<CCoderInfo> _coders = new();
|
||||
internal List<CBindPair> _bindPairs = new();
|
||||
internal List<int> _packStreams = new();
|
||||
internal int _firstPackStreamId;
|
||||
internal List<long> _unpackSizes = new List<long>();
|
||||
internal List<long> _unpackSizes = new();
|
||||
internal uint? _unpackCrc;
|
||||
|
||||
internal bool UnpackCrcDefined => _unpackCrc != null;
|
||||
|
||||
@@ -7,10 +7,10 @@ internal readonly struct CMethodId
|
||||
public const ulong K_LZMA2_ID = 0x21;
|
||||
public const ulong K_AES_ID = 0x06F10701;
|
||||
|
||||
public static readonly CMethodId K_COPY = new CMethodId(K_COPY_ID);
|
||||
public static readonly CMethodId K_LZMA = new CMethodId(K_LZMA_ID);
|
||||
public static readonly CMethodId K_LZMA2 = new CMethodId(K_LZMA2_ID);
|
||||
public static readonly CMethodId K_AES = new CMethodId(K_AES_ID);
|
||||
public static readonly CMethodId K_COPY = new(K_COPY_ID);
|
||||
public static readonly CMethodId K_LZMA = new(K_LZMA_ID);
|
||||
public static readonly CMethodId K_LZMA2 = new(K_LZMA2_ID);
|
||||
public static readonly CMethodId K_AES = new(K_AES_ID);
|
||||
|
||||
public readonly ulong _id;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SevenZipEntry : Entry
|
||||
|
||||
public override long Crc => FilePart.Header.Crc ?? 0;
|
||||
|
||||
public override string Key => FilePart.Header.Name;
|
||||
public override string? Key => FilePart.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
@@ -38,5 +38,8 @@ public class SevenZipEntry : Entry
|
||||
public override int? Attrib =>
|
||||
FilePart.Header.Attrib.HasValue ? (int?)FilePart.Header.Attrib.Value : null;
|
||||
|
||||
public int? ExtendedAttrib =>
|
||||
FilePart.Header.ExtendedAttrib.HasValue ? (int?)FilePart.Header.ExtendedAttrib.Value : null;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => FilePart.AsEnumerable<FilePart>();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.IO;
|
||||
@@ -41,7 +40,7 @@ internal class SevenZipFilePart : FilePart
|
||||
{
|
||||
if (!Header.HasStream)
|
||||
{
|
||||
return null!;
|
||||
return Stream.Null;
|
||||
}
|
||||
var folderStream = _database.GetFolderStream(_stream, Folder!, _database.PasswordProvider);
|
||||
|
||||
@@ -73,29 +72,24 @@ internal class SevenZipFilePart : FilePart
|
||||
private const uint K_PPMD = 0x030401;
|
||||
private const uint K_B_ZIP2 = 0x040202;
|
||||
|
||||
internal CompressionType GetCompression()
|
||||
private CompressionType GetCompression()
|
||||
{
|
||||
var coder = Folder!._coders.First();
|
||||
switch (coder._methodId._id)
|
||||
if (Header.IsDir)
|
||||
{
|
||||
case K_LZMA:
|
||||
case K_LZMA2:
|
||||
{
|
||||
return CompressionType.LZMA;
|
||||
}
|
||||
case K_PPMD:
|
||||
{
|
||||
return CompressionType.PPMd;
|
||||
}
|
||||
case K_B_ZIP2:
|
||||
{
|
||||
return CompressionType.BZip2;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
return CompressionType.None;
|
||||
}
|
||||
|
||||
var coder = Folder.NotNull()._coders.First();
|
||||
return coder._methodId._id switch
|
||||
{
|
||||
K_LZMA or K_LZMA2 => CompressionType.LZMA,
|
||||
K_PPMD => CompressionType.PPMd,
|
||||
K_B_ZIP2 => CompressionType.BZip2,
|
||||
_ => throw new InvalidFormatException(),
|
||||
};
|
||||
}
|
||||
|
||||
internal bool IsEncrypted =>
|
||||
Folder!._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1;
|
||||
!Header.IsDir
|
||||
&& Folder?._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1;
|
||||
}
|
||||
|
||||
48
src/SharpCompress/Common/SharpCompressException.cs
Normal file
48
src/SharpCompress/Common/SharpCompressException.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
public class SharpCompressException : Exception
|
||||
{
|
||||
public SharpCompressException() { }
|
||||
|
||||
public SharpCompressException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public SharpCompressException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
|
||||
public class ArchiveException(string message) : SharpCompressException(message);
|
||||
|
||||
public class IncompleteArchiveException(string message) : ArchiveException(message);
|
||||
|
||||
public class CryptographicException(string message) : SharpCompressException(message);
|
||||
|
||||
public class ReaderCancelledException(string message) : SharpCompressException(message);
|
||||
|
||||
public class ExtractionException : SharpCompressException
|
||||
{
|
||||
public ExtractionException() { }
|
||||
|
||||
public ExtractionException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public ExtractionException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
|
||||
public class MultipartStreamRequiredException(string message) : ExtractionException(message);
|
||||
|
||||
public class MultiVolumeExtractionException(string message) : ExtractionException(message);
|
||||
|
||||
public class InvalidFormatException : ExtractionException
|
||||
{
|
||||
public InvalidFormatException() { }
|
||||
|
||||
public InvalidFormatException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public InvalidFormatException(string message, Exception inner)
|
||||
: base(message, inner) { }
|
||||
}
|
||||
@@ -14,5 +14,5 @@ internal enum EntryType : byte
|
||||
LongName = (byte)'L',
|
||||
SparseFile = (byte)'S',
|
||||
VolumeHeader = (byte)'V',
|
||||
GlobalExtendedHeader = (byte)'g'
|
||||
GlobalExtendedHeader = (byte)'g',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
@@ -9,12 +7,12 @@ namespace SharpCompress.Common.Tar.Headers;
|
||||
|
||||
internal sealed class TarHeader
|
||||
{
|
||||
internal static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
internal static readonly DateTime EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public TarHeader(ArchiveEncoding archiveEncoding) => ArchiveEncoding = archiveEncoding;
|
||||
|
||||
internal string Name { get; set; }
|
||||
internal string LinkName { get; set; }
|
||||
internal string? Name { get; set; }
|
||||
internal string? LinkName { get; set; }
|
||||
|
||||
internal long Mode { get; set; }
|
||||
internal long UserId { get; set; }
|
||||
@@ -22,7 +20,7 @@ internal sealed class TarHeader
|
||||
internal long Size { get; set; }
|
||||
internal DateTime LastModifiedTime { get; set; }
|
||||
internal EntryType EntryType { get; set; }
|
||||
internal Stream PackedStream { get; set; }
|
||||
internal Stream? PackedStream { get; set; }
|
||||
internal ArchiveEncoding ArchiveEncoding { get; }
|
||||
|
||||
internal const int BLOCK_SIZE = 512;
|
||||
@@ -36,7 +34,9 @@ internal sealed class TarHeader
|
||||
WriteOctalBytes(0, buffer, 116, 8); // group ID
|
||||
|
||||
//ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257);
|
||||
var nameByteCount = ArchiveEncoding.GetEncoding().GetByteCount(Name);
|
||||
var nameByteCount = ArchiveEncoding
|
||||
.GetEncoding()
|
||||
.GetByteCount(Name.NotNull("Name is null"));
|
||||
if (nameByteCount > 100)
|
||||
{
|
||||
// Set mock filename and filetype to indicate the next block is the actual name of the file
|
||||
@@ -46,7 +46,7 @@ internal sealed class TarHeader
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStringBytes(ArchiveEncoding.Encode(Name), buffer, 100);
|
||||
WriteStringBytes(ArchiveEncoding.Encode(Name.NotNull("Name is null")), buffer, 100);
|
||||
WriteOctalBytes(Size, buffer, 124, 12);
|
||||
var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds;
|
||||
WriteOctalBytes(time, buffer, 136, 12);
|
||||
@@ -77,7 +77,7 @@ internal sealed class TarHeader
|
||||
//
|
||||
// and then infinite recursion is occured in WriteLongFilenameHeader because truncated.Length is 102.
|
||||
Name = ArchiveEncoding.Decode(
|
||||
ArchiveEncoding.Encode(Name),
|
||||
ArchiveEncoding.Encode(Name.NotNull("Name is null")),
|
||||
0,
|
||||
100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1)
|
||||
);
|
||||
@@ -87,7 +87,7 @@ internal sealed class TarHeader
|
||||
|
||||
private void WriteLongFilenameHeader(Stream output)
|
||||
{
|
||||
var nameBytes = ArchiveEncoding.Encode(Name);
|
||||
var nameBytes = ArchiveEncoding.Encode(Name.NotNull("Name is null"));
|
||||
output.Write(nameBytes, 0, nameBytes.Length);
|
||||
|
||||
// pad to multiple of BlockSize bytes, and make sure a terminating null is added
|
||||
@@ -101,57 +101,85 @@ internal sealed class TarHeader
|
||||
|
||||
internal bool Read(BinaryReader reader)
|
||||
{
|
||||
var buffer = ReadBlock(reader);
|
||||
if (buffer.Length == 0)
|
||||
string? longName = null;
|
||||
string? longLinkName = null;
|
||||
var hasLongValue = true;
|
||||
byte[] buffer;
|
||||
EntryType entryType;
|
||||
|
||||
do
|
||||
{
|
||||
buffer = ReadBlock(reader);
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entryType = ReadEntryType(buffer);
|
||||
|
||||
// LongName and LongLink headers can follow each other and need
|
||||
// to apply to the header that follows them.
|
||||
if (entryType == EntryType.LongName)
|
||||
{
|
||||
longName = ReadLongName(reader, buffer);
|
||||
continue;
|
||||
}
|
||||
else if (entryType == EntryType.LongLink)
|
||||
{
|
||||
longLinkName = ReadLongName(reader, buffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
hasLongValue = false;
|
||||
} while (hasLongValue);
|
||||
|
||||
// Check header checksum
|
||||
if (!checkChecksum(buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// for symlinks, additionally read the linkname
|
||||
if (ReadEntryType(buffer) == EntryType.SymLink)
|
||||
{
|
||||
LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls();
|
||||
}
|
||||
|
||||
if (ReadEntryType(buffer) == EntryType.LongName)
|
||||
{
|
||||
Name = ReadLongName(reader, buffer);
|
||||
buffer = ReadBlock(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
|
||||
}
|
||||
|
||||
EntryType = ReadEntryType(buffer);
|
||||
Name = longName ?? ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
|
||||
EntryType = entryType;
|
||||
Size = ReadSize(buffer);
|
||||
|
||||
// for symlinks, additionally read the linkname
|
||||
if (entryType == EntryType.SymLink || entryType == EntryType.HardLink)
|
||||
{
|
||||
LinkName = longLinkName ?? ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls();
|
||||
}
|
||||
|
||||
Mode = ReadAsciiInt64Base8(buffer, 100, 7);
|
||||
if (EntryType == EntryType.Directory)
|
||||
|
||||
if (entryType == EntryType.Directory)
|
||||
{
|
||||
Mode |= 0b1_000_000_000;
|
||||
}
|
||||
|
||||
UserId = ReadAsciiInt64Base8(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8(buffer, 116, 7);
|
||||
var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
UserId = ReadAsciiInt64Base8oldGnu(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8oldGnu(buffer, 116, 7);
|
||||
|
||||
var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(Magic) && "ustar".Equals(Magic))
|
||||
{
|
||||
var namePrefix = ArchiveEncoding.Decode(buffer, 345, 157);
|
||||
namePrefix = namePrefix.TrimNulls();
|
||||
var namePrefix = ArchiveEncoding.Decode(buffer, 345, 157).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(namePrefix))
|
||||
{
|
||||
Name = namePrefix + "/" + Name;
|
||||
}
|
||||
}
|
||||
if (EntryType != EntryType.LongName && Name.Length == 0)
|
||||
|
||||
if (entryType != EntryType.LongName && Name.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -188,7 +216,7 @@ internal sealed class TarHeader
|
||||
|
||||
if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE)
|
||||
{
|
||||
throw new InvalidOperationException("Buffer is invalid size");
|
||||
throw new InvalidFormatException("Buffer is invalid size");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
@@ -249,6 +277,24 @@ internal sealed class TarHeader
|
||||
return Convert.ToInt64(s, 8);
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64Base8oldGnu(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer[offset] == 0x80 && buffer[offset + 1] == 0x00)
|
||||
{
|
||||
return buffer[offset + 4] << 24
|
||||
| buffer[offset + 5] << 16
|
||||
| buffer[offset + 6] << 8
|
||||
| buffer[offset + 7];
|
||||
}
|
||||
var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return Convert.ToInt64(s, 8);
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
@@ -268,9 +314,45 @@ internal sealed class TarHeader
|
||||
(byte)' ',
|
||||
(byte)' ',
|
||||
(byte)' ',
|
||||
(byte)' '
|
||||
(byte)' ',
|
||||
};
|
||||
|
||||
internal static bool checkChecksum(byte[] buf)
|
||||
{
|
||||
const int eightSpacesChksum = 256;
|
||||
var buffer = new Span<byte>(buf).Slice(0, 512);
|
||||
int posix_sum = eightSpacesChksum;
|
||||
int sun_sum = eightSpacesChksum;
|
||||
|
||||
foreach (byte b in buffer)
|
||||
{
|
||||
posix_sum += b;
|
||||
sun_sum += unchecked((sbyte)b);
|
||||
}
|
||||
|
||||
// Special case, empty file header
|
||||
if (posix_sum == eightSpacesChksum)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove current checksum from calculation
|
||||
foreach (byte b in buffer.Slice(148, 8))
|
||||
{
|
||||
posix_sum -= b;
|
||||
sun_sum -= unchecked((sbyte)b);
|
||||
}
|
||||
|
||||
// Read and compare checksum for header
|
||||
var crc = ReadAsciiInt64Base8(buf, 148, 7);
|
||||
if (crc != posix_sum && crc != sun_sum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static int RecalculateChecksum(byte[] buf)
|
||||
{
|
||||
// Set default value for checksum. That is 8 spaces.
|
||||
@@ -305,5 +387,5 @@ internal sealed class TarHeader
|
||||
|
||||
public long? DataStartPosition { get; set; }
|
||||
|
||||
public string Magic { get; set; }
|
||||
public string? Magic { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -10,9 +8,9 @@ namespace SharpCompress.Common.Tar;
|
||||
|
||||
public class TarEntry : Entry
|
||||
{
|
||||
private readonly TarFilePart _filePart;
|
||||
private readonly TarFilePart? _filePart;
|
||||
|
||||
internal TarEntry(TarFilePart filePart, CompressionType type)
|
||||
internal TarEntry(TarFilePart? filePart, CompressionType type)
|
||||
{
|
||||
_filePart = filePart;
|
||||
CompressionType = type;
|
||||
@@ -22,15 +20,15 @@ public class TarEntry : Entry
|
||||
|
||||
public override long Crc => 0;
|
||||
|
||||
public override string Key => _filePart.Header.Name;
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string LinkTarget => _filePart.Header.LinkName;
|
||||
public override string? LinkTarget => _filePart?.Header.LinkName;
|
||||
|
||||
public override long CompressedSize => _filePart.Header.Size;
|
||||
public override long CompressedSize => _filePart?.Header.Size ?? 0;
|
||||
|
||||
public override long Size => _filePart.Header.Size;
|
||||
public override long Size => _filePart?.Header.Size ?? 0;
|
||||
|
||||
public override DateTime? LastModifiedTime => _filePart.Header.LastModifiedTime;
|
||||
public override DateTime? LastModifiedTime => _filePart?.Header.LastModifiedTime;
|
||||
|
||||
public override DateTime? CreatedTime => null;
|
||||
|
||||
@@ -40,17 +38,17 @@ public class TarEntry : Entry
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => _filePart.Header.EntryType == EntryType.Directory;
|
||||
public override bool IsDirectory => _filePart?.Header.EntryType == EntryType.Directory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
public long Mode => _filePart.Header.Mode;
|
||||
public long Mode => _filePart?.Header.Mode ?? 0;
|
||||
|
||||
public long UserID => _filePart.Header.UserId;
|
||||
public long UserID => _filePart?.Header.UserId ?? 0;
|
||||
|
||||
public long GroupId => _filePart.Header.GroupId;
|
||||
public long GroupId => _filePart?.Header.GroupId ?? 0;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.AsEnumerable<FilePart>();
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
|
||||
internal static IEnumerable<TarEntry> GetEntries(
|
||||
StreamingMode mode,
|
||||
@@ -59,17 +57,17 @@ public class TarEntry : Entry
|
||||
ArchiveEncoding archiveEncoding
|
||||
)
|
||||
{
|
||||
foreach (var h in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding))
|
||||
foreach (var header in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding))
|
||||
{
|
||||
if (h != null)
|
||||
if (header != null)
|
||||
{
|
||||
if (mode == StreamingMode.Seekable)
|
||||
{
|
||||
yield return new TarEntry(new TarFilePart(h, stream), compressionType);
|
||||
yield return new TarEntry(new TarFilePart(header, stream), compressionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new TarEntry(new TarFilePart(h, null), compressionType);
|
||||
yield return new TarEntry(new TarFilePart(header, null), compressionType);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -5,9 +5,9 @@ namespace SharpCompress.Common.Tar;
|
||||
|
||||
internal sealed class TarFilePart : FilePart
|
||||
{
|
||||
private readonly Stream _seekableStream;
|
||||
private readonly Stream? _seekableStream;
|
||||
|
||||
internal TarFilePart(TarHeader header, Stream seekableStream)
|
||||
internal TarFilePart(TarHeader header, Stream? seekableStream)
|
||||
: base(header.ArchiveEncoding)
|
||||
{
|
||||
_seekableStream = seekableStream;
|
||||
@@ -16,16 +16,16 @@ internal sealed class TarFilePart : FilePart
|
||||
|
||||
internal TarHeader Header { get; }
|
||||
|
||||
internal override string FilePartName => Header.Name;
|
||||
internal override string? FilePartName => Header?.Name;
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_seekableStream != null)
|
||||
{
|
||||
_seekableStream.Position = Header.DataStartPosition!.Value;
|
||||
_seekableStream.Position = Header.DataStartPosition ?? 0;
|
||||
return new TarReadOnlySubStream(_seekableStream, Header.Size);
|
||||
}
|
||||
return Header.PackedStream;
|
||||
return Header.PackedStream.NotNull();
|
||||
}
|
||||
|
||||
internal override Stream? GetRawStream() => null;
|
||||
|
||||
@@ -28,7 +28,6 @@ internal static class TarHeaderFactory
|
||||
switch (mode)
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
|
||||
{
|
||||
header.DataStartPosition = reader.BaseStream.Position;
|
||||
|
||||
@@ -37,7 +36,6 @@ internal static class TarHeaderFactory
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
|
||||
{
|
||||
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,41 @@
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
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.
|
||||
@@ -47,7 +74,7 @@ internal class TarReadOnlySubStream : NonDisposingStream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
@@ -63,7 +90,7 @@ internal class TarReadOnlySubStream : NonDisposingStream
|
||||
{
|
||||
count = (int)BytesLeftToRead;
|
||||
}
|
||||
int read = Stream.Read(buffer, offset, count);
|
||||
var read = Stream.Read(buffer, offset, count);
|
||||
if (read > 0)
|
||||
{
|
||||
BytesLeftToRead -= read;
|
||||
@@ -78,7 +105,7 @@ internal class TarReadOnlySubStream : NonDisposingStream
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int value = Stream.ReadByte();
|
||||
var value = Stream.ReadByte();
|
||||
if (value != -1)
|
||||
{
|
||||
--BytesLeftToRead;
|
||||
|
||||
@@ -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)
|
||||
internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0)
|
||||
{
|
||||
Index = index;
|
||||
ReaderOptions = readerOptions;
|
||||
if (readerOptions.LeaveStreamOpen)
|
||||
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.
|
||||
|
||||
@@ -14,8 +14,8 @@ internal class DirectoryEntryHeader : ZipFileEntry
|
||||
VersionNeededToExtract = reader.ReadUInt16();
|
||||
Flags = (HeaderFlags)reader.ReadUInt16();
|
||||
CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16();
|
||||
LastModifiedTime = reader.ReadUInt16();
|
||||
LastModifiedDate = reader.ReadUInt16();
|
||||
OriginalLastModifiedTime = LastModifiedTime = reader.ReadUInt16();
|
||||
OriginalLastModifiedDate = LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
@@ -52,8 +52,8 @@ internal class DirectoryEntryHeader : ZipFileEntry
|
||||
|
||||
LoadExtra(extra);
|
||||
|
||||
var unicodePathExtra = Extra.FirstOrDefault(
|
||||
u => u.Type == ExtraDataType.UnicodePathExtraField
|
||||
var unicodePathExtra = Extra.FirstOrDefault(u =>
|
||||
u.Type == ExtraDataType.UnicodePathExtraField
|
||||
);
|
||||
if (unicodePathExtra != null && ArchiveEncoding.Forced == null)
|
||||
{
|
||||
@@ -85,6 +85,36 @@ internal class DirectoryEntryHeader : ZipFileEntry
|
||||
RelativeOffsetOfEntryHeader = zip64ExtraData.RelativeOffsetOfEntryHeader;
|
||||
}
|
||||
}
|
||||
|
||||
var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);
|
||||
|
||||
if (unixTimeExtra is not null)
|
||||
{
|
||||
// Tuple order is last modified time, last access time, and creation time.
|
||||
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;
|
||||
|
||||
if (unixTimeTuple.Item1.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item2.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item3.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort Version { get; private set; }
|
||||
@@ -93,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; }
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ internal enum HeaderFlags : ushort
|
||||
EnhancedDeflate = 16,
|
||||
|
||||
//Bit 11: Language encoding flag
|
||||
Efs = 2048
|
||||
Efs = 2048,
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ internal class LocalEntryHeader : ZipFileEntry
|
||||
Version = reader.ReadUInt16();
|
||||
Flags = (HeaderFlags)reader.ReadUInt16();
|
||||
CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16();
|
||||
LastModifiedTime = reader.ReadUInt16();
|
||||
LastModifiedDate = reader.ReadUInt16();
|
||||
OriginalLastModifiedTime = LastModifiedTime = reader.ReadUInt16();
|
||||
OriginalLastModifiedDate = LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
@@ -42,8 +42,8 @@ internal class LocalEntryHeader : ZipFileEntry
|
||||
|
||||
LoadExtra(extra);
|
||||
|
||||
var unicodePathExtra = Extra.FirstOrDefault(
|
||||
u => u.Type == ExtraDataType.UnicodePathExtraField
|
||||
var unicodePathExtra = Extra.FirstOrDefault(u =>
|
||||
u.Type == ExtraDataType.UnicodePathExtraField
|
||||
);
|
||||
if (unicodePathExtra != null && ArchiveEncoding.Forced == null)
|
||||
{
|
||||
@@ -64,6 +64,36 @@ internal class LocalEntryHeader : ZipFileEntry
|
||||
UncompressedSize = zip64ExtraData.UncompressedSize;
|
||||
}
|
||||
}
|
||||
|
||||
var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);
|
||||
|
||||
if (unixTimeExtra is not null)
|
||||
{
|
||||
// Tuple order is last modified time, last access time, and creation time.
|
||||
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;
|
||||
|
||||
if (unixTimeTuple.Item1.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item2.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item3.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort Version { get; private set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
@@ -13,7 +13,8 @@ internal enum ExtraDataType : ushort
|
||||
// Third Party Mappings
|
||||
// -Info-ZIP Unicode Path Extra Field
|
||||
UnicodePathExtraField = 0x7075,
|
||||
Zip64ExtendedInformationExtraField = 0x0001
|
||||
Zip64ExtendedInformationExtraField = 0x0001,
|
||||
UnixTimeExtraField = 0x5455,
|
||||
}
|
||||
|
||||
internal class ExtraData
|
||||
@@ -145,15 +146,106 @@ internal sealed class Zip64ExtendedInformationExtraField : ExtraData
|
||||
public uint VolumeNumber { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed class UnixTimeExtraField : ExtraData
|
||||
{
|
||||
public UnixTimeExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
|
||||
: base(type, length, dataBytes) { }
|
||||
|
||||
/// <summary>
|
||||
/// The unix modified time, last access time, and creation time, if set.
|
||||
/// </summary>
|
||||
/// <remarks>Must return Tuple explicitly due to net462 support.</remarks>
|
||||
internal Tuple<DateTime?, DateTime?, DateTime?> UnicodeTimes
|
||||
{
|
||||
get
|
||||
{
|
||||
// There has to be at least 5 byte for there to be a timestamp.
|
||||
// 1 byte for flags and 4 bytes for a timestamp.
|
||||
if (DataBytes is null || DataBytes.Length < 5)
|
||||
{
|
||||
return Tuple.Create<DateTime?, DateTime?, DateTime?>(null, null, null);
|
||||
}
|
||||
|
||||
var flags = (RecordedTimeFlag)DataBytes[0];
|
||||
var isModifiedTimeSpecified = flags.HasFlag(RecordedTimeFlag.LastModified);
|
||||
var isLastAccessTimeSpecified = flags.HasFlag(RecordedTimeFlag.LastAccessed);
|
||||
var isCreationTimeSpecified = flags.HasFlag(RecordedTimeFlag.Created);
|
||||
var currentIndex = 1;
|
||||
DateTime? modifiedTime = null;
|
||||
DateTime? lastAccessTime = null;
|
||||
DateTime? creationTime = null;
|
||||
|
||||
if (isModifiedTimeSpecified)
|
||||
{
|
||||
var modifiedEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
modifiedTime = DateTimeOffset.FromUnixTimeSeconds(modifiedEpochTime).UtcDateTime;
|
||||
}
|
||||
|
||||
if (isLastAccessTimeSpecified)
|
||||
{
|
||||
if (currentIndex + 4 > DataBytes.Length)
|
||||
{
|
||||
return Tuple.Create<DateTime?, DateTime?, DateTime?>(null, null, null);
|
||||
}
|
||||
|
||||
var lastAccessEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
lastAccessTime = DateTimeOffset
|
||||
.FromUnixTimeSeconds(lastAccessEpochTime)
|
||||
.UtcDateTime;
|
||||
}
|
||||
|
||||
if (isCreationTimeSpecified)
|
||||
{
|
||||
if (currentIndex + 4 > DataBytes.Length)
|
||||
{
|
||||
return Tuple.Create<DateTime?, DateTime?, DateTime?>(null, null, null);
|
||||
}
|
||||
|
||||
var creationTimeEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
creationTime = DateTimeOffset
|
||||
.FromUnixTimeSeconds(creationTimeEpochTime)
|
||||
.UtcDateTime;
|
||||
}
|
||||
|
||||
return Tuple.Create(modifiedTime, lastAccessTime, creationTime);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum RecordedTimeFlag
|
||||
{
|
||||
None = 0,
|
||||
LastModified = 1,
|
||||
LastAccessed = 2,
|
||||
Created = 4,
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LocalEntryHeaderExtraFactory
|
||||
{
|
||||
internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extraData) =>
|
||||
type switch
|
||||
{
|
||||
ExtraDataType.UnicodePathExtraField
|
||||
=> new ExtraUnicodePathExtraField(type, length, extraData),
|
||||
ExtraDataType.Zip64ExtendedInformationExtraField
|
||||
=> new Zip64ExtendedInformationExtraField(type, length, extraData),
|
||||
_ => new ExtraData(type, length, extraData)
|
||||
ExtraDataType.UnicodePathExtraField => new ExtraUnicodePathExtraField(
|
||||
type,
|
||||
length,
|
||||
extraData
|
||||
),
|
||||
ExtraDataType.Zip64ExtendedInformationExtraField =>
|
||||
new Zip64ExtendedInformationExtraField(type, length, extraData),
|
||||
ExtraDataType.UnixTimeExtraField => new UnixTimeExtraField(type, length, extraData),
|
||||
_ => new ExtraData(type, length, extraData),
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user