diff --git a/.codacy.yaml b/.codacy.yaml
new file mode 100644
index 000000000..28293b226
--- /dev/null
+++ b/.codacy.yaml
@@ -0,0 +1,3 @@
+---
+exclude_paths:
+ - 'docs/**/*'
diff --git a/.editorconfig b/.editorconfig
index 92cbdb1d6..5b84cc53b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -33,3 +33,7 @@ indent_size = 2
end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 000000000..463b5d6fb
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,165 @@
+name: Build and Test
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Tag Ref'
+ required: true
+ pull_request:
+ branches: [ master ]
+ push:
+ branches: [ master ]
+ release:
+
+jobs:
+ Build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ target: [netstandard2.0, netstandard2.1, net6.0]
+ env:
+ LIB_PROJ: src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.events.inputs.tag }}
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Show .NET info
+ run: dotnet --info
+
+ - name: Build library (Debug)
+ run: dotnet build -c debug -f ${{ matrix.target }} ${{ env.LIB_PROJ }}
+
+ - name: Build library (Release)
+ run: dotnet build -c release -f ${{ matrix.target }} ${{ env.LIB_PROJ }}
+
+ Test:
+ runs-on: ${{ matrix.os }}-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # Windows testing is combined with code coverage
+ os: [ubuntu, macos]
+ target: [net6.0]
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET Core
+ if: matrix.target == 'net6.0'
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Restore test dependencies
+ run: dotnet restore
+
+ - name: Run tests (Debug)
+ run: dotnet test -c debug -f ${{ matrix.target }} --no-restore
+
+ - name: Run tests (Release)
+ run: dotnet test -c release -f ${{ matrix.target }} --no-restore
+
+
+ CodeCov:
+ name: Code Coverage
+ runs-on: windows-2019
+ env:
+ DOTCOVER_VER: 2021.1.2
+ DOTCOVER_PKG: jetbrains.dotcover.commandlinetools
+ COVER_SNAPSHOT: SharpZipLib.dcvr
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+
+ # NOTE: This is the temporary fix for https://github.com/actions/virtual-environments/issues/1090
+ - name: Cleanup before restore
+ run: dotnet clean ICSharpCode.SharpZipLib.sln && dotnet nuget locals all --clear
+
+ - name: Install codecov
+ run: nuget install -o tools -version ${{env.DOTCOVER_VER}} ${{env.DOTCOVER_PKG}}
+
+ - name: Add dotcover to path
+ run: echo "$(pwd)\tools\${{env.DOTCOVER_PKG}}.${{env.DOTCOVER_VER}}\tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+
+ - name: Run tests with code coverage
+ run: dotcover dotnet --output=${{env.COVER_SNAPSHOT}} --filters=-:ICSharpCode.SharpZipLib.Tests -- test -c release
+
+ - name: Create code coverage report
+ run: dotcover report --source=${{env.COVER_SNAPSHOT}} --reporttype=detailedxml --output=dotcover-report.xml
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1.2.2
+ with:
+ files: dotcover-report.xml
+
+ - name: Upload coverage snapshot artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: Code coverage snapshot
+ path: ${{env.COVER_SNAPSHOT}}
+
+ Pack:
+ needs: [Build, Test, CodeCov]
+ runs-on: windows-latest
+ env:
+ PKG_SUFFIX: ''
+ PKG_PROJ: src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+ PKG_PROPS: '/p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true'
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.events.inputs.tag }}
+ fetch-depth: 0
+
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Build library for .NET Standard 2.0
+ run: dotnet build -c Release -f netstandard2.0 ${{ env.PKG_PROPS }} ${{ env.PKG_PROJ }}
+ - name: Build library for .NET Standard 2.1
+ run: dotnet build -c Release -f netstandard2.1 ${{ env.PKG_PROPS }} ${{ env.PKG_PROJ }}
+ - name: Build library for .NET 6.0
+ run: dotnet build -c Release -f net6.0 ${{ env.PKG_PROPS }} ${{ env.PKG_PROJ }}
+
+ - name: Add PR suffix to package
+ if: ${{ github.event_name == 'pull_request' }}
+ run: echo "PKG_SUFFIX=-PR" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+ - name: Set package version
+ continue-on-error: true
+ run: |-
+ $PKG_GIT_VERSION="$(git describe --tags --abbrev | % { $_.substring(1) })"
+ Write-Output "::notice::Git describe: $PKG_GIT_VERSION"
+ Write-Output "::notice::Package suffix: $env:PKG_SUFFIX"
+ $PKG_VERSION = "${PKG_GIT_VERSION}${env:PKG_SUFFIX}"
+ Write-Output "::notice::Package version: $PKG_VERSION"
+ Write-Output "PKG_VERSION=$PKG_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+ - name: Create nuget package
+ run: dotnet pack ${{ env.PKG_PROJ }} -c Release --output dist ${{ env.PKG_PROPS }} /p:Version=${{ env.PKG_VERSION }}
+
+ - name: Upload nuget package artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: Nuget package
+ path: dist/*.nupkg
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..549469d55
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '40 7 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'csharp' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..388f7f5e9
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,63 @@
+# Workflow to execute when a new version is released
+name: Release
+
+on:
+ release:
+
+ # Used for testing and manual execution
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Tag Ref'
+ required: true
+
+jobs:
+ build:
+ runs-on: windows-latest
+ name: Generate DocFX documentation
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.events.inputs.tag }}
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Build project
+ run: dotnet build -f netstandard2.0 src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+
+ - name: Install docfx
+ run: choco install docfx
+
+ - name: Build Documentation
+ run: docfx docs/help/docfx.json --warningsAsErrors
+
+ - name: Upload documentation as artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: site
+ path: docs/help/_site
+
+ deploy:
+ needs: [build] # The second job must depend on the first one to complete before running and uses ubuntu-latest instead of windows.
+ runs-on: ubuntu-latest
+ name: Update github pages docs
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Download Artifacts # The built project is downloaded into the 'site' folder.
+ uses: actions/download-artifact@v1
+ with:
+ name: site
+
+ - name: Publish documentation to Github Pages
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ branch: gh-pages
+ folder: site
+ target-folder: help
+ clean: false
diff --git a/.gitignore b/.gitignore
index 7bc034335..e5f2a21e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -253,3 +253,4 @@ paket-files/
/test/ICSharpCode.SharpZipLib.TestBootstrapper/Properties/launchSettings.json
_testRunner/
docs/help/api/.manifest
+/benchmark/ICSharpCode.SharpZipLib.Benchmark/BenchmarkDotNet.Artifacts/results
diff --git a/.travis.yml b/.travis.yml
index 9b54b3f9d..1e76533f4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,14 +15,14 @@ install:
# - nuget restore ICSharpCode.SharpZipLib.sln
# - nuget install NUnit.Console -Version 3.8.0 -OutputDirectory _testRunner
script:
- - dotnet build -f netstandard2 src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+ - dotnet build -f netstandard2.0 src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
- dotnet run -c Debug -f netcoreapp2 -p test/ICSharpCode.SharpZipLib.TestBootstrapper/ICSharpCode.SharpZipLib.TestBootstrapper.csproj -- --where "class !~ WindowsNameTransformHandling & test !~ ZipEntryFactoryHandling.CreatedValues & test !~ ZipNameTransformHandling.FilenameCleaning" --result=docs/nunit3-test-results-debug.xml
- dotnet run -c Release -f netcoreapp2 -p test/ICSharpCode.SharpZipLib.TestBootstrapper/ICSharpCode.SharpZipLib.TestBootstrapper.csproj -- --where "class !~ WindowsNameTransformHandling & test !~ ZipEntryFactoryHandling.CreatedValues & test !~ ZipNameTransformHandling.FilenameCleaning" --result=docs\nunit3-test-results-release.xml
# - dotnet test test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.Tests.csproj
# - xbuild /p:Configuration=Release ICSharpCode.SharpZipLib.sln
# - mono ./packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe --framework=mono-4.0 --labels=All --result=./Documentation/nunit3-test-results-travis.xml ./bin/Release/ICSharpCode.SharpZipLib.Tests.dll
after_script:
- - dotnet pack -f netstandard2 -o _dist/ src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+ - dotnet pack -f netstandard2.0 -o _dist/ src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
#cache:
# directories:
# - bin
diff --git a/ICSharpCode.SharpZipLib.sln b/ICSharpCode.SharpZipLib.sln
index cab9675b5..0c4c6c5f4 100644
--- a/ICSharpCode.SharpZipLib.sln
+++ b/ICSharpCode.SharpZipLib.sln
@@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib.Tests", "test\ICSharpCode.SharpZipLib.Tests\ICSharpCode.SharpZipLib.Tests.csproj", "{82211166-9C45-4603-8E3A-2CA2EFFCBC26}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib.TestBootstrapper", "test\ICSharpCode.SharpZipLib.TestBootstrapper\ICSharpCode.SharpZipLib.TestBootstrapper.csproj", "{535D7365-C5B1-4253-9233-D72D972CA851}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib.Benchmark", "benchmark\ICSharpCode.SharpZipLib.Benchmark\ICSharpCode.SharpZipLib.Benchmark.csproj", "{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}"
EndProject
Global
@@ -33,10 +31,6 @@ Global
{82211166-9C45-4603-8E3A-2CA2EFFCBC26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82211166-9C45-4603-8E3A-2CA2EFFCBC26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82211166-9C45-4603-8E3A-2CA2EFFCBC26}.Release|Any CPU.Build.0 = Release|Any CPU
- {535D7365-C5B1-4253-9233-D72D972CA851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {535D7365-C5B1-4253-9233-D72D972CA851}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {535D7365-C5B1-4253-9233-D72D972CA851}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {535D7365-C5B1-4253-9233-D72D972CA851}.Release|Any CPU.Build.0 = Release|Any CPU
{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/README.md b/README.md
index b6a9559d1..5f09f1f01 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,4 @@
-# SharpZipLib [](https://ci.appveyor.com/project/icsharpcode/sharpziplib/branch/master) [](https://gitter.im/icsharpcode/SharpZipLib?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-
-The SharpZipLib project is looking for a new maintainer - please read [State of the Union August 2017](https://github.com/icsharpcode/SharpZipLib/issues/187)
+# SharpZipLib [](https://github.com/icsharpcode/SharpZipLib/actions/workflows/build-test.yml) [](https://www.nuget.org/packages/SharpZipLib/) [](https://openupm.com/packages/org.icsharpcode.sharpziplib/)
Introduction
------------
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..1d4f9f0e4
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,12 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 1.3.x | :white_check_mark: |
+| < 1.3 | :x: |
+
+## Reporting a Vulnerability
+
+Send an e-mail to sharpziplib@containrrr.dev
diff --git a/appveyor.yml b/appveyor.yml
index a6197ff29..df3ffb4ba 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,5 +1,5 @@
version: '{build}'
-image: Visual Studio 2017
+image: Visual Studio 2019
configuration:
- Debug
- Release
@@ -31,4 +31,4 @@ test_script:
artifacts:
- path: docs\help\_site
type: zip
- name: Documentation
\ No newline at end of file
+ name: Documentation
diff --git a/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.snk b/assets/ICSharpCode.SharpZipLib.snk
similarity index 100%
rename from src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.snk
rename to assets/ICSharpCode.SharpZipLib.snk
diff --git a/assets/sharpziplib-nuget-256x256.png b/assets/sharpziplib-nuget-256x256.png
new file mode 100644
index 000000000..60b3a8dae
Binary files /dev/null and b/assets/sharpziplib-nuget-256x256.png differ
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/BZip2/BZip2InputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/BZip2/BZip2InputStream.cs
new file mode 100644
index 000000000..8d5a7ccc2
--- /dev/null
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/BZip2/BZip2InputStream.cs
@@ -0,0 +1,37 @@
+using System;
+using System.IO;
+using BenchmarkDotNet.Attributes;
+
+namespace ICSharpCode.SharpZipLib.Benchmark.BZip2
+{
+ [Config(typeof(MultipleRuntimes))]
+ public class BZip2InputStream
+ {
+ private byte[] compressedData;
+
+ public BZip2InputStream()
+ {
+ var outputMemoryStream = new MemoryStream();
+ using (var outputStream = new SharpZipLib.BZip2.BZip2OutputStream(outputMemoryStream))
+ {
+ var random = new Random(1234);
+ var inputData = new byte[1024 * 1024 * 30];
+ random.NextBytes(inputData);
+ var inputMemoryStream = new MemoryStream(inputData);
+ inputMemoryStream.CopyTo(outputStream);
+ }
+
+ compressedData = outputMemoryStream.ToArray();
+ }
+
+ [Benchmark]
+ public void DecompressData()
+ {
+ var memoryStream = new MemoryStream(compressedData);
+ using (var inputStream = new SharpZipLib.BZip2.BZip2InputStream(memoryStream))
+ {
+ inputStream.CopyTo(Stream.Null);
+ }
+ }
+ }
+}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj b/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
index 4991a9ad1..7688d0ff2 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp2.1;net461
+ net6.0;netcoreapp3.1;net462
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
index dca463c24..697e2923a 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
@@ -1,6 +1,4 @@
-using System;
-using BenchmarkDotNet;
-using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.CsProj;
@@ -13,7 +11,7 @@ public MultipleRuntimes()
{
AddJob(Job.Default.WithToolchain(CsProjClassicNetToolchain.Net461).AsBaseline()); // NET 4.6.1
AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp21)); // .NET Core 2.1
- //Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp30)); // .NET Core 3.0
+ AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp31)); // .NET Core 3.1
}
}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarInputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarInputStream.cs
new file mode 100644
index 000000000..b59a217ab
--- /dev/null
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarInputStream.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using ICSharpCode.SharpZipLib.Tar;
+
+namespace ICSharpCode.SharpZipLib.Benchmark.Tar
+{
+ [MemoryDiagnoser]
+ [Config(typeof(MultipleRuntimes))]
+ public class TarInputStream
+ {
+ private readonly byte[] archivedData;
+ private readonly byte[] readBuffer = new byte[1024];
+
+ public TarInputStream()
+ {
+ using (var outputMemoryStream = new MemoryStream())
+ {
+ using (var zipOutputStream =
+ new ICSharpCode.SharpZipLib.Tar.TarOutputStream(outputMemoryStream, Encoding.UTF8))
+ {
+ var tarEntry = TarEntry.CreateTarEntry("some file");
+ tarEntry.Size = 1024 * 1024;
+ zipOutputStream.PutNextEntry(tarEntry);
+
+ var rng = RandomNumberGenerator.Create();
+ var inputBuffer = new byte[1024];
+ rng.GetBytes(inputBuffer);
+
+ for (int i = 0; i < 1024; i++)
+ {
+ zipOutputStream.Write(inputBuffer, 0, inputBuffer.Length);
+ }
+ }
+
+ archivedData = outputMemoryStream.ToArray();
+ }
+ }
+
+ [Benchmark]
+ public long ReadTarInputStream()
+ {
+ using (var memoryStream = new MemoryStream(archivedData))
+ using (var zipInputStream = new ICSharpCode.SharpZipLib.Tar.TarInputStream(memoryStream, Encoding.UTF8))
+ {
+ var entry = zipInputStream.GetNextEntry();
+
+ while (zipInputStream.Read(readBuffer, 0, readBuffer.Length) > 0)
+ {
+ }
+
+ return entry.Size;
+ }
+ }
+
+ [Benchmark]
+ public async Task ReadTarInputStreamAsync()
+ {
+ using (var memoryStream = new MemoryStream(archivedData))
+ using (var zipInputStream = new ICSharpCode.SharpZipLib.Tar.TarInputStream(memoryStream, Encoding.UTF8))
+ {
+ var entry = await zipInputStream.GetNextEntryAsync(CancellationToken.None);
+
+#if NETCOREAPP2_1_OR_GREATER
+ while (await zipInputStream.ReadAsync(readBuffer.AsMemory()) > 0)
+ {
+ }
+#else
+ while (await zipInputStream.ReadAsync(readBuffer, 0, readBuffer.Length) > 0)
+ {
+ }
+#endif
+
+ return entry.Size;
+ }
+ }
+ }
+}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarOutputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarOutputStream.cs
new file mode 100644
index 000000000..f24e83e35
--- /dev/null
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Tar/TarOutputStream.cs
@@ -0,0 +1,64 @@
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using ICSharpCode.SharpZipLib.Tar;
+
+namespace ICSharpCode.SharpZipLib.Benchmark.Tar
+{
+ [MemoryDiagnoser]
+ [Config(typeof(MultipleRuntimes))]
+ public class TarOutputStream
+ {
+ private readonly byte[] backingArray = new byte[1024 * 1024 + (6 * 1024)];
+ private readonly byte[] inputBuffer = new byte[1024];
+ private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
+
+ [Benchmark]
+ public void WriteTarOutputStream()
+ {
+ using (var outputMemoryStream = new MemoryStream(backingArray))
+ {
+ using (var tarOutputStream =
+ new ICSharpCode.SharpZipLib.Tar.TarOutputStream(outputMemoryStream, Encoding.UTF8))
+ {
+ var tarEntry = TarEntry.CreateTarEntry("some file");
+ tarEntry.Size = 1024 * 1024;
+ tarOutputStream.PutNextEntry(tarEntry);
+
+ _rng.GetBytes(inputBuffer);
+
+ for (int i = 0; i < 1024; i++)
+ {
+ tarOutputStream.Write(inputBuffer, 0, inputBuffer.Length);
+ }
+ }
+ }
+ }
+
+ [Benchmark]
+ public async Task WriteTarOutputStreamAsync()
+ {
+ using (var outputMemoryStream = new MemoryStream(backingArray))
+ {
+ using (var tarOutputStream =
+ new ICSharpCode.SharpZipLib.Tar.TarOutputStream(outputMemoryStream, Encoding.UTF8))
+ {
+ var tarEntry = TarEntry.CreateTarEntry("some file");
+ tarEntry.Size = 1024 * 1024;
+
+ await tarOutputStream.PutNextEntryAsync(tarEntry, CancellationToken.None);
+
+ _rng.GetBytes(inputBuffer);
+
+ for (int i = 0; i < 1024; i++)
+ {
+ await tarOutputStream.WriteAsync(inputBuffer, 0, inputBuffer.Length);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
index d112e22e3..eb099ebfd 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
@@ -4,6 +4,7 @@
namespace ICSharpCode.SharpZipLib.Benchmark.Zip
{
+ [MemoryDiagnoser]
[Config(typeof(MultipleRuntimes))]
public class ZipInputStream
{
@@ -12,6 +13,7 @@ public class ZipInputStream
private const int N = ChunkCount * ChunkSize;
byte[] zippedData;
+ byte[] readBuffer = new byte[4096];
public ZipInputStream()
{
@@ -40,10 +42,9 @@ public long ReadZipInputStream()
{
using (var zipInputStream = new SharpZipLib.Zip.ZipInputStream(memoryStream))
{
- var buffer = new byte[4096];
var entry = zipInputStream.GetNextEntry();
- while (zipInputStream.Read(buffer, 0, buffer.Length) > 0)
+ while (zipInputStream.Read(readBuffer, 0, readBuffer.Length) > 0)
{
}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
index 0f7b5c7c4..ed125c1c7 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
@@ -1,9 +1,11 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
namespace ICSharpCode.SharpZipLib.Benchmark.Zip
{
+ [MemoryDiagnoser]
[Config(typeof(MultipleRuntimes))]
public class ZipOutputStream
{
@@ -36,5 +38,25 @@ public long WriteZipOutputStream()
return memoryStream.Position;
}
}
+
+ [Benchmark]
+ public async Task WriteZipOutputStreamAsync()
+ {
+ using (var memoryStream = new MemoryStream(outputBuffer))
+ {
+ using (var zipOutputStream = new SharpZipLib.Zip.ZipOutputStream(memoryStream))
+ {
+ zipOutputStream.IsStreamOwner = false;
+ zipOutputStream.PutNextEntry(new SharpZipLib.Zip.ZipEntry("0"));
+
+ for (int i = 0; i < ChunkCount; i++)
+ {
+ await zipOutputStream.WriteAsync(inputBuffer, 0, inputBuffer.Length);
+ }
+ }
+
+ return memoryStream.Position;
+ }
+ }
}
}
diff --git a/docs/help/docfx.json b/docs/help/docfx.json
index b2123cfa2..1da3079e4 100644
--- a/docs/help/docfx.json
+++ b/docs/help/docfx.json
@@ -16,7 +16,7 @@
],
"dest": "api",
"properties": {
- "TargetFramework": "NETSTANDARD2"
+ "TargetFramework": "netstandard2.0"
}
}
],
@@ -65,7 +65,7 @@
],
"globalMetadata": {
"_appTitle": "SharpZipLib Help",
- "_appFooter": "Copyright © 2000-2019 SharpZipLib Contributors",
+ "_appFooter": "Copyright © 2000-2022 SharpZipLib Contributors",
"_gitContribute": {
"repo": "https://github.com/icsharpcode/SharpZipLib",
"branch": "master"
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/Cmd_BZip2.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/Cmd_BZip2.csproj
index 351f801f9..b1536ff0f 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/Cmd_BZip2.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/Cmd_BZip2.csproj
@@ -80,84 +80,7 @@ copy Cmd_BZip2.exe bunzip2.exe
4096
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -165,7 +88,6 @@ copy Cmd_BZip2.exe bunzip2.exe
-
@@ -177,12 +99,10 @@ copy Cmd_BZip2.exe bunzip2.exe
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_BZip2/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.cs b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.cs
index 49426af69..c40ede339 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.cs
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.cs
@@ -132,7 +132,7 @@ public static int Main(string[] args)
case Command.Crc32:
var currentCrc = new Crc32();
while ((bytesRead = checksumStream.Read(buffer, 0, buffer.Length)) > 0) {
- currentCrc.Update(buffer, 0, bytesRead);
+ currentCrc.Update(new ArraySegment(buffer, 0, bytesRead));
}
Console.WriteLine("CRC32 for {0} is 0x{1:X8}", args[0], currentCrc.Value);
break;
@@ -140,7 +140,7 @@ public static int Main(string[] args)
case Command.BZip2:
var currentBZip2Crc = new BZip2Crc();
while ((bytesRead = checksumStream.Read(buffer, 0, buffer.Length)) > 0) {
- currentBZip2Crc.Update(buffer, 0, bytesRead);
+ currentBZip2Crc.Update(new ArraySegment(buffer, 0, bytesRead));
}
Console.WriteLine("BZip2CRC32 for {0} is 0x{1:X8}", args[0], currentBZip2Crc.Value);
break;
@@ -148,7 +148,7 @@ public static int Main(string[] args)
case Command.Adler:
var currentAdler = new Adler32();
while ((bytesRead = checksumStream.Read(buffer, 0, buffer.Length)) > 0) {
- currentAdler.Update(buffer, 0, bytesRead);
+ currentAdler.Update(new ArraySegment(buffer, 0, bytesRead));
}
Console.WriteLine("Adler32 for {0} is 0x{1:X8}", args[0], currentAdler.Value);
break;
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.csproj
index 25d80e70b..d5d9f6cfe 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/Cmd_Checksum.csproj
@@ -74,84 +74,12 @@
true
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -159,7 +87,6 @@
-
@@ -171,12 +98,10 @@
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Checksum/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/Cmd_GZip.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/Cmd_GZip.csproj
index 825bc2cc1..9ebaa8ca6 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/Cmd_GZip.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/Cmd_GZip.csproj
@@ -74,84 +74,12 @@ copy Cmd_GZip.exe gunzip.exe
true
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -159,7 +87,6 @@ copy Cmd_GZip.exe gunzip.exe
-
@@ -171,12 +98,10 @@ copy Cmd_GZip.exe gunzip.exe
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_GZip/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/Cmd_Tar.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/Cmd_Tar.csproj
index db8910d00..d40eef52e 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/Cmd_Tar.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/Cmd_Tar.csproj
@@ -66,86 +66,14 @@
Cmd_Tar
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -153,7 +81,6 @@
-
@@ -162,15 +89,13 @@
true
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_Tar/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/Cmd_ZipInfo.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/Cmd_ZipInfo.csproj
index 12e856069..311fcb85d 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/Cmd_ZipInfo.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/Cmd_ZipInfo.csproj
@@ -73,84 +73,12 @@
true
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -158,7 +86,6 @@
-
@@ -170,12 +97,10 @@
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/Cmd_ZipInfo/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/CreateZipFile.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/CreateZipFile.csproj
index ed2046a94..efd2cd464 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/CreateZipFile.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/CreateZipFile.csproj
@@ -81,84 +81,12 @@
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -166,7 +94,6 @@
-
@@ -178,12 +105,10 @@
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/CreateZipFile/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/FastZip.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/FastZip.csproj
index 7e52bb16f..efacf9ff8 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/FastZip.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/FastZip.csproj
@@ -67,84 +67,12 @@
..\bin\FastZip.XML
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -152,7 +80,6 @@
-
@@ -161,12 +88,10 @@
true
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/FastZip/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.cs b/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.cs
index d928f8907..7a1d10a1b 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.cs
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.cs
@@ -294,7 +294,7 @@ bool SetArgs(string[] args) {
try {
int enc = int.Parse(optArg);
if (Encoding.GetEncoding(enc) != null) {
- ZipConstants.DefaultCodePage = enc;
+ ZipStrings.CodePage = enc;
} else {
result = false;
Console.WriteLine("Invalid encoding " + args[argIndex]);
@@ -306,7 +306,7 @@ bool SetArgs(string[] args) {
}
} else {
try {
- ZipConstants.DefaultCodePage = Encoding.GetEncoding(optArg).CodePage;
+ ZipStrings.CodePage = Encoding.GetEncoding(optArg).CodePage;
}
catch (Exception) {
result = false;
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.csproj
index 864be9d84..121d55859 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/sz/sz.csproj
@@ -62,84 +62,12 @@
false
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -147,7 +75,6 @@
-
@@ -156,12 +83,10 @@
true
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/unzipfile.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/unzipfile.csproj
index 1566ae6f4..43403d1e4 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/unzipfile.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/unzipfile/unzipfile.csproj
@@ -38,88 +38,16 @@
UnZipFileClass
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -127,19 +55,16 @@
-
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/viewzipfile.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/viewzipfile.csproj
index 0141dc7fa..4734de832 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/viewzipfile.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/viewzipfile/viewzipfile.csproj
@@ -38,88 +38,16 @@
ViewZipFileClass
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -127,19 +55,16 @@
-
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.cs b/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.cs
index 88cd628ea..56eea376d 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.cs
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.cs
@@ -205,7 +205,7 @@ bool SetArgs(string[] args)
#if OPTIONTEST
Console.WriteLine("Encoding set to {0}", enc);
#endif
- ZipConstants.DefaultCodePage = enc;
+ ZipStrings.CodePage = enc;
}
else
{
@@ -223,7 +223,7 @@ bool SetArgs(string[] args)
{
try
{
- ZipConstants.DefaultCodePage = Encoding.GetEncoding(optArg).CodePage;
+ ZipStrings.CodePage = Encoding.GetEncoding(optArg).CodePage;
}
catch (Exception)
{
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.csproj b/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.csproj
index d4802d44b..1dbf75744 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.csproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/cs/zf/zf.csproj
@@ -62,84 +62,12 @@
false
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -147,7 +75,6 @@
-
@@ -156,12 +83,10 @@
true
+
+
+ 1.3.3
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
\ No newline at end of file
+
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/CreateZipFile.vbproj b/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/CreateZipFile.vbproj
index 4ed49c1cd..42db45963 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/CreateZipFile.vbproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/CreateZipFile.vbproj
@@ -66,86 +66,14 @@
false
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -164,9 +92,12 @@
-
-
+
+
+ 1.3.3
+
+
False
@@ -175,11 +106,4 @@
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/CreateZipFile/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/My Project/AssemblyInfo.vb b/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/My Project/AssemblyInfo.vb
index 798a0f93a..bdef02000 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/My Project/AssemblyInfo.vb
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/My Project/AssemblyInfo.vb
@@ -18,7 +18,7 @@ Imports System.Runtime.InteropServices
'NeutralResourceLanguage attribute below. Update the "en-US" in the line
'below to match the UICulture setting in the project file.
-
+
'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries can be found.
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/WpfCreateZipFile.vbproj b/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/WpfCreateZipFile.vbproj
index 12a8ab1d3..d86ec9e00 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/WpfCreateZipFile.vbproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/WpfCreateZipFile.vbproj
@@ -73,97 +73,21 @@
My Project\app.manifest
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
4.0
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
-
- ..\..\packages\WPFFolderBrowser.1.0.2\lib\WPFFolderBrowser.dll
- True
-
@@ -234,7 +158,6 @@
Settings.Designer.vb
-
@@ -246,12 +169,13 @@
false
+
+
+ 1.3.3
+
+
+ 1.0.2
+
+
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/packages.config
deleted file mode 100644
index 58052c86a..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/WpfCreateZipFile/packages.config
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/minibzip2.vbproj b/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/minibzip2.vbproj
index d253f603c..e15a05ec6 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/minibzip2.vbproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/minibzip2.vbproj
@@ -74,86 +74,14 @@
My Project\app.manifest
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -182,7 +110,11 @@
Resources.Designer.vb
-
+
+
+ 1.3.3
+
+
False
@@ -193,7 +125,6 @@
-
@@ -202,11 +133,4 @@
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/minibzip2/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/viewzipfile.vbproj b/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/viewzipfile.vbproj
index ded5b3ade..c85b1082e 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/viewzipfile.vbproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/viewzipfile/viewzipfile.vbproj
@@ -66,86 +66,14 @@
false
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -160,7 +88,11 @@
Designer
-
+
+
+ 1.3.3
+
+
False
@@ -170,14 +102,6 @@
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/packages.config b/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/packages.config
deleted file mode 100644
index d353d1619..000000000
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/packages.config
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/zipfiletest.vbproj b/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/zipfiletest.vbproj
index a04d2231e..b8b02293e 100644
--- a/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/zipfiletest.vbproj
+++ b/samples/ICSharpCode.SharpZipLib.Samples/vb/zipfiletest/zipfiletest.vbproj
@@ -76,86 +76,14 @@
42353,42354,42355
-
- ..\..\packages\SharpZipLib.1.0.0-alpha1\lib\netstandard1.3\ICSharpCode.SharpZipLib.dll
- True
-
-
- ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
- True
-
-
- ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
- True
-
-
- ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
- True
-
-
- ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
- True
-
-
- ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
- True
-
-
- ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
- True
-
-
- ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
- True
-
-
- ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
- True
-
-
- ..\..\packages\System.Net.Http.4.1.2\lib\net46\System.Net.Http.dll
- True
-
-
- ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
- True
-
-
- ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
- True
-
-
- ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
- True
-
-
- ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
- True
-
@@ -170,7 +98,11 @@
Designer
-
+
+
+ 1.3.3
+
+
False
@@ -180,14 +112,6 @@
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
\ No newline at end of file
diff --git a/src/ICSharpCode.SharpZipLib/AssemblyInfo.cs b/src/ICSharpCode.SharpZipLib/AssemblyInfo.cs
new file mode 100644
index 000000000..8f8e62016
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("ICSharpCode.SharpZipLib.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b9a14ea8fc9d7599e0e82a1292a23103f0210e2f928a0f466963af23fffadba59dcc8c9e26ecd114d7c0b4179e4bc93b1656b7ee2d4a67dd7c1992653e0d9cc534f7914b6f583b022e0a7aa8a430f407932f9a6806f0fc64d61e78d5ae01aa8f8233196719d44da2c50a2d1cfa3f7abb7487b3567a4f0456aa6667154c6749b1")]
diff --git a/src/ICSharpCode.SharpZipLib/BZip2/BZip2.cs b/src/ICSharpCode.SharpZipLib/BZip2/BZip2.cs
index ec6ff9402..4bd48b035 100644
--- a/src/ICSharpCode.SharpZipLib/BZip2/BZip2.cs
+++ b/src/ICSharpCode.SharpZipLib/BZip2/BZip2.cs
@@ -17,10 +17,11 @@ public static class BZip2
/// Both streams are closed on completion if true.
public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner)
{
- if (inStream == null || outStream == null)
- {
- throw new Exception("Null Stream");
- }
+ if (inStream == null)
+ throw new ArgumentNullException(nameof(inStream));
+
+ if (outStream == null)
+ throw new ArgumentNullException(nameof(outStream));
try
{
@@ -51,10 +52,11 @@ public static void Decompress(Stream inStream, Stream outStream, bool isStreamOw
/// the lowest compression and 9 the highest.
public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level)
{
- if (inStream == null || outStream == null)
- {
- throw new Exception("Null Stream");
- }
+ if (inStream == null)
+ throw new ArgumentNullException(nameof(inStream));
+
+ if (outStream == null)
+ throw new ArgumentNullException(nameof(outStream));
try
{
diff --git a/src/ICSharpCode.SharpZipLib/BZip2/BZip2Constants.cs b/src/ICSharpCode.SharpZipLib/BZip2/BZip2Constants.cs
index 146e0a093..52fb8ad20 100644
--- a/src/ICSharpCode.SharpZipLib/BZip2/BZip2Constants.cs
+++ b/src/ICSharpCode.SharpZipLib/BZip2/BZip2Constants.cs
@@ -3,7 +3,7 @@ namespace ICSharpCode.SharpZipLib.BZip2
///
/// Defines internal values for both compression and decompression
///
- internal sealed class BZip2Constants
+ internal static class BZip2Constants
{
///
/// Random numbers used to randomise repetitive blocks
@@ -113,9 +113,5 @@ internal sealed class BZip2Constants
/// Backend constant
///
public const int OvershootBytes = 20;
-
- private BZip2Constants()
- {
- }
}
}
diff --git a/src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs b/src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs
index e639bc1f5..3948b4e4c 100644
--- a/src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs
@@ -1,3 +1,7 @@
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
+ #define VECTORIZE_MEMORY_MOVE
+#endif
+
using ICSharpCode.SharpZipLib.Checksum;
using System;
using System.IO;
@@ -19,7 +23,11 @@ public class BZip2InputStream : Stream
private const int NO_RAND_PART_B_STATE = 6;
private const int NO_RAND_PART_C_STATE = 7;
- #endregion Constants
+#if VECTORIZE_MEMORY_MOVE
+ private static readonly int VectorSize = System.Numerics.Vector.Count;
+#endif // VECTORIZE_MEMORY_MOVE
+
+#endregion Constants
#region Instance Fields
@@ -711,10 +719,27 @@ cache misses.
unzftab[seqToUnseq[tmp]]++;
ll8[last] = seqToUnseq[tmp];
- for (int j = nextSym - 1; j > 0; --j)
+ var j = nextSym - 1;
+
+#if VECTORIZE_MEMORY_MOVE
+ // This is vectorized memory move. Going from the back, we're taking chunks of array
+ // and write them at the new location shifted by one. Since chunks are VectorSize long,
+ // at the end we have to move "tail" (or head actually) of the array using a plain loop.
+ // If System.Numerics.Vector API is not available, the plain loop is used to do the whole copying.
+
+ while(j >= VectorSize)
{
- yy[j] = yy[j - 1];
+ var arrayPart = new System.Numerics.Vector(yy, j - VectorSize);
+ arrayPart.CopyTo(yy, j - VectorSize + 1);
+ j -= VectorSize;
}
+#endif // VECTORIZE_MEMORY_MOVE
+
+ while(j > 0)
+ {
+ yy[j] = yy[--j];
+ }
+
yy[0] = tmp;
if (groupPos == 0)
diff --git a/src/ICSharpCode.SharpZipLib/Checksum/BZip2Crc.cs b/src/ICSharpCode.SharpZipLib/Checksum/BZip2Crc.cs
index 9674dcec7..be76da11e 100644
--- a/src/ICSharpCode.SharpZipLib/Checksum/BZip2Crc.cs
+++ b/src/ICSharpCode.SharpZipLib/Checksum/BZip2Crc.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
namespace ICSharpCode.SharpZipLib.Checksum
{
@@ -25,9 +26,19 @@ namespace ICSharpCode.SharpZipLib.Checksum
/// out is a one). We start with the highest power (least significant bit) of
/// q and repeat for all eight bits of q.
///
- /// The table is simply the CRC of all possible eight bit values. This is all
- /// the information needed to generate CRC's on data a byte at a time for all
- /// combinations of CRC register values and incoming bytes.
+ /// This implementation uses sixteen lookup tables stored in one linear array
+ /// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8
+ /// algorithm described in this Intel white paper:
+ ///
+ /// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf
+ ///
+ /// The first lookup table is simply the CRC of all possible eight bit values.
+ /// Each successive lookup table is derived from the original table generated
+ /// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs
+ /// together will produce the same output as a byte-by-byte CRC loop with
+ /// fewer arithmetic and bit manipulation operations, at the cost of increased
+ /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table,
+ /// which is still small enough to fit in most processors' L1 cache.)
///
public sealed class BZip2Crc : IChecksum
{
@@ -36,72 +47,7 @@ public sealed class BZip2Crc : IChecksum
private const uint crcInit = 0xFFFFFFFF;
//const uint crcXor = 0x00000000;
- private static readonly uint[] crcTable = {
- 0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9,
- 0X130476DC, 0X17C56B6B, 0X1A864DB2, 0X1E475005,
- 0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61,
- 0X350C9B64, 0X31CD86D3, 0X3C8EA00A, 0X384FBDBD,
- 0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9,
- 0X5F15ADAC, 0X5BD4B01B, 0X569796C2, 0X52568B75,
- 0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011,
- 0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD,
- 0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, 0X95609039,
- 0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5,
- 0XBE2B5B58, 0XBAEA46EF, 0XB7A96036, 0XB3687D81,
- 0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D,
- 0XD4326D90, 0XD0F37027, 0XDDB056FE, 0XD9714B49,
- 0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95,
- 0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1,
- 0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, 0XEC7DD02D,
- 0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE,
- 0X278206AB, 0X23431B1C, 0X2E003DC5, 0X2AC12072,
- 0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16,
- 0X018AEB13, 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA,
- 0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE,
- 0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02,
- 0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, 0X53DC6066,
- 0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA,
- 0XACA5C697, 0XA864DB20, 0XA527FDF9, 0XA1E6E04E,
- 0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692,
- 0X8AAD2B2F, 0X8E6C3698, 0X832F1041, 0X87EE0DF6,
- 0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A,
- 0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E,
- 0XF3B06B3B, 0XF771768C, 0XFA325055, 0XFEF34DE2,
- 0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686,
- 0XD5B88683, 0XD1799B34, 0XDC3ABDED, 0XD8FBA05A,
- 0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637,
- 0X7A089632, 0X7EC98B85, 0X738AAD5C, 0X774BB0EB,
- 0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F,
- 0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53,
- 0X251D3B9E, 0X21DC2629, 0X2C9F00F0, 0X285E1D47,
- 0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B,
- 0X0315D626, 0X07D4CB91, 0X0A97ED48, 0X0E56F0FF,
- 0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623,
- 0XF12F560E, 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7,
- 0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B,
- 0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F,
- 0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, 0XC960EBB3,
- 0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7,
- 0XAE3AFBA2, 0XAAFBE615, 0XA7B8C0CC, 0XA379DD7B,
- 0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F,
- 0X8832161A, 0X8CF30BAD, 0X81B02D74, 0X857130C3,
- 0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640,
- 0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C,
- 0X7B827D21, 0X7F436096, 0X7200464F, 0X76C15BF8,
- 0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24,
- 0X119B4BE9, 0X155A565E, 0X18197087, 0X1CD86D30,
- 0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC,
- 0X3793A651, 0X3352BBE6, 0X3E119D3F, 0X3AD08088,
- 0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654,
- 0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0,
- 0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, 0XDBEE767C,
- 0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18,
- 0XF0A5BD1D, 0XF464A0AA, 0XF9278673, 0XFDE69BC4,
- 0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0,
- 0X9ABC8BD5, 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C,
- 0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668,
- 0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4
- };
+ private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0x04C11DB7, isReversed: false);
///
/// The CRC data checksum so far.
@@ -134,7 +80,7 @@ public long Value
{
get
{
- // Tehcnically, the output should be:
+ // Technically, the output should be:
//return (long)(~checkValue ^ crcXor);
// but x ^ 0 = x, so there is no point in adding
// the XOR operation
@@ -149,6 +95,7 @@ public long Value
/// the byte is taken as the lower 8 bits of bval
///
/// Reversed Data = false
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(int bval)
{
checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8));
@@ -166,7 +113,7 @@ public void Update(byte[] buffer)
throw new ArgumentNullException(nameof(buffer));
}
- Update(new ArraySegment(buffer, 0, buffer.Length));
+ Update(buffer, 0, buffer.Length);
}
///
@@ -177,11 +124,48 @@ public void Update(byte[] buffer)
///
public void Update(ArraySegment segment)
{
- var count = segment.Count;
- var offset = segment.Offset;
+ Update(segment.Array, segment.Offset, segment.Count);
+ }
+
+ ///
+ /// Internal helper function for updating a block of data using slicing.
+ ///
+ /// The array containing the data to add
+ /// Range start for (inclusive)
+ /// The number of bytes to checksum starting from
+ private void Update(byte[] data, int offset, int count)
+ {
+ int remainder = count % CrcUtilities.SlicingDegree;
+ int end = offset + count - remainder;
+
+ while (offset != end)
+ {
+ checkValue = CrcUtilities.UpdateDataForNormalPoly(data, offset, crcTable, checkValue);
+ offset += CrcUtilities.SlicingDegree;
+ }
- while (--count >= 0)
- Update(segment.Array[offset++]);
+ if (remainder != 0)
+ {
+ SlowUpdateLoop(data, offset, end + remainder);
+ }
+ }
+
+ ///
+ /// A non-inlined function for updating data that doesn't fit in a 16-byte
+ /// block. We don't expect to enter this function most of the time, and when
+ /// we do we're not here for long, so disabling inlining here improves
+ /// performance overall.
+ ///
+ /// The array containing the data to add
+ /// Range start for (inclusive)
+ /// Range end for (exclusive)
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void SlowUpdateLoop(byte[] data, int offset, int end)
+ {
+ while (offset != end)
+ {
+ Update(data[offset++]);
+ }
}
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Checksum/Crc32.cs b/src/ICSharpCode.SharpZipLib/Checksum/Crc32.cs
index 9b8ab4b6d..740aff566 100644
--- a/src/ICSharpCode.SharpZipLib/Checksum/Crc32.cs
+++ b/src/ICSharpCode.SharpZipLib/Checksum/Crc32.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
namespace ICSharpCode.SharpZipLib.Checksum
{
@@ -25,9 +26,19 @@ namespace ICSharpCode.SharpZipLib.Checksum
/// out is a one). We start with the highest power (least significant bit) of
/// q and repeat for all eight bits of q.
///
- /// The table is simply the CRC of all possible eight bit values. This is all
- /// the information needed to generate CRC's on data a byte at a time for all
- /// combinations of CRC register values and incoming bytes.
+ /// This implementation uses sixteen lookup tables stored in one linear array
+ /// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8
+ /// algorithm described in this Intel white paper:
+ ///
+ /// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf
+ ///
+ /// The first lookup table is simply the CRC of all possible eight bit values.
+ /// Each successive lookup table is derived from the original table generated
+ /// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs
+ /// together will produce the same output as a byte-by-byte CRC loop with
+ /// fewer arithmetic and bit manipulation operations, at the cost of increased
+ /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table,
+ /// which is still small enough to fit in most processors' L1 cache.)
///
public sealed class Crc32 : IChecksum
{
@@ -36,60 +47,7 @@ public sealed class Crc32 : IChecksum
private static readonly uint crcInit = 0xFFFFFFFF;
private static readonly uint crcXor = 0xFFFFFFFF;
- private static readonly uint[] crcTable = {
- 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
- 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
- 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
- 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
- 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
- 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
- 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
- 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
- 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
- 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
- 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
- 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
- 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
- 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
- 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
- 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
- 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
- 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
- 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
- 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
- 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
- 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
- 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
- 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
- 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
- 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
- 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
- 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
- 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
- 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
- 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
- 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
- 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
- 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
- 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
- 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
- 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
- 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
- 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
- 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
- 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
- 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
- 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
- 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
- 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
- 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
- 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
- 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
- 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
- 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
- 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
- 0x2D02EF8D
- };
+ private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0xEDB88320, isReversed: true);
///
/// The CRC data checksum so far.
@@ -98,6 +56,7 @@ public sealed class Crc32 : IChecksum
#endregion Instance Fields
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static uint ComputeCrc32(uint oldCrc, byte bval)
{
return (uint)(Crc32.crcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8));
@@ -138,6 +97,7 @@ public long Value
/// the byte is taken as the lower 8 bits of bval
///
/// Reversed Data = true
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(int bval)
{
checkValue = unchecked(crcTable[(checkValue ^ bval) & 0xFF] ^ (checkValue >> 8));
@@ -155,7 +115,7 @@ public void Update(byte[] buffer)
throw new ArgumentNullException(nameof(buffer));
}
- Update(new ArraySegment(buffer, 0, buffer.Length));
+ Update(buffer, 0, buffer.Length);
}
///
@@ -166,11 +126,48 @@ public void Update(byte[] buffer)
///
public void Update(ArraySegment segment)
{
- var count = segment.Count;
- var offset = segment.Offset;
+ Update(segment.Array, segment.Offset, segment.Count);
+ }
+
+ ///
+ /// Internal helper function for updating a block of data using slicing.
+ ///
+ /// The array containing the data to add
+ /// Range start for (inclusive)
+ /// The number of bytes to checksum starting from
+ private void Update(byte[] data, int offset, int count)
+ {
+ int remainder = count % CrcUtilities.SlicingDegree;
+ int end = offset + count - remainder;
+
+ while (offset != end)
+ {
+ checkValue = CrcUtilities.UpdateDataForReversedPoly(data, offset, crcTable, checkValue);
+ offset += CrcUtilities.SlicingDegree;
+ }
- while (--count >= 0)
- Update(segment.Array[offset++]);
+ if (remainder != 0)
+ {
+ SlowUpdateLoop(data, offset, end + remainder);
+ }
+ }
+
+ ///
+ /// A non-inlined function for updating data that doesn't fit in a 16-byte
+ /// block. We don't expect to enter this function most of the time, and when
+ /// we do we're not here for long, so disabling inlining here improves
+ /// performance overall.
+ ///
+ /// The array containing the data to add
+ /// Range start for (inclusive)
+ /// Range end for (exclusive)
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void SlowUpdateLoop(byte[] data, int offset, int end)
+ {
+ while (offset != end)
+ {
+ Update(data[offset++]);
+ }
}
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Checksum/CrcUtilities.cs b/src/ICSharpCode.SharpZipLib/Checksum/CrcUtilities.cs
new file mode 100644
index 000000000..575abe08b
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Checksum/CrcUtilities.cs
@@ -0,0 +1,158 @@
+using System.Runtime.CompilerServices;
+
+namespace ICSharpCode.SharpZipLib.Checksum
+{
+ internal static class CrcUtilities
+ {
+ ///
+ /// The number of slicing lookup tables to generate.
+ ///
+ internal const int SlicingDegree = 16;
+
+ ///
+ /// Generates multiple CRC lookup tables for a given polynomial, stored
+ /// in a linear array of uints. The first block (i.e. the first 256
+ /// elements) is the same as the byte-by-byte CRC lookup table.
+ ///
+ /// The generating CRC polynomial
+ /// Whether the polynomial is in reversed bit order
+ /// A linear array of 256 * elements
+ ///
+ /// This table could also be generated as a rectangular array, but the
+ /// JIT compiler generates slower code than if we use a linear array.
+ /// Known issue, see: https://github.com/dotnet/runtime/issues/30275
+ ///
+ internal static uint[] GenerateSlicingLookupTable(uint polynomial, bool isReversed)
+ {
+ var table = new uint[256 * SlicingDegree];
+ uint one = isReversed ? 1 : (1U << 31);
+
+ for (int i = 0; i < 256; i++)
+ {
+ uint res = (uint)(isReversed ? i : i << 24);
+ for (int j = 0; j < SlicingDegree; j++)
+ {
+ for (int k = 0; k < 8; k++)
+ {
+ if (isReversed)
+ {
+ res = (res & one) == 1 ? polynomial ^ (res >> 1) : res >> 1;
+ }
+ else
+ {
+ res = (res & one) != 0 ? polynomial ^ (res << 1) : res << 1;
+ }
+ }
+
+ table[(256 * j) + i] = res;
+ }
+ }
+
+ return table;
+ }
+
+ ///
+ /// Mixes the first four bytes of input with
+ /// using normal ordering before calling .
+ ///
+ /// Array of data to checksum
+ /// Offset to start reading from
+ /// The table to use for slicing-by-16 lookup
+ /// Checksum state before this update call
+ /// A new unfinalized checksum value
+ ///
+ ///
+ /// Assumes input[offset]..input[offset + 15] are valid array indexes.
+ /// For performance reasons, this must be checked by the caller.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static uint UpdateDataForNormalPoly(byte[] input, int offset, uint[] crcTable, uint checkValue)
+ {
+ byte x1 = (byte)((byte)(checkValue >> 24) ^ input[offset]);
+ byte x2 = (byte)((byte)(checkValue >> 16) ^ input[offset + 1]);
+ byte x3 = (byte)((byte)(checkValue >> 8) ^ input[offset + 2]);
+ byte x4 = (byte)((byte)checkValue ^ input[offset + 3]);
+
+ return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4);
+ }
+
+ ///
+ /// Mixes the first four bytes of input with
+ /// using reflected ordering before calling .
+ ///
+ /// Array of data to checksum
+ /// Offset to start reading from
+ /// The table to use for slicing-by-16 lookup
+ /// Checksum state before this update call
+ /// A new unfinalized checksum value
+ ///
+ ///
+ /// Assumes input[offset]..input[offset + 15] are valid array indexes.
+ /// For performance reasons, this must be checked by the caller.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static uint UpdateDataForReversedPoly(byte[] input, int offset, uint[] crcTable, uint checkValue)
+ {
+ byte x1 = (byte)((byte)checkValue ^ input[offset]);
+ byte x2 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 1]);
+ byte x3 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 2]);
+ byte x4 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 3]);
+
+ return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4);
+ }
+
+ ///
+ /// A shared method for updating an unfinalized CRC checksum using slicing-by-16.
+ ///
+ /// Array of data to checksum
+ /// Offset to start reading from
+ /// The table to use for slicing-by-16 lookup
+ /// First byte of input after mixing with the old CRC
+ /// Second byte of input after mixing with the old CRC
+ /// Third byte of input after mixing with the old CRC
+ /// Fourth byte of input after mixing with the old CRC
+ /// A new unfinalized checksum value
+ ///
+ ///
+ /// Even though the first four bytes of input are fed in as arguments,
+ /// should be the same value passed to this
+ /// function's caller (either or
+ /// ). This method will get inlined
+ /// into both functions, so using the same offset produces faster code.
+ ///
+ ///
+ /// Because most processors running C# have some kind of instruction-level
+ /// parallelism, the order of XOR operations can affect performance. This
+ /// ordering assumes that the assembly code generated by the just-in-time
+ /// compiler will emit a bunch of arithmetic operations for checking array
+ /// bounds. Then it opportunistically XORs a1 and a2 to keep the processor
+ /// busy while those other parts of the pipeline handle the range check
+ /// calculations.
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint UpdateDataCommon(byte[] input, int offset, uint[] crcTable, byte x1, byte x2, byte x3, byte x4)
+ {
+ uint result;
+ uint a1 = crcTable[x1 + 3840] ^ crcTable[x2 + 3584];
+ uint a2 = crcTable[x3 + 3328] ^ crcTable[x4 + 3072];
+
+ result = crcTable[input[offset + 4] + 2816];
+ result ^= crcTable[input[offset + 5] + 2560];
+ a1 ^= crcTable[input[offset + 9] + 1536];
+ result ^= crcTable[input[offset + 6] + 2304];
+ result ^= crcTable[input[offset + 7] + 2048];
+ result ^= crcTable[input[offset + 8] + 1792];
+ a2 ^= crcTable[input[offset + 13] + 512];
+ result ^= crcTable[input[offset + 10] + 1280];
+ result ^= crcTable[input[offset + 11] + 1024];
+ result ^= crcTable[input[offset + 12] + 768];
+ result ^= a1;
+ result ^= crcTable[input[offset + 14] + 256];
+ result ^= crcTable[input[offset + 15]];
+ result ^= a2;
+
+ return result;
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/ByteOrderUtils.cs b/src/ICSharpCode.SharpZipLib/Core/ByteOrderUtils.cs
new file mode 100644
index 000000000..a2e30da7f
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/ByteOrderUtils.cs
@@ -0,0 +1,130 @@
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using CT = System.Threading.CancellationToken;
+
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable InconsistentNaming
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ internal static class ByteOrderStreamExtensions
+ {
+ internal static byte[] SwappedBytes(ushort value) => new[] {(byte)value, (byte)(value >> 8)};
+ internal static byte[] SwappedBytes(short value) => new[] {(byte)value, (byte)(value >> 8)};
+ internal static byte[] SwappedBytes(uint value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};
+ internal static byte[] SwappedBytes(int value) => new[] {(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24)};
+
+ internal static byte[] SwappedBytes(long value) => new[] {
+ (byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
+ (byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
+ };
+
+ internal static byte[] SwappedBytes(ulong value) => new[] {
+ (byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
+ (byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static long SwappedS64(byte[] bytes) => (
+ (long)bytes[0] << 0 | (long)bytes[1] << 8 | (long)bytes[2] << 16 | (long)bytes[3] << 24 |
+ (long)bytes[4] << 32 | (long)bytes[5] << 40 | (long)bytes[6] << 48 | (long)bytes[7] << 56);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ulong SwappedU64(byte[] bytes) => (
+ (ulong)bytes[0] << 0 | (ulong)bytes[1] << 8 | (ulong)bytes[2] << 16 | (ulong)bytes[3] << 24 |
+ (ulong)bytes[4] << 32 | (ulong)bytes[5] << 40 | (ulong)bytes[6] << 48 | (ulong)bytes[7] << 56);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static int SwappedS32(byte[] bytes) => bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static uint SwappedU32(byte[] bytes) => (uint) SwappedS32(bytes);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static short SwappedS16(byte[] bytes) => (short)(bytes[0] | bytes[1] << 8);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ushort SwappedU16(byte[] bytes) => (ushort) SwappedS16(bytes);
+
+ internal static byte[] ReadBytes(this Stream stream, int count)
+ {
+ var bytes = new byte[count];
+ var remaining = count;
+ while (remaining > 0)
+ {
+ var bytesRead = stream.Read(bytes, count - remaining, remaining);
+ if (bytesRead < 1) throw new EndOfStreamException();
+ remaining -= bytesRead;
+ }
+
+ return bytes;
+ }
+
+ /// Read an unsigned short in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadLEShort(this Stream stream) => SwappedS16(ReadBytes(stream, 2));
+
+ /// Read an int in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadLEInt(this Stream stream) => SwappedS32(ReadBytes(stream, 4));
+
+ /// Read a long in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static long ReadLELong(this Stream stream) => SwappedS64(ReadBytes(stream, 8));
+
+ /// Write an unsigned short in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLEShort(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 2);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLEShortAsync(this Stream stream, int value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);
+
+ /// Write a ushort in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLEUshort(this Stream stream, ushort value) => stream.Write(SwappedBytes(value), 0, 2);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLEUshortAsync(this Stream stream, ushort value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 2, ct);
+
+ /// Write an int in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLEInt(this Stream stream, int value) => stream.Write(SwappedBytes(value), 0, 4);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLEIntAsync(this Stream stream, int value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);
+
+ /// Write a uint in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLEUint(this Stream stream, uint value) => stream.Write(SwappedBytes(value), 0, 4);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLEUintAsync(this Stream stream, uint value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 4, ct);
+
+ /// Write a long in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLELong(this Stream stream, long value) => stream.Write(SwappedBytes(value), 0, 8);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLELongAsync(this Stream stream, long value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);
+
+ /// Write a ulong in little endian byte order.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLEUlong(this Stream stream, ulong value) => stream.Write(SwappedBytes(value), 0, 8);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static async Task WriteLEUlongAsync(this Stream stream, ulong value, CT ct)
+ => await stream.WriteAsync(SwappedBytes(value), 0, 8, ct);
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/EmptyRefs.cs b/src/ICSharpCode.SharpZipLib/Core/EmptyRefs.cs
new file mode 100644
index 000000000..feb7a8e17
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/EmptyRefs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ internal static class Empty
+ {
+#if NET45
+ internal static class EmptyArray
+ {
+ public static readonly T[] Value = new T[0];
+ }
+ public static T[] Array() => EmptyArray.Value;
+#else
+ public static T[] Array() => System.Array.Empty();
+#endif
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/ExactMemoryPool.cs b/src/ICSharpCode.SharpZipLib/Core/ExactMemoryPool.cs
new file mode 100644
index 000000000..d03ca2ecf
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/ExactMemoryPool.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Buffers;
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ ///
+ /// A MemoryPool that will return a Memory which is exactly the length asked for using the bufferSize parameter.
+ /// This is in contrast to the default ArrayMemoryPool which will return a Memory of equal size to the underlying
+ /// array which at least as long as the minBufferSize parameter.
+ /// Note: The underlying array may be larger than the slice of Memory
+ ///
+ ///
+ internal sealed class ExactMemoryPool : MemoryPool
+ {
+ public new static readonly MemoryPool Shared = new ExactMemoryPool();
+
+ public override IMemoryOwner Rent(int bufferSize = -1)
+ {
+ if ((uint)bufferSize > int.MaxValue || bufferSize < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
+ }
+
+ return new ExactMemoryPoolBuffer(bufferSize);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ public override int MaxBufferSize => int.MaxValue;
+
+ private sealed class ExactMemoryPoolBuffer : IMemoryOwner, IDisposable
+ {
+ private T[] array;
+ private readonly int size;
+
+ public ExactMemoryPoolBuffer(int size)
+ {
+ this.size = size;
+ this.array = ArrayPool.Shared.Rent(size);
+ }
+
+ public Memory Memory
+ {
+ get
+ {
+ T[] array = this.array;
+ if (array == null)
+ {
+ throw new ObjectDisposedException(nameof(ExactMemoryPoolBuffer));
+ }
+
+ return new Memory(array).Slice(0, size);
+ }
+ }
+
+ public void Dispose()
+ {
+ T[] array = this.array;
+ if (array == null)
+ {
+ return;
+ }
+
+ this.array = null;
+ ArrayPool.Shared.Return(array);
+ }
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/Exceptions/StreamDecodingException.cs b/src/ICSharpCode.SharpZipLib/Core/Exceptions/StreamDecodingException.cs
index 389b7d065..e22b11b0d 100644
--- a/src/ICSharpCode.SharpZipLib/Core/Exceptions/StreamDecodingException.cs
+++ b/src/ICSharpCode.SharpZipLib/Core/Exceptions/StreamDecodingException.cs
@@ -4,8 +4,8 @@
namespace ICSharpCode.SharpZipLib
{
///
- /// Indicates that an error occured during decoding of a input stream due to corrupt
- /// data or (unintentional) library incompability.
+ /// Indicates that an error occurred during decoding of a input stream due to corrupt
+ /// data or (unintentional) library incompatibility.
///
[Serializable]
public class StreamDecodingException : SharpZipBaseException
diff --git a/src/ICSharpCode.SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs b/src/ICSharpCode.SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
index aefefb61e..d41cf9882 100644
--- a/src/ICSharpCode.SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
+++ b/src/ICSharpCode.SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
@@ -10,14 +10,14 @@ namespace ICSharpCode.SharpZipLib
public class ValueOutOfRangeException : StreamDecodingException
{
///
- /// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable
+ /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable
///
/// Name of the variable, use: nameof()
public ValueOutOfRangeException(string nameOfValue)
: base($"{nameOfValue} out of range") { }
///
- /// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable,
+ /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable,
/// it's current value and expected range.
///
/// Name of the variable, use: nameof()
@@ -28,7 +28,7 @@ public ValueOutOfRangeException(string nameOfValue, long value, long maxValue, l
: this(nameOfValue, value.ToString(), maxValue.ToString(), minValue.ToString()) { }
///
- /// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable,
+ /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable,
/// it's current value and expected range.
///
/// Name of the variable, use: nameof()
diff --git a/src/ICSharpCode.SharpZipLib/Core/FileSystemScanner.cs b/src/ICSharpCode.SharpZipLib/Core/FileSystemScanner.cs
index 8b01e5ff5..427e7d895 100644
--- a/src/ICSharpCode.SharpZipLib/Core/FileSystemScanner.cs
+++ b/src/ICSharpCode.SharpZipLib/Core/FileSystemScanner.cs
@@ -78,7 +78,7 @@ public string Name
}
///
- /// Get set a value indicating wether scanning should continue or not.
+ /// Get set a value indicating whether scanning should continue or not.
///
public bool ContinueRunning
{
@@ -209,7 +209,7 @@ public Exception Exception
}
///
- /// Get / set a value indicating wether scanning should continue.
+ /// Get / set a value indicating whether scanning should continue.
///
public bool ContinueRunning
{
diff --git a/src/ICSharpCode.SharpZipLib/Core/NameFilter.cs b/src/ICSharpCode.SharpZipLib/Core/NameFilter.cs
index 58c578a25..57751891c 100644
--- a/src/ICSharpCode.SharpZipLib/Core/NameFilter.cs
+++ b/src/ICSharpCode.SharpZipLib/Core/NameFilter.cs
@@ -106,7 +106,7 @@ public static bool IsValidFilterExpression(string toTest)
/// Split a string into its component pieces
///
/// The original string
- /// Returns an array of values containing the individual filter elements.
+ /// Returns an array of values containing the individual filter elements.
public static string[] SplitQuoted(string original)
{
char escape = '\\';
diff --git a/src/ICSharpCode.SharpZipLib/Core/PathUtils.cs b/src/ICSharpCode.SharpZipLib/Core/PathUtils.cs
new file mode 100644
index 000000000..b8d0dd409
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/PathUtils.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ ///
+ /// PathUtils provides simple utilities for handling paths.
+ ///
+ public static class PathUtils
+ {
+ ///
+ /// Remove any path root present in the path
+ ///
+ /// A containing path information.
+ /// The path with the root removed if it was present; path otherwise.
+ public static string DropPathRoot(string path)
+ {
+ var invalidChars = Path.GetInvalidPathChars();
+ // If the first character after the root is a ':', .NET < 4.6.2 throws
+ var cleanRootSep = path.Length >= 3 && path[1] == ':' && path[2] == ':';
+
+ // Replace any invalid path characters with '_' to prevent Path.GetPathRoot from throwing.
+ // Only pass the first 258 (should be 260, but that still throws for some reason) characters
+ // as .NET < 4.6.2 throws on longer paths
+ var cleanPath = new string(path.Take(258)
+ .Select( (c, i) => invalidChars.Contains(c) || (i == 2 && cleanRootSep) ? '_' : c).ToArray());
+
+ var stripLength = Path.GetPathRoot(cleanPath).Length;
+ while (path.Length > stripLength && (path[stripLength] == '/' || path[stripLength] == '\\')) stripLength++;
+ return path.Substring(stripLength);
+ }
+
+ ///
+ /// Returns a random file name in the users temporary directory, or in directory of if specified
+ ///
+ /// If specified, used as the base file name for the temporary file
+ /// Returns a temporary file name
+ public static string GetTempFileName(string original = null)
+ {
+ string fileName;
+ var tempPath = Path.GetTempPath();
+
+ do
+ {
+ fileName = original == null
+ ? Path.Combine(tempPath, Path.GetRandomFileName())
+ : $"{original}.{Path.GetRandomFileName()}";
+ } while (File.Exists(fileName));
+
+ return fileName;
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs b/src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs
index 6d0d9b304..47de6e26e 100644
--- a/src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs
+++ b/src/ICSharpCode.SharpZipLib/Core/StreamUtils.cs
@@ -1,12 +1,14 @@
using System;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Core
{
///
/// Provides simple " utilities.
///
- public sealed class StreamUtils
+ public static class StreamUtils
{
///
/// Read from a ensuring all the required data is read.
@@ -14,7 +16,7 @@ public sealed class StreamUtils
/// The stream to read.
/// The buffer to fill.
///
- static public void ReadFully(Stream stream, byte[] buffer)
+ public static void ReadFully(Stream stream, byte[] buffer)
{
ReadFully(stream, buffer, 0, buffer.Length);
}
@@ -29,7 +31,7 @@ static public void ReadFully(Stream stream, byte[] buffer)
/// Required parameter is null
/// and or are invalid.
/// End of stream is encountered before all the data has been read.
- static public void ReadFully(Stream stream, byte[] buffer, int offset, int count)
+ public static void ReadFully(Stream stream, byte[] buffer, int offset, int count)
{
if (stream == null)
{
@@ -73,7 +75,7 @@ static public void ReadFully(Stream stream, byte[] buffer, int offset, int count
/// The number of bytes of data to store.
/// Required parameter is null
/// and or are invalid.
- static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
+ public static int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
{
if (stream == null)
{
@@ -118,7 +120,7 @@ static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, i
/// The stream to source data from.
/// The stream to write data to.
/// The buffer to use during copying.
- static public void Copy(Stream source, Stream destination, byte[] buffer)
+ public static void Copy(Stream source, Stream destination, byte[] buffer)
{
if (source == null)
{
@@ -169,7 +171,7 @@ static public void Copy(Stream source, Stream destination, byte[] buffer)
/// The source for this event.
/// The name to use with the event.
/// This form is specialised for use within #Zip to support events during archive operations.
- static public void Copy(Stream source, Stream destination,
+ public static void Copy(Stream source, Stream destination,
byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name)
{
Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1);
@@ -188,7 +190,7 @@ static public void Copy(Stream source, Stream destination,
/// A predetermined fixed target value to use with progress updates.
/// If the value is negative the target is calculated by looking at the stream.
/// This form is specialised for use within #Zip to support events during archive operations.
- static public void Copy(Stream source, Stream destination,
+ public static void Copy(Stream source, Stream destination,
byte[] buffer,
ProgressHandler progressHandler, TimeSpan updateInterval,
object sender, string name, long fixedTarget)
@@ -272,13 +274,22 @@ static public void Copy(Stream source, Stream destination,
progressHandler(sender, args);
}
}
-
- ///
- /// Initialise an instance of
- ///
- private StreamUtils()
+
+ internal static async Task WriteProcToStreamAsync(this Stream targetStream, MemoryStream bufferStream, Action writeProc, CancellationToken ct)
{
- // Do nothing.
+ bufferStream.SetLength(0);
+ writeProc(bufferStream);
+ bufferStream.Position = 0;
+ await bufferStream.CopyToAsync(targetStream, 81920, ct);
+ bufferStream.SetLength(0);
+ }
+
+ internal static async Task WriteProcToStreamAsync(this Stream targetStream, Action writeProc, CancellationToken ct)
+ {
+ using (var ms = new MemoryStream())
+ {
+ await WriteProcToStreamAsync(targetStream, ms, writeProc, ct);
+ }
}
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Core/StringBuilderPool.cs b/src/ICSharpCode.SharpZipLib/Core/StringBuilderPool.cs
new file mode 100644
index 000000000..a1121f0cc
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/StringBuilderPool.cs
@@ -0,0 +1,22 @@
+using System.Collections.Concurrent;
+using System.Text;
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ internal class StringBuilderPool
+ {
+ public static StringBuilderPool Instance { get; } = new StringBuilderPool();
+ private readonly ConcurrentQueue pool = new ConcurrentQueue();
+
+ public StringBuilder Rent()
+ {
+ return pool.TryDequeue(out var builder) ? builder : new StringBuilder();
+ }
+
+ public void Return(StringBuilder builder)
+ {
+ builder.Clear();
+ pool.Enqueue(builder);
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Core/WindowsPathUtils.cs b/src/ICSharpCode.SharpZipLib/Core/WindowsPathUtils.cs
deleted file mode 100644
index f02a0affb..000000000
--- a/src/ICSharpCode.SharpZipLib/Core/WindowsPathUtils.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-namespace ICSharpCode.SharpZipLib.Core
-{
- ///
- /// WindowsPathUtils provides simple utilities for handling windows paths.
- ///
- public abstract class WindowsPathUtils
- {
- ///
- /// Initializes a new instance of the class.
- ///
- internal WindowsPathUtils()
- {
- }
-
- ///
- /// Remove any path root present in the path
- ///
- /// A containing path information.
- /// The path with the root removed if it was present; path otherwise.
- /// Unlike the class the path isnt otherwise checked for validity.
- public static string DropPathRoot(string path)
- {
- string result = path;
-
- if (!string.IsNullOrEmpty(path))
- {
- if ((path[0] == '\\') || (path[0] == '/'))
- {
- // UNC name ?
- if ((path.Length > 1) && ((path[1] == '\\') || (path[1] == '/')))
- {
- int index = 2;
- int elements = 2;
-
- // Scan for two separate elements \\machine\share\restofpath
- while ((index <= path.Length) &&
- (((path[index] != '\\') && (path[index] != '/')) || (--elements > 0)))
- {
- index++;
- }
-
- index++;
-
- if (index < path.Length)
- {
- result = path.Substring(index);
- }
- else
- {
- result = "";
- }
- }
- }
- else if ((path.Length > 1) && (path[1] == ':'))
- {
- int dropCount = 2;
- if ((path.Length > 2) && ((path[2] == '\\') || (path[2] == '/')))
- {
- dropCount = 3;
- }
- result = result.Remove(0, dropCount);
- }
- }
- return result;
- }
- }
-}
diff --git a/src/ICSharpCode.SharpZipLib/Encryption/PkzipClassic.cs b/src/ICSharpCode.SharpZipLib/Encryption/PkzipClassic.cs
index 7a8c55e6e..1c7bd1f28 100644
--- a/src/ICSharpCode.SharpZipLib/Encryption/PkzipClassic.cs
+++ b/src/ICSharpCode.SharpZipLib/Encryption/PkzipClassic.cs
@@ -6,7 +6,7 @@ namespace ICSharpCode.SharpZipLib.Encryption
{
///
/// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives.
- /// While it has been superceded by more recent and more powerful algorithms, its still in use and
+ /// While it has been superseded by more recent and more powerful algorithms, its still in use and
/// is viable for preventing casual snooping
///
public abstract class PkzipClassic : SymmetricAlgorithm
@@ -444,8 +444,10 @@ public override byte[] Key
public override void GenerateKey()
{
key_ = new byte[12];
- var rnd = new Random();
- rnd.NextBytes(key_);
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(key_);
+ }
}
///
diff --git a/src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs b/src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs
index ffafee5df..fbf76a77b 100644
--- a/src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs
@@ -1,7 +1,10 @@
using System;
using System.IO;
using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Core;
+using ICSharpCode.SharpZipLib.Zip;
namespace ICSharpCode.SharpZipLib.Encryption
{
@@ -37,7 +40,7 @@ public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode m
}
// The final n bytes of the AES stream contain the Auth Code.
- private const int AUTH_CODE_LENGTH = 10;
+ public const int AUTH_CODE_LENGTH = Zip.ZipConstants.AESAuthCodeLength;
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
private const int CRYPTO_BLOCK_SIZE = 16;
@@ -90,6 +93,13 @@ public override int Read(byte[] buffer, int offset, int count)
return nBytes;
}
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ var readCount = Read(buffer, offset, count);
+ return Task.FromResult(readCount);
+ }
+
// Read data from the underlying stream and decrypt it
private int ReadAndTransform(byte[] buffer, int offset, int count)
{
@@ -137,14 +147,14 @@ private int ReadAndTransform(byte[] buffer, int offset, int count)
nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, finalBlock);
}
else if (byteCount < AUTH_CODE_LENGTH)
- throw new Exception("Internal error missed auth code"); // Coding bug
+ throw new ZipException("Internal error missed auth code"); // Coding bug
// Final block done. Check Auth code.
byte[] calcAuthCode = _transform.GetAuthCode();
for (int i = 0; i < AUTH_CODE_LENGTH; i++)
{
if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i])
{
- throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
+ throw new ZipException("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
+ "The file may be damaged.");
}
}
@@ -163,7 +173,7 @@ private int ReadBufferedData(byte[] buffer, int offset, int count)
{
int copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos);
- Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, count);
+ Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, copyCount);
_transformBufferStartPos += copyCount;
return copyCount;
diff --git a/src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs b/src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs
index 437e25c10..9e7790dec 100644
--- a/src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs
+++ b/src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs
@@ -8,33 +8,6 @@ namespace ICSharpCode.SharpZipLib.Encryption
///
internal class ZipAESTransform : ICryptoTransform
{
-#if NET45
- class IncrementalHash : HMACSHA1
- {
- bool _finalised;
- public IncrementalHash(byte[] key) : base(key) { }
- public static IncrementalHash CreateHMAC(string n, byte[] key) => new IncrementalHash(key);
- public void AppendData(byte[] buffer, int offset, int count) => TransformBlock(buffer, offset, count, buffer, offset);
- public byte[] GetHashAndReset()
- {
- if (!_finalised)
- {
- byte[] dummy = new byte[0];
- TransformFinalBlock(dummy, 0, 0);
- _finalised = true;
- }
- return Hash;
- }
- }
-
- static class HashAlgorithmName
- {
- public static string SHA1 = null;
- }
-#endif
-
- private const int PWD_VER_LENGTH = 2;
-
// WinZip use iteration count of 1000 for PBKDF2 key generation
private const int KEY_ROUNDS = 1000;
@@ -53,6 +26,7 @@ static class HashAlgorithmName
private byte[] _authCode = null;
private bool _writeMode;
+ private Action _appendHmac = remaining => { };
///
/// Constructor.
@@ -75,7 +49,11 @@ public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMo
_encrPos = ENCRYPT_BLOCK;
// Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
+#if NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER
+ var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS, HashAlgorithmName.SHA1);
+#else
var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
+#endif
var rm = Aes.Create();
rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
_counterNonce = new byte[_blockSize];
@@ -84,12 +62,29 @@ public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMo
// Use empty IV for AES
_encryptor = rm.CreateEncryptor(key1bytes, new byte[16]);
- _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
+ _pwdVerifier = pdb.GetBytes(Zip.ZipConstants.AESPasswordVerifyLength);
//
_hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes);
_writeMode = writeMode;
}
+ ///
+ /// Append all of the last transformed input data to the HMAC.
+ ///
+ public void AppendAllPending()
+ {
+ _appendHmac(0);
+ }
+
+ ///
+ /// Append all except the number of bytes specified by remaining of the last transformed input data to the HMAC.
+ ///
+ /// The number of bytes not to be added to the HMAC. The excluded bytes are form the end.
+ public void AppendFinal(int remaining)
+ {
+ _appendHmac(remaining);
+ }
+
///
/// Implement the ICryptoTransform method.
///
@@ -99,8 +94,16 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
// This does not change the inputBuffer. Do this before decryption for read mode.
if (!_writeMode)
{
- _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
+ if (!ManualHmac)
+ {
+ _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
+ }
+ else
+ {
+ _appendHmac = remaining => _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount - remaining);
+ }
}
+
// Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
int ix = 0;
while (ix < inputCount)
@@ -132,91 +135,74 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
///
/// Returns the 2 byte password verifier
///
- public byte[] PwdVerifier
- {
- get
- {
- return _pwdVerifier;
- }
- }
+ public byte[] PwdVerifier => _pwdVerifier;
///
/// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream.
///
- public byte[] GetAuthCode()
- {
- if (_authCode == null)
- {
- _authCode = _hmacsha1.GetHashAndReset();
- }
- return _authCode;
- }
+ public byte[] GetAuthCode() => _authCode ?? (_authCode = _hmacsha1.GetHashAndReset());
#region ICryptoTransform Members
///
- /// Not implemented.
+ /// Transform final block and read auth code
///
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
- if(inputCount > 0)
- {
- throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0");
+ var buffer = Array.Empty();
+
+ // FIXME: When used together with `ZipAESStream`, the final block handling is done inside of it instead
+ // This should not be necessary anymore, and the entire `ZipAESStream` class should be replaced with a plain `CryptoStream`
+ if (inputCount != 0) {
+ if (inputCount > ZipAESStream.AUTH_CODE_LENGTH)
+ {
+ // At least one byte of data is preceeding the auth code
+ int finalBlock = inputCount - ZipAESStream.AUTH_CODE_LENGTH;
+ buffer = new byte[finalBlock];
+ TransformBlock(inputBuffer, inputOffset, finalBlock, buffer, 0);
+ }
+ else if (inputCount < ZipAESStream.AUTH_CODE_LENGTH)
+ throw new Zip.ZipException("Auth code missing from input stream");
+
+ // Read the authcode from the last 10 bytes
+ _authCode = _hmacsha1.GetHashAndReset();
}
- return new byte[0];
+
+
+ return buffer;
}
///
/// Gets the size of the input data blocks in bytes.
///
- public int InputBlockSize
- {
- get
- {
- return _blockSize;
- }
- }
+ public int InputBlockSize => _blockSize;
///
/// Gets the size of the output data blocks in bytes.
///
- public int OutputBlockSize
- {
- get
- {
- return _blockSize;
- }
- }
+ public int OutputBlockSize => _blockSize;
///
/// Gets a value indicating whether multiple blocks can be transformed.
///
- public bool CanTransformMultipleBlocks
- {
- get
- {
- return true;
- }
- }
+ public bool CanTransformMultipleBlocks => true;
///
/// Gets a value indicating whether the current transform can be reused.
///
- public bool CanReuseTransform
- {
- get
- {
- return true;
- }
- }
+ public bool CanReuseTransform => true;
+
+ ///
+ /// Gets of sets a value indicating if the HMAC is updates on every read of if updating the HMAC has to be controlled manually
+ /// Manual control of HMAC is needed in case not all the Transformed data should be automatically added to the HMAC.
+ /// E.g. because its not know how much data belongs to the current entry before the data is decrypted and analyzed.
+ ///
+ public bool ManualHmac { get; set; }
///
/// Cleanup internal state.
///
- public void Dispose()
- {
- _encryptor.Dispose();
- }
+ public void Dispose() => _encryptor.Dispose();
#endregion ICryptoTransform Members
}
diff --git a/src/ICSharpCode.SharpZipLib/GZip/GZipConstants.cs b/src/ICSharpCode.SharpZipLib/GZip/GZipConstants.cs
index 422cd97a4..a59799278 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GZipConstants.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GZipConstants.cs
@@ -1,58 +1,78 @@
+using System;
+using System.Text;
+
namespace ICSharpCode.SharpZipLib.GZip
{
///
/// This class contains constants used for gzip.
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
sealed public class GZipConstants
{
///
- /// Magic number found at start of GZIP header
+ /// First GZip identification byte
///
- public const int GZIP_MAGIC = 0x1F8B;
+ public const byte ID1 = 0x1F;
- /* The flag byte is divided into individual bits as follows:
+ ///
+ /// Second GZip identification byte
+ ///
+ public const byte ID2 = 0x8B;
- bit 0 FTEXT
- bit 1 FHCRC
- bit 2 FEXTRA
- bit 3 FNAME
- bit 4 FCOMMENT
- bit 5 reserved
- bit 6 reserved
- bit 7 reserved
- */
+ ///
+ /// Deflate compression method
+ ///
+ public const byte CompressionMethodDeflate = 0x8;
///
- /// Flag bit mask for text
+ /// Get the GZip specified encoding (CP-1252 if supported, otherwise ASCII)
///
- public const int FTEXT = 0x1;
+ public static Encoding Encoding
+ {
+ get
+ {
+ try
+ {
+ return Encoding.GetEncoding(1252);
+ }
+ catch
+ {
+ return Encoding.ASCII;
+ }
+ }
+ }
+ }
+
+ ///
+ /// GZip header flags
+ ///
+ [Flags]
+ public enum GZipFlags: byte
+ {
///
- /// Flag bitmask for Crc
+ /// Text flag hinting that the file is in ASCII
///
- public const int FHCRC = 0x2;
+ FTEXT = 0x1 << 0,
///
- /// Flag bit mask for extra
+ /// CRC flag indicating that a CRC16 preceeds the data
///
- public const int FEXTRA = 0x4;
+ FHCRC = 0x1 << 1,
///
- /// flag bitmask for name
+ /// Extra flag indicating that extra fields are present
///
- public const int FNAME = 0x8;
+ FEXTRA = 0x1 << 2,
///
- /// flag bit mask indicating comment is present
+ /// Filename flag indicating that the original filename is present
///
- public const int FCOMMENT = 0x10;
+ FNAME = 0x1 << 3,
///
- /// Initialise default instance.
+ /// Flag bit mask indicating that a comment is present
///
- /// Constructor is private to prevent instances being created.
- private GZipConstants()
- {
- }
+ FCOMMENT = 0x1 << 4,
}
}
diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
index a924a7ffc..20a4ded17 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
@@ -3,6 +3,7 @@
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.IO;
+using System.Text;
namespace ICSharpCode.SharpZipLib.GZip
{
@@ -54,6 +55,8 @@ public class GZipInputStream : InflaterInputStream
///
private bool completedLastBlock;
+ private string fileName;
+
#endregion Instance Fields
#region Constructors
@@ -149,6 +152,15 @@ public override int Read(byte[] buffer, int offset, int count)
}
}
+ ///
+ /// Retrieves the filename header field for the block last read
+ ///
+ ///
+ public string GetFilename()
+ {
+ return fileName;
+ }
+
#endregion Stream overrides
#region Support routines
@@ -170,132 +182,96 @@ private bool ReadHeader()
}
}
- // 1. Check the two magic bytes
var headCRC = new Crc32();
- int magic = inputBuffer.ReadLeByte();
- if (magic < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
+ // 1. Check the two magic bytes
+ var magic = inputBuffer.ReadLeByte();
headCRC.Update(magic);
- if (magic != (GZipConstants.GZIP_MAGIC >> 8))
+ if (magic != GZipConstants.ID1)
{
throw new GZipException("Error GZIP header, first magic byte doesn't match");
}
- //magic = baseInputStream.ReadByte();
magic = inputBuffer.ReadLeByte();
-
- if (magic < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
-
- if (magic != (GZipConstants.GZIP_MAGIC & 0xFF))
+ if (magic != GZipConstants.ID2)
{
throw new GZipException("Error GZIP header, second magic byte doesn't match");
}
-
headCRC.Update(magic);
// 2. Check the compression type (must be 8)
- int compressionType = inputBuffer.ReadLeByte();
-
- if (compressionType < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
+ var compressionType = inputBuffer.ReadLeByte();
- if (compressionType != 8)
+ if (compressionType != GZipConstants.CompressionMethodDeflate)
{
throw new GZipException("Error GZIP header, data not in deflate format");
}
headCRC.Update(compressionType);
// 3. Check the flags
- int flags = inputBuffer.ReadLeByte();
- if (flags < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
- headCRC.Update(flags);
-
- /* This flag byte is divided into individual bits as follows:
+ var flagsByte = inputBuffer.ReadLeByte();
- bit 0 FTEXT
- bit 1 FHCRC
- bit 2 FEXTRA
- bit 3 FNAME
- bit 4 FCOMMENT
- bit 5 reserved
- bit 6 reserved
- bit 7 reserved
- */
+ headCRC.Update(flagsByte);
// 3.1 Check the reserved bits are zero
- if ((flags & 0xE0) != 0)
+ if ((flagsByte & 0xE0) != 0)
{
throw new GZipException("Reserved flag bits in GZIP header != 0");
}
+ var flags = (GZipFlags)flagsByte;
+
// 4.-6. Skip the modification time, extra flags, and OS type
for (int i = 0; i < 6; i++)
{
- int readByte = inputBuffer.ReadLeByte();
- if (readByte < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
- headCRC.Update(readByte);
+ headCRC.Update(inputBuffer.ReadLeByte());
}
// 7. Read extra field
- if ((flags & GZipConstants.FEXTRA) != 0)
+ if (flags.HasFlag(GZipFlags.FEXTRA))
{
// XLEN is total length of extra subfields, we will skip them all
- int len1, len2;
- len1 = inputBuffer.ReadLeByte();
- len2 = inputBuffer.ReadLeByte();
- if ((len1 < 0) || (len2 < 0))
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
+ var len1 = inputBuffer.ReadLeByte();
+ var len2 = inputBuffer.ReadLeByte();
+
headCRC.Update(len1);
headCRC.Update(len2);
int extraLen = (len2 << 8) | len1; // gzip is LSB first
for (int i = 0; i < extraLen; i++)
{
- int readByte = inputBuffer.ReadLeByte();
- if (readByte < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
- headCRC.Update(readByte);
+ headCRC.Update(inputBuffer.ReadLeByte());
}
}
// 8. Read file name
- if ((flags & GZipConstants.FNAME) != 0)
+ if (flags.HasFlag(GZipFlags.FNAME))
{
+ var fname = new byte[1024];
+ var fnamePos = 0;
int readByte;
while ((readByte = inputBuffer.ReadLeByte()) > 0)
{
+ if (fnamePos < 1024)
+ {
+ fname[fnamePos++] = (byte)readByte;
+ }
headCRC.Update(readByte);
}
- if (readByte < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
headCRC.Update(readByte);
+
+ fileName = GZipConstants.Encoding.GetString(fname, 0, fnamePos);
+ }
+ else
+ {
+ fileName = null;
}
// 9. Read comment
- if ((flags & GZipConstants.FCOMMENT) != 0)
+ if (flags.HasFlag(GZipFlags.FCOMMENT))
{
int readByte;
while ((readByte = inputBuffer.ReadLeByte()) > 0)
@@ -303,16 +279,11 @@ bit 7 reserved
headCRC.Update(readByte);
}
- if (readByte < 0)
- {
- throw new EndOfStreamException("EOS reading GZIP header");
- }
-
headCRC.Update(readByte);
}
// 10. Read header CRC
- if ((flags & GZipConstants.FHCRC) != 0)
+ if (flags.HasFlag(GZipFlags.FHCRC))
{
int tempByte;
int crcval = inputBuffer.ReadLeByte();
diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
index 3079b04aa..ade624818 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipOutputStream.cs
@@ -3,6 +3,9 @@
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.GZip
{
@@ -53,6 +56,10 @@ private enum OutputState
private OutputState state_ = OutputState.Header;
+ private string fileName;
+
+ private GZipFlags flags = 0;
+
#endregion Instance Fields
#region Constructors
@@ -111,6 +118,26 @@ public int GetLevel()
return deflater_.GetLevel();
}
+ ///
+ /// Original filename
+ ///
+ public string FileName
+ {
+ get => fileName;
+ set
+ {
+ fileName = CleanFilename(value);
+ if (string.IsNullOrEmpty(fileName))
+ {
+ flags &= ~GZipFlags.FNAME;
+ }
+ else
+ {
+ flags |= GZipFlags.FNAME;
+ }
+ }
+ }
+
#endregion Public API
#region Stream overrides
@@ -159,6 +186,44 @@ protected override void Dispose(bool disposing)
}
}
}
+
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await FinishAsync(CancellationToken.None);
+ }
+ finally
+ {
+ if (state_ != OutputState.Closed)
+ {
+ state_ = OutputState.Closed;
+ if (IsStreamOwner)
+ {
+ await baseOutputStream_.DisposeAsync();
+ }
+ }
+
+ await base.DisposeAsync();
+ }
+ }
+#endif
+
+ ///
+ /// Flushes the stream by ensuring the header is written, and then calling Flush
+ /// on the deflater.
+ ///
+ public override void Flush()
+ {
+ if (state_ == OutputState.Header)
+ {
+ WriteHeader();
+ }
+
+ base.Flush();
+ }
#endregion Stream overrides
@@ -179,60 +244,119 @@ public override void Finish()
{
state_ = OutputState.Finished;
base.Finish();
-
- var totalin = (uint)(deflater_.TotalIn & 0xffffffff);
- var crcval = (uint)(crc.Value & 0xffffffff);
-
- byte[] gzipFooter;
-
- unchecked
- {
- gzipFooter = new byte[] {
- (byte) crcval, (byte) (crcval >> 8),
- (byte) (crcval >> 16), (byte) (crcval >> 24),
-
- (byte) totalin, (byte) (totalin >> 8),
- (byte) (totalin >> 16), (byte) (totalin >> 24)
- };
- }
-
+ var gzipFooter = GetFooter();
baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length);
}
}
+
+ ///
+ public override async Task FlushAsync(CancellationToken ct)
+ {
+ await WriteHeaderAsync();
+ await base.FlushAsync(ct);
+ }
+
+
+ ///
+ public override async Task FinishAsync(CancellationToken ct)
+ {
+ // If no data has been written a header should be added.
+ if (state_ == OutputState.Header)
+ {
+ await WriteHeaderAsync();
+ }
+
+ if (state_ == OutputState.Footer)
+ {
+ state_ = OutputState.Finished;
+ await base.FinishAsync(ct);
+ var gzipFooter = GetFooter();
+ await baseOutputStream_.WriteAsync(gzipFooter, 0, gzipFooter.Length, ct);
+ }
+ }
#endregion DeflaterOutputStream overrides
#region Support Routines
- private void WriteHeader()
+ private byte[] GetFooter()
{
- if (state_ == OutputState.Header)
+ var totalin = (uint)(deflater_.TotalIn & 0xffffffff);
+ var crcval = (uint)(crc.Value & 0xffffffff);
+
+ byte[] gzipFooter;
+
+ unchecked
{
- state_ = OutputState.Footer;
+ gzipFooter = new [] {
+ (byte) crcval,
+ (byte) (crcval >> 8),
+ (byte) (crcval >> 16),
+ (byte) (crcval >> 24),
+ (byte) totalin,
+ (byte) (totalin >> 8),
+ (byte) (totalin >> 16),
+ (byte) (totalin >> 24),
+ };
+ }
- var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
- byte[] gzipHeader = {
- // The two magic bytes
- (byte) (GZipConstants.GZIP_MAGIC >> 8), (byte) (GZipConstants.GZIP_MAGIC & 0xff),
+ return gzipFooter;
+ }
- // The compression type
- (byte) Deflater.DEFLATED,
+ private byte[] GetHeader()
+ {
+ var modTime = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
+ byte[] gzipHeader = {
+ // The two magic bytes
+ GZipConstants.ID1,
+ GZipConstants.ID2,
- // The flags (not set)
- 0,
+ // The compression type
+ GZipConstants.CompressionMethodDeflate,
- // The modification time
- (byte) mod_time, (byte) (mod_time >> 8),
- (byte) (mod_time >> 16), (byte) (mod_time >> 24),
+ // The flags (not set)
+ (byte)flags,
- // The extra flags
- 0,
+ // The modification time
+ (byte) modTime, (byte) (modTime >> 8),
+ (byte) (modTime >> 16), (byte) (modTime >> 24),
- // The OS type (unknown)
- (byte) 255
- };
- baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
+ // The extra flags
+ 0,
+
+ // The OS type (unknown)
+ 255
+ };
+
+ if (!flags.HasFlag(GZipFlags.FNAME))
+ {
+ return gzipHeader;
}
+
+
+ return gzipHeader
+ .Concat(GZipConstants.Encoding.GetBytes(fileName))
+ .Concat(new byte []{0}) // End filename string with a \0
+ .ToArray();
+ }
+
+ private static string CleanFilename(string path)
+ => path.Substring(path.LastIndexOf('/') + 1);
+
+ private void WriteHeader()
+ {
+ if (state_ != OutputState.Header) return;
+ state_ = OutputState.Footer;
+ var gzipHeader = GetHeader();
+ baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
+ }
+
+ private async Task WriteHeaderAsync()
+ {
+ if (state_ != OutputState.Header) return;
+ state_ = OutputState.Footer;
+ var gzipHeader = GetHeader();
+ await baseOutputStream_.WriteAsync(gzipHeader, 0, gzipHeader.Length);
}
#endregion Support Routines
diff --git a/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj b/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
index ea7bdf756..0dfc04003 100644
--- a/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
+++ b/src/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.csproj
@@ -1,9 +1,11 @@
- netstandard2;net45
- True
- ICSharpCode.SharpZipLib.snk
+ netstandard2.0;netstandard2.1;net6.0
+ true
+ true
+ true
+ ../../assets/ICSharpCode.SharpZipLib.snk
true
true
$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
@@ -11,22 +13,22 @@
- 1.2.0.7
- 1.2.0.7
- 1.2.0
+ 1.4.0
+ $(Version).12
+ $(FileVersion)
SharpZipLib
ICSharpCode
ICSharpCode
SharpZipLib (#ziplib, formerly NZipLib) is a compression library for Zip, GZip, BZip2, and Tar written entirely in C# for .NET. It is implemented as an assembly (installable in the GAC), and thus can easily be incorporated into other projects (in any .NET language)
MIT
http://icsharpcode.github.io/SharpZipLib/
- http://icsharpcode.github.io/SharpZipLib/assets/sharpziplib-nuget-256x256.png
+ images/sharpziplib-nuget-256x256.png
https://github.com/icsharpcode/SharpZipLib
- Copyright © 2000-2019 SharpZipLib Contributors
+ Copyright © 2000-2022 SharpZipLib Contributors
Compression Library Zip GZip BZip2 LZW Tar
en-US
-Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.2 for more information.
+Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.4.0 for more information.
https://github.com/icsharpcode/SharpZipLib
@@ -34,4 +36,16 @@ Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.2 for more
+
+
+
+
+
+
+
+ True
+ images
+
+
+
diff --git a/src/ICSharpCode.SharpZipLib/Lzw/LzwConstants.cs b/src/ICSharpCode.SharpZipLib/Lzw/LzwConstants.cs
index b7dc60f59..88934830f 100644
--- a/src/ICSharpCode.SharpZipLib/Lzw/LzwConstants.cs
+++ b/src/ICSharpCode.SharpZipLib/Lzw/LzwConstants.cs
@@ -3,6 +3,7 @@ namespace ICSharpCode.SharpZipLib.Lzw
///
/// This class contains constants used for LZW
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
sealed public class LzwConstants
{
///
diff --git a/src/ICSharpCode.SharpZipLib/Lzw/LzwInputStream.cs b/src/ICSharpCode.SharpZipLib/Lzw/LzwInputStream.cs
index fc2a65aaa..1045ef778 100644
--- a/src/ICSharpCode.SharpZipLib/Lzw/LzwInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Lzw/LzwInputStream.cs
@@ -485,7 +485,7 @@ public override void SetLength(long value)
/// Writes a sequence of bytes to stream and advances the current position
/// This method always throws a NotSupportedException
///
- /// Thew buffer containing data to write.
+ /// The buffer containing data to write.
/// The offset of the first byte to write.
/// The number of bytes to write.
/// Any access
diff --git a/src/ICSharpCode.SharpZipLib/Tar/InvalidHeaderException.cs b/src/ICSharpCode.SharpZipLib/Tar/InvalidHeaderException.cs
index a2e114052..9f385e425 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/InvalidHeaderException.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/InvalidHeaderException.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.Serialization;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -6,6 +7,7 @@ namespace ICSharpCode.SharpZipLib.Tar
/// This exception is used to indicate that there is a problem
/// with a TAR archive header.
///
+ [Serializable]
public class InvalidHeaderException : TarException
{
///
@@ -33,5 +35,21 @@ public InvalidHeaderException(string message, Exception exception)
: base(message, exception)
{
}
+
+ ///
+ /// Initializes a new instance of the InvalidHeaderException class with serialized data.
+ ///
+ ///
+ /// The System.Runtime.Serialization.SerializationInfo that holds the serialized
+ /// object data about the exception being thrown.
+ ///
+ ///
+ /// The System.Runtime.Serialization.StreamingContext that contains contextual information
+ /// about the source or destination.
+ ///
+ protected InvalidHeaderException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs b/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs
index 133b33081..878649017 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
+using System.Numerics;
using System.Text;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -61,7 +63,7 @@ protected TarArchive()
}
///
- /// Initalise a TarArchive for input.
+ /// Initialise a TarArchive for input.
///
/// The to use for input.
protected TarArchive(TarInputStream stream)
@@ -100,7 +102,22 @@ protected TarArchive(TarOutputStream stream)
///
/// The stream to retrieve archive data from.
/// Returns a new suitable for reading from.
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public static TarArchive CreateInputTarArchive(Stream inputStream)
+ {
+ return CreateInputTarArchive(inputStream, null);
+ }
+
+ ///
+ /// The InputStream based constructors create a TarArchive for the
+ /// purposes of extracting or listing a tar archive. Thus, use
+ /// these constructors when you wish to extract files from or list
+ /// the contents of an existing tar archive.
+ ///
+ /// The stream to retrieve archive data from.
+ /// The used for the Name fields, or null for ASCII only
+ /// Returns a new suitable for reading from.
+ public static TarArchive CreateInputTarArchive(Stream inputStream, Encoding nameEncoding)
{
if (inputStream == null)
{
@@ -116,7 +133,7 @@ public static TarArchive CreateInputTarArchive(Stream inputStream)
}
else
{
- result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);
+ result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding);
}
return result;
}
@@ -127,7 +144,20 @@ public static TarArchive CreateInputTarArchive(Stream inputStream)
/// A stream containing the tar archive contents
/// The blocking factor to apply
/// Returns a suitable for reading.
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)
+ {
+ return CreateInputTarArchive(inputStream, blockFactor, null);
+ }
+
+ ///
+ /// Create TarArchive for reading setting block factor
+ ///
+ /// A stream containing the tar archive contents
+ /// The blocking factor to apply
+ /// The used for the Name fields, or null for ASCII only
+ /// Returns a suitable for reading.
+ public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor, Encoding nameEncoding)
{
if (inputStream == null)
{
@@ -139,15 +169,15 @@ public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFact
throw new ArgumentException("TarInputStream not valid");
}
- return new TarArchive(new TarInputStream(inputStream, blockFactor));
+ return new TarArchive(new TarInputStream(inputStream, blockFactor, nameEncoding));
}
-
///
/// Create a TarArchive for writing to, using the default blocking factor
///
/// The to write to
+ /// The used for the Name fields, or null for ASCII only
/// Returns a suitable for writing.
- public static TarArchive CreateOutputTarArchive(Stream outputStream)
+ public static TarArchive CreateOutputTarArchive(Stream outputStream, Encoding nameEncoding)
{
if (outputStream == null)
{
@@ -163,10 +193,19 @@ public static TarArchive CreateOutputTarArchive(Stream outputStream)
}
else
{
- result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);
+ result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding);
}
return result;
}
+ ///
+ /// Create a TarArchive for writing to, using the default blocking factor
+ ///
+ /// The to write to
+ /// Returns a suitable for writing.
+ public static TarArchive CreateOutputTarArchive(Stream outputStream)
+ {
+ return CreateOutputTarArchive(outputStream, null);
+ }
///
/// Create a tar archive for writing.
@@ -175,6 +214,17 @@ public static TarArchive CreateOutputTarArchive(Stream outputStream)
/// The blocking factor to use for buffering.
/// Returns a suitable for writing.
public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)
+ {
+ return CreateOutputTarArchive(outputStream, blockFactor, null);
+ }
+ ///
+ /// Create a tar archive for writing.
+ ///
+ /// The stream to write to
+ /// The blocking factor to use for buffering.
+ /// The used for the Name fields, or null for ASCII only
+ /// Returns a suitable for writing.
+ public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor, Encoding nameEncoding)
{
if (outputStream == null)
{
@@ -186,7 +236,7 @@ public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFa
throw new ArgumentException("TarOutputStream is not valid");
}
- return new TarArchive(new TarOutputStream(outputStream, blockFactor));
+ return new TarArchive(new TarOutputStream(outputStream, blockFactor, nameEncoding));
}
#endregion Static factory methods
@@ -306,8 +356,7 @@ public string RootPath
{
throw new ObjectDisposedException("TarArchive");
}
- // Convert to forward slashes for matching. Trim trailing / for correct final path
- rootPath = value.Replace('\\', '/').TrimEnd('/');
+ rootPath = value.ToTarArchivePath().TrimEnd('/');
}
}
@@ -546,13 +595,25 @@ public void ListContents()
///
/// The destination directory into which to extract.
///
- public void ExtractContents(string destinationDirectory)
+ public void ExtractContents(string destinationDirectory)
+ => ExtractContents(destinationDirectory, false);
+
+ ///
+ /// Perform the "extract" command and extract the contents of the archive.
+ ///
+ ///
+ /// The destination directory into which to extract.
+ ///
+ /// Allow parent directory traversal in file paths (e.g. ../file)
+ public void ExtractContents(string destinationDirectory, bool allowParentTraversal)
{
if (isDisposed)
{
throw new ObjectDisposedException("TarArchive");
}
+ var fullDistDir = Path.GetFullPath(destinationDirectory).TrimEnd('/', '\\');
+
while (true)
{
TarEntry entry = tarIn.GetNextEntry();
@@ -565,7 +626,7 @@ public void ExtractContents(string destinationDirectory)
if (entry.TarHeader.TypeFlag == TarHeader.LF_LINK || entry.TarHeader.TypeFlag == TarHeader.LF_SYMLINK)
continue;
- ExtractEntry(destinationDirectory, entry);
+ ExtractEntry(fullDistDir, entry, allowParentTraversal);
}
}
@@ -579,7 +640,8 @@ public void ExtractContents(string destinationDirectory)
///
/// The TarEntry returned by tarIn.GetNextEntry().
///
- private void ExtractEntry(string destDir, TarEntry entry)
+ /// Allow parent directory traversal in file paths (e.g. ../file)
+ private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraversal)
{
OnProgressMessageEvent(entry, null);
@@ -595,6 +657,14 @@ private void ExtractEntry(string destDir, TarEntry entry)
name = name.Replace('/', Path.DirectorySeparatorChar);
string destFile = Path.Combine(destDir, name);
+ var destFileDir = Path.GetDirectoryName(Path.GetFullPath(destFile)) ?? "";
+
+ var isRootDir = entry.IsDirectory && entry.Name == "";
+
+ if (!allowParentTraversal && !isRootDir && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase))
+ {
+ throw new InvalidNameException("Parent traversal in paths is not allowed");
+ }
if (entry.IsDirectory)
{
@@ -756,7 +826,7 @@ private void WriteEntryCore(TarEntry sourceEntry, bool recurse)
{
if (!IsBinary(entryFilename))
{
- tempFileName = Path.GetTempFileName();
+ tempFileName = PathUtils.GetTempFileName();
using (StreamReader inStream = File.OpenText(entryFilename))
{
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs b/src/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs
index 43a6d5cdf..b190ed1f3 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs
@@ -1,5 +1,8 @@
using System;
+using System.Buffers;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -72,10 +75,7 @@ or which contains garbage records after a zero block.
/// This is equal to the multiplied by the
public int RecordSize
{
- get
- {
- return recordSize;
- }
+ get { return recordSize; }
}
///
@@ -95,10 +95,7 @@ public int GetRecordSize()
/// This is the number of blocks in each record.
public int BlockFactor
{
- get
- {
- return blockFactor;
- }
+ get { return blockFactor; }
}
///
@@ -207,7 +204,7 @@ private void Initialize(int archiveBlockFactor)
{
blockFactor = archiveBlockFactor;
recordSize = archiveBlockFactor * BlockSize;
- recordBuffer = new byte[RecordSize];
+ recordBuffer = ArrayPool.Shared.Rent(RecordSize);
if (inputStream != null)
{
@@ -289,7 +286,14 @@ public static bool IsEndOfArchiveBlock(byte[] block)
///
/// Skip over a block on the input stream.
///
- public void SkipBlock()
+ public void SkipBlock() => SkipBlockAsync(CancellationToken.None, false).GetAwaiter().GetResult();
+
+ ///
+ /// Skip over a block on the input stream.
+ ///
+ public Task SkipBlockAsync(CancellationToken ct) => SkipBlockAsync(ct, true).AsTask();
+
+ private async ValueTask SkipBlockAsync(CancellationToken ct, bool isAsync)
{
if (inputStream == null)
{
@@ -298,7 +302,7 @@ public void SkipBlock()
if (currentBlockIndex >= BlockFactor)
{
- if (!ReadRecord())
+ if (!await ReadRecordAsync(ct, isAsync))
{
throw new TarException("Failed to read a record");
}
@@ -322,7 +326,7 @@ public byte[] ReadBlock()
if (currentBlockIndex >= BlockFactor)
{
- if (!ReadRecord())
+ if (!ReadRecordAsync(CancellationToken.None, false).GetAwaiter().GetResult())
{
throw new TarException("Failed to read a record");
}
@@ -335,17 +339,41 @@ public byte[] ReadBlock()
return result;
}
+ internal async ValueTask ReadBlockIntAsync(byte[] buffer, CancellationToken ct, bool isAsync)
+ {
+ if (buffer.Length != BlockSize)
+ {
+ throw new ArgumentException("BUG: buffer must have length BlockSize");
+ }
+
+ if (inputStream == null)
+ {
+ throw new TarException("TarBuffer.ReadBlock - no input stream defined");
+ }
+
+ if (currentBlockIndex >= BlockFactor)
+ {
+ if (!await ReadRecordAsync(ct, isAsync))
+ {
+ throw new TarException("Failed to read a record");
+ }
+ }
+
+ recordBuffer.AsSpan().Slice(currentBlockIndex * BlockSize, BlockSize).CopyTo(buffer);
+ currentBlockIndex++;
+ }
+
///
/// Read a record from data stream.
///
///
/// false if End-Of-File, else true.
///
- private bool ReadRecord()
+ private async ValueTask ReadRecordAsync(CancellationToken ct, bool isAsync)
{
if (inputStream == null)
{
- throw new TarException("no input stream stream defined");
+ throw new TarException("no input stream defined");
}
currentBlockIndex = 0;
@@ -355,7 +383,9 @@ private bool ReadRecord()
while (bytesNeeded > 0)
{
- long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded);
+ long numBytes = isAsync
+ ? await inputStream.ReadAsync(recordBuffer, offset, bytesNeeded, ct)
+ : inputStream.Read(recordBuffer, offset, bytesNeeded);
//
// NOTE
@@ -438,6 +468,18 @@ public int GetCurrentRecordNum()
return currentRecordIndex;
}
+ ///
+ /// Write a block of data to the archive.
+ ///
+ ///
+ /// The data to write to the archive.
+ ///
+ ///
+ public ValueTask WriteBlockAsync(byte[] block, CancellationToken ct)
+ {
+ return WriteBlockAsync(block, 0, ct);
+ }
+
///
/// Write a block of data to the archive.
///
@@ -446,30 +488,24 @@ public int GetCurrentRecordNum()
///
public void WriteBlock(byte[] block)
{
- if (block == null)
- {
- throw new ArgumentNullException(nameof(block));
- }
-
- if (outputStream == null)
- {
- throw new TarException("TarBuffer.WriteBlock - no output stream defined");
- }
-
- if (block.Length != BlockSize)
- {
- string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block size of '{1}'",
- block.Length, BlockSize);
- throw new TarException(errorText);
- }
-
- if (currentBlockIndex >= BlockFactor)
- {
- WriteRecord();
- }
+ WriteBlock(block, 0);
+ }
- Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
- currentBlockIndex++;
+ ///
+ /// Write an archive record to the archive, where the record may be
+ /// inside of a larger array buffer. The buffer must be "offset plus
+ /// record size" long.
+ ///
+ ///
+ /// The buffer containing the record data to write.
+ ///
+ ///
+ /// The offset of the record data within buffer.
+ ///
+ ///
+ public ValueTask WriteBlockAsync(byte[] buffer, int offset, CancellationToken ct)
+ {
+ return WriteBlockAsync(buffer, offset, ct, true);
}
///
@@ -484,6 +520,11 @@ public void WriteBlock(byte[] block)
/// The offset of the record data within buffer.
///
public void WriteBlock(byte[] buffer, int offset)
+ {
+ WriteBlockAsync(buffer, offset, CancellationToken.None, false).GetAwaiter().GetResult();
+ }
+
+ internal async ValueTask WriteBlockAsync(byte[] buffer, int offset, CancellationToken ct, bool isAsync)
{
if (buffer == null)
{
@@ -492,7 +533,7 @@ public void WriteBlock(byte[] buffer, int offset)
if (outputStream == null)
{
- throw new TarException("TarBuffer.WriteBlock - no output stream stream defined");
+ throw new TarException("TarBuffer.WriteBlock - no output stream defined");
}
if ((offset < 0) || (offset >= buffer.Length))
@@ -502,14 +543,15 @@ public void WriteBlock(byte[] buffer, int offset)
if ((offset + BlockSize) > buffer.Length)
{
- string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'",
+ string errorText = string.Format(
+ "TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'",
buffer.Length, offset, recordSize);
throw new TarException(errorText);
}
if (currentBlockIndex >= BlockFactor)
{
- WriteRecord();
+ await WriteRecordAsync(CancellationToken.None, isAsync);
}
Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
@@ -520,15 +562,23 @@ public void WriteBlock(byte[] buffer, int offset)
///
/// Write a TarBuffer record to the archive.
///
- private void WriteRecord()
+ private async ValueTask WriteRecordAsync(CancellationToken ct, bool isAsync)
{
if (outputStream == null)
{
throw new TarException("TarBuffer.WriteRecord no output stream defined");
}
- outputStream.Write(recordBuffer, 0, RecordSize);
- outputStream.Flush();
+ if (isAsync)
+ {
+ await outputStream.WriteAsync(recordBuffer, 0, RecordSize, ct);
+ await outputStream.FlushAsync(ct);
+ }
+ else
+ {
+ outputStream.Write(recordBuffer, 0, RecordSize);
+ outputStream.Flush();
+ }
currentBlockIndex = 0;
currentRecordIndex++;
@@ -539,7 +589,7 @@ private void WriteRecord()
///
/// Any trailing bytes are set to zero which is by definition correct behaviour
/// for the end of a tar stream.
- private void WriteFinalRecord()
+ private async ValueTask WriteFinalRecordAsync(CancellationToken ct, bool isAsync)
{
if (outputStream == null)
{
@@ -550,36 +600,77 @@ private void WriteFinalRecord()
{
int dataBytes = currentBlockIndex * BlockSize;
Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes);
- WriteRecord();
+ await WriteRecordAsync(ct, isAsync);
}
- outputStream.Flush();
+ if (isAsync)
+ {
+ await outputStream.FlushAsync(ct);
+ }
+ else
+ {
+ outputStream.Flush();
+ }
}
///
/// Close the TarBuffer. If this is an output buffer, also flush the
/// current block before closing.
///
- public void Close()
+ public void Close() => CloseAsync(CancellationToken.None, false).GetAwaiter().GetResult();
+
+ ///
+ /// Close the TarBuffer. If this is an output buffer, also flush the
+ /// current block before closing.
+ ///
+ public Task CloseAsync(CancellationToken ct) => CloseAsync(ct, true).AsTask();
+
+ private async ValueTask CloseAsync(CancellationToken ct, bool isAsync)
{
if (outputStream != null)
{
- WriteFinalRecord();
+ await WriteFinalRecordAsync(ct, isAsync);
if (IsStreamOwner)
{
- outputStream.Dispose();
+ if (isAsync)
+ {
+#if NETSTANDARD2_1_OR_GREATER
+ await outputStream.DisposeAsync();
+#else
+ outputStream.Dispose();
+#endif
+ }
+ else
+ {
+ outputStream.Dispose();
+ }
}
+
outputStream = null;
}
else if (inputStream != null)
{
if (IsStreamOwner)
{
- inputStream.Dispose();
+ if (isAsync)
+ {
+#if NETSTANDARD2_1_OR_GREATER
+ await inputStream.DisposeAsync();
+#else
+ inputStream.Dispose();
+#endif
+ }
+ else
+ {
+ inputStream.Dispose();
+ }
}
+
inputStream = null;
}
+
+ ArrayPool.Shared.Return(recordBuffer);
}
#region Instance Fields
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs b/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs
index f7d2a493d..82c813367 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Text;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -49,10 +51,25 @@ private TarEntry()
///
/// The header bytes from a tar archive entry.
///
- public TarEntry(byte[] headerBuffer)
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
+ public TarEntry(byte[] headerBuffer) : this(headerBuffer, null)
+ {
+ }
+
+ ///
+ /// Construct an entry from an archive's header bytes. File is set
+ /// to null.
+ ///
+ ///
+ /// The header bytes from a tar archive entry.
+ ///
+ ///
+ /// The used for the Name fields, or null for ASCII only
+ ///
+ public TarEntry(byte[] headerBuffer, Encoding nameEncoding)
{
header = new TarHeader();
- header.ParseBuffer(headerBuffer);
+ header.ParseBuffer(headerBuffer, nameEncoding);
}
///
@@ -97,7 +114,8 @@ public object Clone()
public static TarEntry CreateTarEntry(string name)
{
var entry = new TarEntry();
- TarEntry.NameTarHeader(entry.header, name);
+
+ entry.NameTarHeader(name);
return entry;
}
@@ -171,10 +189,7 @@ public bool IsDescendent(TarEntry toTest)
///
public TarHeader TarHeader
{
- get
- {
- return header;
- }
+ get { return header; }
}
///
@@ -182,14 +197,8 @@ public TarHeader TarHeader
///
public string Name
{
- get
- {
- return header.Name;
- }
- set
- {
- header.Name = value;
- }
+ get { return header.Name; }
+ set { header.Name = value; }
}
///
@@ -197,14 +206,8 @@ public string Name
///
public int UserId
{
- get
- {
- return header.UserId;
- }
- set
- {
- header.UserId = value;
- }
+ get { return header.UserId; }
+ set { header.UserId = value; }
}
///
@@ -212,14 +215,8 @@ public int UserId
///
public int GroupId
{
- get
- {
- return header.GroupId;
- }
- set
- {
- header.GroupId = value;
- }
+ get { return header.GroupId; }
+ set { header.GroupId = value; }
}
///
@@ -227,14 +224,8 @@ public int GroupId
///
public string UserName
{
- get
- {
- return header.UserName;
- }
- set
- {
- header.UserName = value;
- }
+ get { return header.UserName; }
+ set { header.UserName = value; }
}
///
@@ -242,14 +233,8 @@ public string UserName
///
public string GroupName
{
- get
- {
- return header.GroupName;
- }
- set
- {
- header.GroupName = value;
- }
+ get { return header.GroupName; }
+ set { header.GroupName = value; }
}
///
@@ -287,14 +272,8 @@ public void SetNames(string userName, string groupName)
///
public DateTime ModTime
{
- get
- {
- return header.ModTime;
- }
- set
- {
- header.ModTime = value;
- }
+ get { return header.ModTime; }
+ set { header.ModTime = value; }
}
///
@@ -305,10 +284,7 @@ public DateTime ModTime
///
public string File
{
- get
- {
- return file;
- }
+ get { return file; }
}
///
@@ -316,14 +292,8 @@ public string File
///
public long Size
{
- get
- {
- return header.Size;
- }
- set
- {
- header.Size = value;
- }
+ get { return header.Size; }
+ set { header.Size = value; }
}
///
@@ -402,15 +372,10 @@ public void GetFileTarHeader(TarHeader header, string file)
}
*/
- name = name.Replace(Path.DirectorySeparatorChar, '/');
-
// No absolute pathnames
// Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\",
// so we loop on starting /'s.
- while (name.StartsWith("/", StringComparison.Ordinal))
- {
- name = name.Substring(1);
- }
+ name = name.ToTarArchivePath();
header.LinkName = String.Empty;
header.Name = name;
@@ -433,7 +398,8 @@ public void GetFileTarHeader(TarHeader header, string file)
header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length;
}
- header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime();
+ header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar))
+ .ToUniversalTime();
header.DevMajor = 0;
header.DevMinor = 0;
}
@@ -449,7 +415,7 @@ public TarEntry[] GetDirectoryEntries()
{
if ((file == null) || !Directory.Exists(file))
{
- return new TarEntry[0];
+ return Empty.Array();
}
string[] list = Directory.GetFileSystemEntries(file);
@@ -469,9 +435,24 @@ public TarEntry[] GetDirectoryEntries()
///
/// The tar entry header buffer to fill in.
///
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public void WriteEntryHeader(byte[] outBuffer)
{
- header.WriteHeader(outBuffer);
+ WriteEntryHeader(outBuffer, null);
+ }
+
+ ///
+ /// Write an entry's header information to a header buffer.
+ ///
+ ///
+ /// The tar entry header buffer to fill in.
+ ///
+ ///
+ /// The used for the Name fields, or null for ASCII only
+ ///
+ public void WriteEntryHeader(byte[] outBuffer, Encoding nameEncoding)
+ {
+ header.WriteHeader(outBuffer, nameEncoding);
}
///
@@ -484,27 +465,38 @@ public void WriteEntryHeader(byte[] outBuffer)
///
/// The new name to place into the header buffer.
///
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
static public void AdjustEntryName(byte[] buffer, string newName)
{
- TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN);
+ AdjustEntryName(buffer, newName, null);
}
///
- /// Fill in a TarHeader given only the entry's name.
+ /// Convenience method that will modify an entry's name directly
+ /// in place in an entry header buffer byte array.
///
- ///
- /// The TarHeader to fill in.
+ ///
+ /// The buffer containing the entry header to modify.
+ ///
+ ///
+ /// The new name to place into the header buffer.
+ ///
+ ///
+ /// The used for the Name fields, or null for ASCII only
///
+ static public void AdjustEntryName(byte[] buffer, string newName, Encoding nameEncoding)
+ {
+ TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN, nameEncoding);
+ }
+
+ ///
+ /// Fill in a TarHeader given only the entry's name.
+ ///
///
/// The tar entry name.
///
- static public void NameTarHeader(TarHeader header, string name)
+ public void NameTarHeader(string name)
{
- if (header == null)
- {
- throw new ArgumentNullException(nameof(header));
- }
-
if (name == null)
{
throw new ArgumentNullException(nameof(name));
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs b/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs
index d1d438ad0..b711e6d54 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Text;
namespace ICSharpCode.SharpZipLib.Tar
@@ -26,7 +27,10 @@ public class TarExtendedHeaderReader
private int state = LENGTH;
- private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' };
+ private int currHeaderLength;
+ private int currHeaderRead;
+
+ private static readonly byte[] StateNext = { (byte)' ', (byte)'=', (byte)'\n' };
///
/// Creates a new .
@@ -46,23 +50,46 @@ public void Read(byte[] buffer, int length)
for (int i = 0; i < length; i++)
{
byte next = buffer[i];
+
+ var foundStateEnd = state == VALUE
+ ? currHeaderRead == currHeaderLength -1
+ : next == StateNext[state];
- if (next == StateNext[state])
+ if (foundStateEnd)
{
Flush();
headerParts[state] = sb.ToString();
sb.Clear();
-
+
if (++state == END)
{
- headers.Add(headerParts[KEY], headerParts[VALUE]);
+ if (!headers.ContainsKey(headerParts[KEY]))
+ {
+ headers.Add(headerParts[KEY], headerParts[VALUE]);
+ }
+
headerParts = new string[3];
+ currHeaderLength = 0;
+ currHeaderRead = 0;
state = LENGTH;
}
+ else
+ {
+ currHeaderRead++;
+ }
+
+
+ if (state != VALUE) continue;
+
+ if (int.TryParse(headerParts[LENGTH], out var vl))
+ {
+ currHeaderLength = vl;
+ }
}
else
{
byteBuffer[bbIndex++] = next;
+ currHeaderRead++;
if (bbIndex == 4)
Flush();
}
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarHeader.cs b/src/ICSharpCode.SharpZipLib/Tar/TarHeader.cs
index e29507427..36d6eca44 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarHeader.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarHeader.cs
@@ -1,5 +1,7 @@
using System;
+using System.Buffers;
using System.Text;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -124,106 +126,106 @@ public class TarHeader
///
/// Normal file type.
///
- public const byte LF_NORMAL = (byte)'0';
+ public const byte LF_NORMAL = (byte) '0';
///
/// Link file type.
///
- public const byte LF_LINK = (byte)'1';
+ public const byte LF_LINK = (byte) '1';
///
/// Symbolic link file type.
///
- public const byte LF_SYMLINK = (byte)'2';
+ public const byte LF_SYMLINK = (byte) '2';
///
/// Character device file type.
///
- public const byte LF_CHR = (byte)'3';
+ public const byte LF_CHR = (byte) '3';
///
/// Block device file type.
///
- public const byte LF_BLK = (byte)'4';
+ public const byte LF_BLK = (byte) '4';
///
/// Directory file type.
///
- public const byte LF_DIR = (byte)'5';
+ public const byte LF_DIR = (byte) '5';
///
/// FIFO (pipe) file type.
///
- public const byte LF_FIFO = (byte)'6';
+ public const byte LF_FIFO = (byte) '6';
///
/// Contiguous file type.
///
- public const byte LF_CONTIG = (byte)'7';
+ public const byte LF_CONTIG = (byte) '7';
///
/// Posix.1 2001 global extended header
///
- public const byte LF_GHDR = (byte)'g';
+ public const byte LF_GHDR = (byte) 'g';
///
/// Posix.1 2001 extended header
///
- public const byte LF_XHDR = (byte)'x';
+ public const byte LF_XHDR = (byte) 'x';
// POSIX allows for upper case ascii type as extensions
///
/// Solaris access control list file type
///
- public const byte LF_ACL = (byte)'A';
+ public const byte LF_ACL = (byte) 'A';
///
/// GNU dir dump file type
/// This is a dir entry that contains the names of files that were in the
/// dir at the time the dump was made
///
- public const byte LF_GNU_DUMPDIR = (byte)'D';
+ public const byte LF_GNU_DUMPDIR = (byte) 'D';
///
/// Solaris Extended Attribute File
///
- public const byte LF_EXTATTR = (byte)'E';
+ public const byte LF_EXTATTR = (byte) 'E';
///
/// Inode (metadata only) no file content
///
- public const byte LF_META = (byte)'I';
+ public const byte LF_META = (byte) 'I';
///
/// Identifies the next file on the tape as having a long link name
///
- public const byte LF_GNU_LONGLINK = (byte)'K';
+ public const byte LF_GNU_LONGLINK = (byte) 'K';
///
/// Identifies the next file on the tape as having a long name
///
- public const byte LF_GNU_LONGNAME = (byte)'L';
+ public const byte LF_GNU_LONGNAME = (byte) 'L';
///
/// Continuation of a file that began on another volume
///
- public const byte LF_GNU_MULTIVOL = (byte)'M';
+ public const byte LF_GNU_MULTIVOL = (byte) 'M';
///
/// For storing filenames that dont fit in the main header (old GNU)
///
- public const byte LF_GNU_NAMES = (byte)'N';
+ public const byte LF_GNU_NAMES = (byte) 'N';
///
/// GNU Sparse file
///
- public const byte LF_GNU_SPARSE = (byte)'S';
+ public const byte LF_GNU_SPARSE = (byte) 'S';
///
/// GNU Tape/volume header ignore on extraction
///
- public const byte LF_GNU_VOLHDR = (byte)'V';
+ public const byte LF_GNU_VOLHDR = (byte) 'V';
///
/// The magic tag representing a POSIX tar archive. (would be written with a trailing NULL)
@@ -235,7 +237,7 @@ public class TarHeader
///
public const string GNU_TMAGIC = "ustar ";
- private const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds
+ private const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds
private static readonly DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
#endregion Constants
@@ -277,6 +279,7 @@ public string Name
{
throw new ArgumentNullException(nameof(value));
}
+
name = value;
}
}
@@ -339,6 +342,7 @@ public long Size
{
throw new ArgumentOutOfRangeException(nameof(value), "Cannot be less than zero");
}
+
size = value;
}
}
@@ -359,6 +363,7 @@ public DateTime ModTime
{
throw new ArgumentOutOfRangeException(nameof(value), "ModTime cannot be before Jan 1st 1970");
}
+
modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second);
}
}
@@ -401,6 +406,7 @@ public string LinkName
{
throw new ArgumentNullException(nameof(value));
}
+
linkName = value;
}
}
@@ -418,6 +424,7 @@ public string Magic
{
throw new ArgumentNullException(nameof(value));
}
+
magic = value;
}
}
@@ -428,10 +435,7 @@ public string Magic
/// Thrown when attempting to set Version to null.
public string Version
{
- get
- {
- return version;
- }
+ get { return version; }
set
{
@@ -439,6 +443,7 @@ public string Version
{
throw new ArgumentNullException(nameof(value));
}
+
version = value;
}
}
@@ -462,6 +467,7 @@ public string UserName
{
currentUser = currentUser.Substring(0, UNAMELEN);
}
+
userName = currentUser;
}
}
@@ -528,7 +534,10 @@ public object Clone()
///
/// The tar entry header buffer to get information from.
///
- public void ParseBuffer(byte[] header)
+ ///
+ /// The used for the Name field, or null for ASCII only
+ ///
+ public void ParseBuffer(byte[] header, Encoding nameEncoding)
{
if (header == null)
{
@@ -536,17 +545,18 @@ public void ParseBuffer(byte[] header)
}
int offset = 0;
+ var headerSpan = header.AsSpan();
- name = ParseName(header, offset, NAMELEN).ToString();
+ name = ParseName(headerSpan.Slice(offset, NAMELEN), nameEncoding);
offset += NAMELEN;
- mode = (int)ParseOctal(header, offset, MODELEN);
+ mode = (int) ParseOctal(header, offset, MODELEN);
offset += MODELEN;
- UserId = (int)ParseOctal(header, offset, UIDLEN);
+ UserId = (int) ParseOctal(header, offset, UIDLEN);
offset += UIDLEN;
- GroupId = (int)ParseOctal(header, offset, GIDLEN);
+ GroupId = (int) ParseOctal(header, offset, GIDLEN);
offset += GIDLEN;
Size = ParseBinaryOrOctal(header, offset, SIZELEN);
@@ -555,46 +565,69 @@ public void ParseBuffer(byte[] header)
ModTime = GetDateTimeFromCTime(ParseOctal(header, offset, MODTIMELEN));
offset += MODTIMELEN;
- checksum = (int)ParseOctal(header, offset, CHKSUMLEN);
+ checksum = (int) ParseOctal(header, offset, CHKSUMLEN);
offset += CHKSUMLEN;
TypeFlag = header[offset++];
- LinkName = ParseName(header, offset, NAMELEN).ToString();
+ LinkName = ParseName(headerSpan.Slice(offset, NAMELEN), nameEncoding);
offset += NAMELEN;
- Magic = ParseName(header, offset, MAGICLEN).ToString();
+ Magic = ParseName(headerSpan.Slice(offset, MAGICLEN), nameEncoding);
offset += MAGICLEN;
if (Magic == "ustar")
{
- Version = ParseName(header, offset, VERSIONLEN).ToString();
+ Version = ParseName(headerSpan.Slice(offset, VERSIONLEN), nameEncoding);
offset += VERSIONLEN;
- UserName = ParseName(header, offset, UNAMELEN).ToString();
+ UserName = ParseName(headerSpan.Slice(offset, UNAMELEN), nameEncoding);
offset += UNAMELEN;
- GroupName = ParseName(header, offset, GNAMELEN).ToString();
+ GroupName = ParseName(headerSpan.Slice(offset, GNAMELEN), nameEncoding);
offset += GNAMELEN;
- DevMajor = (int)ParseOctal(header, offset, DEVLEN);
+ DevMajor = (int) ParseOctal(header, offset, DEVLEN);
offset += DEVLEN;
- DevMinor = (int)ParseOctal(header, offset, DEVLEN);
+ DevMinor = (int) ParseOctal(header, offset, DEVLEN);
offset += DEVLEN;
- string prefix = ParseName(header, offset, PREFIXLEN).ToString();
+ string prefix = ParseName(headerSpan.Slice(offset, PREFIXLEN), nameEncoding);
if (!string.IsNullOrEmpty(prefix)) Name = prefix + '/' + Name;
}
isChecksumValid = Checksum == TarHeader.MakeCheckSum(header);
}
+ ///
+ /// Parse TarHeader information from a header buffer.
+ ///
+ ///
+ /// The tar entry header buffer to get information from.
+ ///
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
+ public void ParseBuffer(byte[] header)
+ {
+ ParseBuffer(header, null);
+ }
+
///
/// 'Write' header information to buffer provided, updating the check sum.
///
/// output buffer for header information
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public void WriteHeader(byte[] outBuffer)
+ {
+ WriteHeader(outBuffer, null);
+ }
+
+ ///
+ /// 'Write' header information to buffer provided, updating the check sum.
+ ///
+ /// output buffer for header information
+ /// The used for the Name field, or null for ASCII only
+ public void WriteHeader(byte[] outBuffer, Encoding nameEncoding)
{
if (outBuffer == null)
{
@@ -603,7 +636,7 @@ public void WriteHeader(byte[] outBuffer)
int offset = 0;
- offset = GetNameBytes(Name, outBuffer, offset, NAMELEN);
+ offset = GetNameBytes(Name, outBuffer, offset, NAMELEN, nameEncoding);
offset = GetOctalBytes(mode, outBuffer, offset, MODELEN);
offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN);
offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN);
@@ -614,16 +647,16 @@ public void WriteHeader(byte[] outBuffer)
int csOffset = offset;
for (int c = 0; c < CHKSUMLEN; ++c)
{
- outBuffer[offset++] = (byte)' ';
+ outBuffer[offset++] = (byte) ' ';
}
outBuffer[offset++] = TypeFlag;
- offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN);
- offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN);
- offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN);
- offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN);
- offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN);
+ offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN, nameEncoding);
+ offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN, nameEncoding);
+ offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN, nameEncoding);
+ offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN, nameEncoding);
+ offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN, nameEncoding);
if ((TypeFlag == LF_CHR) || (TypeFlag == LF_BLK))
{
@@ -664,25 +697,26 @@ public override bool Equals(object obj)
if (localHeader != null)
{
result = (name == localHeader.name)
- && (mode == localHeader.mode)
- && (UserId == localHeader.UserId)
- && (GroupId == localHeader.GroupId)
- && (Size == localHeader.Size)
- && (ModTime == localHeader.ModTime)
- && (Checksum == localHeader.Checksum)
- && (TypeFlag == localHeader.TypeFlag)
- && (LinkName == localHeader.LinkName)
- && (Magic == localHeader.Magic)
- && (Version == localHeader.Version)
- && (UserName == localHeader.UserName)
- && (GroupName == localHeader.GroupName)
- && (DevMajor == localHeader.DevMajor)
- && (DevMinor == localHeader.DevMinor);
+ && (mode == localHeader.mode)
+ && (UserId == localHeader.UserId)
+ && (GroupId == localHeader.GroupId)
+ && (Size == localHeader.Size)
+ && (ModTime == localHeader.ModTime)
+ && (Checksum == localHeader.Checksum)
+ && (TypeFlag == localHeader.TypeFlag)
+ && (LinkName == localHeader.LinkName)
+ && (Magic == localHeader.Magic)
+ && (Version == localHeader.Version)
+ && (UserName == localHeader.UserName)
+ && (GroupName == localHeader.GroupName)
+ && (DevMajor == localHeader.DevMajor)
+ && (DevMinor == localHeader.DevMinor);
}
else
{
result = false;
}
+
return result;
}
@@ -693,7 +727,7 @@ public override bool Equals(object obj)
/// Value to apply as a default for userName.
/// Value to apply as a default for groupId.
/// Value to apply as a default for groupName.
- static internal void SetValueDefaults(int userId, string userName, int groupId, string groupName)
+ internal static void SetValueDefaults(int userId, string userName, int groupId, string groupName)
{
defaultUserId = userIdAsSet = userId;
defaultUser = userNameAsSet = userName;
@@ -701,7 +735,7 @@ static internal void SetValueDefaults(int userId, string userName, int groupId,
defaultGroupName = groupNameAsSet = groupName;
}
- static internal void RestoreSetValues()
+ internal static void RestoreSetValues()
{
defaultUserId = userIdAsSet;
defaultUser = userNameAsSet;
@@ -711,7 +745,7 @@ static internal void RestoreSetValues()
// Return value that may be stored in octal or binary. Length must exceed 8.
//
- static private long ParseBinaryOrOctal(byte[] header, int offset, int length)
+ private static long ParseBinaryOrOctal(byte[] header, int offset, int length)
{
if (header[offset] >= 0x80)
{
@@ -721,8 +755,10 @@ static private long ParseBinaryOrOctal(byte[] header, int offset, int length)
{
result = result << 8 | header[offset + pos];
}
+
return result;
}
+
return ParseOctal(header, offset, length);
}
@@ -733,7 +769,7 @@ static private long ParseBinaryOrOctal(byte[] header, int offset, int length)
/// The offset into the buffer from which to parse.
/// The number of header bytes to parse.
/// The long equivalent of the octal string.
- static public long ParseOctal(byte[] header, int offset, int length)
+ public static long ParseOctal(byte[] header, int offset, int length)
{
if (header == null)
{
@@ -751,14 +787,14 @@ static public long ParseOctal(byte[] header, int offset, int length)
break;
}
- if (header[i] == (byte)' ' || header[i] == '0')
+ if (header[i] == (byte) ' ' || header[i] == '0')
{
if (stillPadding)
{
continue;
}
- if (header[i] == (byte)' ')
+ if (header[i] == (byte) ' ')
{
break;
}
@@ -787,39 +823,62 @@ static public long ParseOctal(byte[] header, int offset, int length)
///
/// The name parsed.
///
- static public StringBuilder ParseName(byte[] header, int offset, int length)
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
+ public static string ParseName(byte[] header, int offset, int length)
{
- if (header == null)
- {
- throw new ArgumentNullException(nameof(header));
- }
+ return ParseName(header.AsSpan().Slice(offset, length), null);
+ }
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be less than zero");
- }
+ ///
+ /// Parse a name from a header buffer.
+ ///
+ ///
+ /// The header buffer from which to parse.
+ ///
+ ///
+ /// name encoding, or null for ASCII only
+ ///
+ ///
+ /// The name parsed.
+ ///
+ public static string ParseName(ReadOnlySpan header, Encoding encoding)
+ {
+ var builder = StringBuilderPool.Instance.Rent();
- if (length < 0)
+ int count = 0;
+ if (encoding == null)
{
- throw new ArgumentOutOfRangeException(nameof(length), "Cannot be less than zero");
- }
+ for (int i = 0; i < header.Length; ++i)
+ {
+ var b = header[i];
+ if (b == 0)
+ {
+ break;
+ }
- if (offset + length > header.Length)
- {
- throw new ArgumentException("Exceeds header size", nameof(length));
+ builder.Append((char) b);
+ }
}
-
- var result = new StringBuilder(length);
-
- for (int i = offset; i < offset + length; ++i)
+ else
{
- if (header[i] == 0)
+ for (int i = 0; i < header.Length; ++i, ++count)
{
- break;
+ if (header[i] == 0)
+ {
+ break;
+ }
}
- result.Append((char)header[i]);
+
+#if NETSTANDARD2_1_OR_GREATER
+ var value = encoding.GetString(header.Slice(0, count));
+#else
+ var value = encoding.GetString(header.ToArray(), 0, count);
+#endif
+ builder.Append(value);
}
+ var result = builder.ToString();
+ StringBuilderPool.Instance.Return(builder);
return result;
}
@@ -834,17 +893,7 @@ static public StringBuilder ParseName(byte[] header, int offset, int length)
/// The next free index in the
public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
-
- return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length);
+ return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length, null);
}
///
@@ -857,6 +906,22 @@ public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer
/// The number of characters/bytes to add
/// The next free index in the
public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length)
+ {
+ return GetNameBytes(name, nameOffset, buffer, bufferOffset, length, null);
+ }
+
+ ///
+ /// Add name to the buffer as a collection of bytes
+ ///
+ /// The name to add
+ /// The offset of the first character
+ /// The buffer to add to
+ /// The index of the first byte to add
+ /// The number of characters/bytes to add
+ /// name encoding, or null for ASCII only
+ /// The next free index in the
+ public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length,
+ Encoding encoding)
{
if (name == null)
{
@@ -869,10 +934,25 @@ public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int b
}
int i;
-
- for (i = 0; i < length && nameOffset + i < name.Length; ++i)
+ if (encoding != null)
{
- buffer[bufferOffset + i] = (byte)name[nameOffset + i];
+ // it can be more sufficient if using Span or unsafe
+ ReadOnlySpan nameArray =
+ name.AsSpan().Slice(nameOffset, Math.Min(name.Length - nameOffset, length));
+ var charArray = ArrayPool.Shared.Rent(nameArray.Length);
+ nameArray.CopyTo(charArray);
+
+ // it can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer
+ var bytesLength = encoding.GetBytes(charArray, 0, nameArray.Length, buffer, bufferOffset);
+ ArrayPool.Shared.Return(charArray);
+ i = Math.Min(bytesLength, length);
+ }
+ else
+ {
+ for (i = 0; i < length && nameOffset + i < name.Length; ++i)
+ {
+ buffer[bufferOffset + i] = (byte) name[nameOffset + i];
+ }
}
for (; i < length; ++i)
@@ -901,7 +981,34 @@ public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int b
///
/// The index of the next free byte in the buffer
///
+ /// TODO: what should be default behavior?(omit upper byte or UTF8?)
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length)
+ {
+ return GetNameBytes(name, buffer, offset, length, null);
+ }
+
+ ///
+ /// Add an entry name to the buffer
+ ///
+ ///
+ /// The name to add
+ ///
+ ///
+ /// The buffer to add to
+ ///
+ ///
+ /// The offset into the buffer from which to start adding
+ ///
+ ///
+ /// The number of header bytes to add
+ ///
+ ///
+ ///
+ ///
+ /// The index of the next free byte in the buffer
+ ///
+ public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length, Encoding encoding)
{
if (name == null)
{
@@ -913,7 +1020,7 @@ public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, in
throw new ArgumentNullException(nameof(buffer));
}
- return GetNameBytes(name.ToString(), 0, buffer, offset, length);
+ return GetNameBytes(name.ToString(), 0, buffer, offset, length, encoding);
}
///
@@ -924,7 +1031,23 @@ public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, in
/// The offset into the buffer from which to start adding
/// The number of header bytes to add
/// The index of the next free byte in the buffer
+ /// TODO: what should be default behavior?(omit upper byte or UTF8?)
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public static int GetNameBytes(string name, byte[] buffer, int offset, int length)
+ {
+ return GetNameBytes(name, buffer, offset, length, null);
+ }
+
+ ///
+ /// Add an entry name to the buffer
+ ///
+ /// The name to add
+ /// The buffer to add to
+ /// The offset into the buffer from which to start adding
+ /// The number of header bytes to add
+ ///
+ /// The index of the next free byte in the buffer
+ public static int GetNameBytes(string name, byte[] buffer, int offset, int length, Encoding encoding)
{
if (name == null)
{
@@ -936,7 +1059,7 @@ public static int GetNameBytes(string name, byte[] buffer, int offset, int lengt
throw new ArgumentNullException(nameof(buffer));
}
- return GetNameBytes(name, 0, buffer, offset, length);
+ return GetNameBytes(name, 0, buffer, offset, length, encoding);
}
///
@@ -948,7 +1071,24 @@ public static int GetNameBytes(string name, byte[] buffer, int offset, int lengt
/// The offset to start adding at.
/// The number of ascii characters to add.
/// The next free index in the buffer.
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length)
+ {
+ return GetAsciiBytes(toAdd, nameOffset, buffer, bufferOffset, length, null);
+ }
+
+ ///
+ /// Add a string to a buffer as a collection of ascii bytes.
+ ///
+ /// The string to add
+ /// The offset of the first character to add.
+ /// The buffer to add to.
+ /// The offset to start adding at.
+ /// The number of ascii characters to add.
+ /// String encoding, or null for ASCII only
+ /// The next free index in the buffer.
+ public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length,
+ Encoding encoding)
{
if (toAdd == null)
{
@@ -961,10 +1101,23 @@ public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int
}
int i;
- for (i = 0; i < length && nameOffset + i < toAdd.Length; ++i)
+ if (encoding == null)
+ {
+ for (i = 0; i < length && nameOffset + i < toAdd.Length; ++i)
+ {
+ buffer[bufferOffset + i] = (byte) toAdd[nameOffset + i];
+ }
+ }
+ else
{
- buffer[bufferOffset + i] = (byte)toAdd[nameOffset + i];
+ // It can be more sufficient if using unsafe code or Span(ToCharArray can be omitted)
+ var chars = toAdd.ToCharArray();
+ // It can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer
+ var bytes = encoding.GetBytes(chars, nameOffset, Math.Min(toAdd.Length - nameOffset, length));
+ i = Math.Min(bytes.Length, length);
+ Array.Copy(bytes, 0, buffer, bufferOffset, i);
}
+
// If length is beyond the toAdd string length (which is OK by the prev loop condition), eg if a field has fixed length and the string is shorter, make sure all of the extra chars are written as NULLs, so that the reader func would ignore them and get back the original string
for (; i < length; ++i)
buffer[bufferOffset + i] = 0;
@@ -1006,14 +1159,14 @@ public static int GetOctalBytes(long value, byte[] buffer, int offset, int lengt
{
for (long v = value; (localIndex >= 0) && (v > 0); --localIndex)
{
- buffer[offset + localIndex] = (byte)((byte)'0' + (byte)(v & 7));
+ buffer[offset + localIndex] = (byte) ((byte) '0' + (byte) (v & 7));
v >>= 3;
}
}
for (; localIndex >= 0; --localIndex)
{
- buffer[offset + localIndex] = (byte)'0';
+ buffer[offset + localIndex] = (byte) '0';
}
return offset + length;
@@ -1030,16 +1183,19 @@ public static int GetOctalBytes(long value, byte[] buffer, int offset, int lengt
private static int GetBinaryOrOctalBytes(long value, byte[] buffer, int offset, int length)
{
if (value > 0x1FFFFFFFF)
- { // Octal 77777777777 (11 digits)
- // Put value as binary, right-justified into the buffer. Set high order bit of left-most byte.
+ {
+ // Octal 77777777777 (11 digits)
+ // Put value as binary, right-justified into the buffer. Set high order bit of left-most byte.
for (int pos = length - 1; pos > 0; pos--)
{
- buffer[offset + pos] = (byte)value;
+ buffer[offset + pos] = (byte) value;
value = value >> 8;
}
+
buffer[offset] = 0x80;
return offset + length;
}
+
return GetOctalBytes(value, buffer, offset, length);
}
@@ -1073,6 +1229,7 @@ private static int ComputeCheckSum(byte[] buffer)
{
sum += buffer[i];
}
+
return sum;
}
@@ -1091,19 +1248,20 @@ private static int MakeCheckSum(byte[] buffer)
for (int i = 0; i < CHKSUMLEN; ++i)
{
- sum += (byte)' ';
+ sum += (byte) ' ';
}
for (int i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i)
{
sum += buffer[i];
}
+
return sum;
}
private static int GetCTime(DateTime dateTime)
{
- return unchecked((int)((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor));
+ return unchecked((int) ((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor));
}
private static DateTime GetDateTimeFromCTime(long ticks)
@@ -1118,6 +1276,7 @@ private static DateTime GetDateTimeFromCTime(long ticks)
{
result = dateTime1970;
}
+
return result;
}
@@ -1145,16 +1304,16 @@ private static DateTime GetDateTimeFromCTime(long ticks)
#region Class Fields
// Values used during recursive operations.
- static internal int userIdAsSet;
+ internal static int userIdAsSet;
- static internal int groupIdAsSet;
- static internal string userNameAsSet;
- static internal string groupNameAsSet = "None";
+ internal static int groupIdAsSet;
+ internal static string userNameAsSet;
+ internal static string groupNameAsSet = "None";
- static internal int defaultUserId;
- static internal int defaultGroupId;
- static internal string defaultGroupName = "None";
- static internal string defaultUser;
+ internal static int defaultUserId;
+ internal static int defaultGroupId;
+ internal static string defaultGroupName = "None";
+ internal static string defaultUser;
#endregion Class Fields
}
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs b/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs
index 3c0cd96cd..36e294628 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs
@@ -1,6 +1,10 @@
using System;
+using System.Buffers;
using System.IO;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -18,8 +22,19 @@ public class TarInputStream : Stream
/// Construct a TarInputStream with default block factor
///
/// stream to source data from
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public TarInputStream(Stream inputStream)
- : this(inputStream, TarBuffer.DefaultBlockFactor)
+ : this(inputStream, TarBuffer.DefaultBlockFactor, null)
+ {
+ }
+
+ ///
+ /// Construct a TarInputStream with default block factor
+ ///
+ /// stream to source data from
+ /// The used for the Name fields, or null for ASCII only
+ public TarInputStream(Stream inputStream, Encoding nameEncoding)
+ : this(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding)
{
}
@@ -28,10 +43,25 @@ public TarInputStream(Stream inputStream)
///
/// stream to source data from
/// block factor to apply to archive
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public TarInputStream(Stream inputStream, int blockFactor)
{
this.inputStream = inputStream;
tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
+ encoding = null;
+ }
+
+ ///
+ /// Construct a TarInputStream with user specified block factor
+ ///
+ /// stream to source data from
+ /// block factor to apply to archive
+ /// The used for the Name fields, or null for ASCII only
+ public TarInputStream(Stream inputStream, int blockFactor, Encoding nameEncoding)
+ {
+ this.inputStream = inputStream;
+ tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
+ encoding = nameEncoding;
}
#endregion Constructors
@@ -54,10 +84,7 @@ public bool IsStreamOwner
///
public override bool CanRead
{
- get
- {
- return inputStream.CanRead;
- }
+ get { return inputStream.CanRead; }
}
///
@@ -66,10 +93,7 @@ public override bool CanRead
///
public override bool CanSeek
{
- get
- {
- return false;
- }
+ get { return false; }
}
///
@@ -78,10 +102,7 @@ public override bool CanSeek
///
public override bool CanWrite
{
- get
- {
- return false;
- }
+ get { return false; }
}
///
@@ -89,10 +110,7 @@ public override bool CanWrite
///
public override long Length
{
- get
- {
- return inputStream.Length;
- }
+ get { return inputStream.Length; }
}
///
@@ -102,14 +120,8 @@ public override long Length
/// Any attempt to set position
public override long Position
{
- get
- {
- return inputStream.Position;
- }
- set
- {
- throw new NotSupportedException("TarInputStream Seek not supported");
- }
+ get { return inputStream.Position; }
+ set { throw new NotSupportedException("TarInputStream Seek not supported"); }
}
///
@@ -120,6 +132,15 @@ public override void Flush()
inputStream.Flush();
}
+ ///
+ /// Flushes the baseInputStream
+ ///
+ ///
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await inputStream.FlushAsync(cancellationToken);
+ }
+
///
/// Set the streams position. This operation is not supported and will throw a NotSupportedException
///
@@ -173,15 +194,64 @@ public override void WriteByte(byte value)
/// A byte cast to an int; -1 if the at the end of the stream.
public override int ReadByte()
{
- byte[] oneByteBuffer = new byte[1];
- int num = Read(oneByteBuffer, 0, 1);
+ var oneByteBuffer = ArrayPool.Shared.Rent(1);
+ var num = Read(oneByteBuffer, 0, 1);
if (num <= 0)
{
// return -1 to indicate that no byte was read.
return -1;
}
- return oneByteBuffer[0];
+
+ var result = oneByteBuffer[0];
+ ArrayPool.Shared.Return(oneByteBuffer);
+ return result;
+ }
+
+
+ ///
+ /// Reads bytes from the current tar archive entry.
+ ///
+ /// This method is aware of the boundaries of the current
+ /// entry in the archive and will deal with them appropriately
+ ///
+ ///
+ /// The buffer into which to place bytes read.
+ ///
+ ///
+ /// The offset at which to place bytes read.
+ ///
+ ///
+ /// The number of bytes to read.
+ ///
+ ///
+ ///
+ /// The number of bytes read, or 0 at end of stream/EOF.
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return ReadAsync(buffer.AsMemory().Slice(offset, count), cancellationToken, true).AsTask();
+ }
+
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ /// Reads bytes from the current tar archive entry.
+ ///
+ /// This method is aware of the boundaries of the current
+ /// entry in the archive and will deal with them appropriately
+ ///
+ ///
+ /// The buffer into which to place bytes read.
+ ///
+ ///
+ ///
+ /// The number of bytes read, or 0 at end of stream/EOF.
+ ///
+ public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken =
+ new CancellationToken())
+ {
+ return ReadAsync(buffer, cancellationToken, true);
}
+#endif
///
/// Reads bytes from the current tar archive entry.
@@ -208,6 +278,13 @@ public override int Read(byte[] buffer, int offset, int count)
throw new ArgumentNullException(nameof(buffer));
}
+ return ReadAsync(buffer.AsMemory().Slice(offset, count), CancellationToken.None, false).GetAwaiter()
+ .GetResult();
+ }
+
+ private async ValueTask ReadAsync(Memory buffer, CancellationToken ct, bool isAsync)
+ {
+ int offset = 0;
int totalRead = 0;
if (entryOffset >= entrySize)
@@ -215,7 +292,7 @@ public override int Read(byte[] buffer, int offset, int count)
return 0;
}
- long numToRead = count;
+ long numToRead = buffer.Length;
if ((numToRead + entryOffset) > entrySize)
{
@@ -224,19 +301,22 @@ public override int Read(byte[] buffer, int offset, int count)
if (readBuffer != null)
{
- int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead;
+ int sz = (numToRead > readBuffer.Memory.Length) ? readBuffer.Memory.Length : (int)numToRead;
- Array.Copy(readBuffer, 0, buffer, offset, sz);
+ readBuffer.Memory.Slice(0, sz).CopyTo(buffer.Slice(offset, sz));
- if (sz >= readBuffer.Length)
+ if (sz >= readBuffer.Memory.Length)
{
+ readBuffer.Dispose();
readBuffer = null;
}
else
{
- int newLen = readBuffer.Length - sz;
- byte[] newBuf = new byte[newLen];
- Array.Copy(readBuffer, sz, newBuf, 0, newLen);
+ int newLen = readBuffer.Memory.Length - sz;
+ var newBuf = ExactMemoryPool.Shared.Rent(newLen);
+ readBuffer.Memory.Slice(sz, newLen).CopyTo(newBuf.Memory);
+ readBuffer.Dispose();
+
readBuffer = newBuf;
}
@@ -245,28 +325,27 @@ public override int Read(byte[] buffer, int offset, int count)
offset += sz;
}
+ var recLen = TarBuffer.BlockSize;
+ var recBuf = ArrayPool.Shared.Rent(recLen);
+
while (numToRead > 0)
{
- byte[] rec = tarBuffer.ReadBlock();
- if (rec == null)
- {
- // Unexpected EOF!
- throw new TarException("unexpected EOF with " + numToRead + " bytes unread");
- }
+ await tarBuffer.ReadBlockIntAsync(recBuf, ct, isAsync);
var sz = (int)numToRead;
- int recLen = rec.Length;
if (recLen > sz)
{
- Array.Copy(rec, 0, buffer, offset, sz);
- readBuffer = new byte[recLen - sz];
- Array.Copy(rec, sz, readBuffer, 0, recLen - sz);
+ recBuf.AsSpan().Slice(0, sz).CopyTo(buffer.Slice(offset, sz).Span);
+ readBuffer?.Dispose();
+
+ readBuffer = ExactMemoryPool.Shared.Rent(recLen - sz);
+ recBuf.AsSpan().Slice(sz, recLen - sz).CopyTo(readBuffer.Memory.Span);
}
else
{
sz = recLen;
- Array.Copy(rec, 0, buffer, offset, recLen);
+ recBuf.AsSpan().CopyTo(buffer.Slice(offset, recLen).Span);
}
totalRead += sz;
@@ -274,6 +353,8 @@ public override int Read(byte[] buffer, int offset, int count)
offset += sz;
}
+ ArrayPool.Shared.Return(recBuf);
+
entryOffset += totalRead;
return totalRead;
@@ -291,6 +372,17 @@ protected override void Dispose(bool disposing)
}
}
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ /// Closes this stream. Calls the TarBuffer's close() method.
+ /// The underlying stream is closed by the TarBuffer.
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ await tarBuffer.CloseAsync(CancellationToken.None);
+ }
+#endif
+
#endregion Stream Overrides
///
@@ -334,10 +426,7 @@ public int GetRecordSize()
///
public long Available
{
- get
- {
- return entrySize - entryOffset;
- }
+ get { return entrySize - entryOffset; }
}
///
@@ -349,25 +438,42 @@ public long Available
///
/// The number of bytes to skip.
///
- public void Skip(long skipCount)
+ ///
+ private Task SkipAsync(long skipCount, CancellationToken ct) => SkipAsync(skipCount, ct, true).AsTask();
+
+ ///
+ /// Skip bytes in the input buffer. This skips bytes in the
+ /// current entry's data, not the entire archive, and will
+ /// stop at the end of the current entry's data if the number
+ /// to skip extends beyond that point.
+ ///
+ ///
+ /// The number of bytes to skip.
+ ///
+ private void Skip(long skipCount) =>
+ SkipAsync(skipCount, CancellationToken.None, false).GetAwaiter().GetResult();
+
+ private async ValueTask SkipAsync(long skipCount, CancellationToken ct, bool isAsync)
{
// TODO: REVIEW efficiency of TarInputStream.Skip
// This is horribly inefficient, but it ensures that we
// properly skip over bytes via the TarBuffer...
//
- byte[] skipBuf = new byte[8 * 1024];
-
- for (long num = skipCount; num > 0;)
+ var length = 8 * 1024;
+ using (var skipBuf = ExactMemoryPool.Shared.Rent(length))
{
- int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num;
- int numRead = Read(skipBuf, 0, toRead);
-
- if (numRead == -1)
+ for (long num = skipCount; num > 0;)
{
- break;
- }
+ int toRead = num > length ? length : (int)num;
+ int numRead = await ReadAsync(skipBuf.Memory.Slice(0, toRead), ct, isAsync);
- num -= numRead;
+ if (numRead == -1)
+ {
+ break;
+ }
+
+ num -= numRead;
+ }
}
}
@@ -377,10 +483,7 @@ public void Skip(long skipCount)
/// Currently marking is not supported, the return value is always false.
public bool IsMarkSupported
{
- get
- {
- return false;
- }
+ get { return false; }
}
///
@@ -413,7 +516,24 @@ public void Reset()
///
/// The next TarEntry in the archive, or null.
///
- public TarEntry GetNextEntry()
+ public Task GetNextEntryAsync(CancellationToken ct) => GetNextEntryAsync(ct, true).AsTask();
+
+ ///
+ /// Get the next entry in this tar archive. This will skip
+ /// over any remaining data in the current entry, if there
+ /// is one, and place the input stream at the header of the
+ /// next entry, and read the header and instantiate a new
+ /// TarEntry from the header bytes and return that entry.
+ /// If there are no more entries in the archive, null will
+ /// be returned to indicate that the end of the archive has
+ /// been reached.
+ ///
+ ///
+ /// The next TarEntry in the archive, or null.
+ ///
+ public TarEntry GetNextEntry() => GetNextEntryAsync(CancellationToken.None, true).GetAwaiter().GetResult();
+
+ private async ValueTask GetNextEntryAsync(CancellationToken ct, bool isAsync)
{
if (hasHitEOF)
{
@@ -422,21 +542,18 @@ public TarEntry GetNextEntry()
if (currentEntry != null)
{
- SkipToNextEntry();
+ await SkipToNextEntryAsync(ct, isAsync);
}
- byte[] headerBuf = tarBuffer.ReadBlock();
+ byte[] headerBuf = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
+ await tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
- if (headerBuf == null)
- {
- hasHitEOF = true;
- }
- else if (TarBuffer.IsEndOfArchiveBlock(headerBuf))
+ if (TarBuffer.IsEndOfArchiveBlock(headerBuf))
{
hasHitEOF = true;
// Read the second zero-filled block
- tarBuffer.ReadBlock();
+ await tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
}
else
{
@@ -446,61 +563,73 @@ public TarEntry GetNextEntry()
if (hasHitEOF)
{
currentEntry = null;
+ readBuffer?.Dispose();
}
else
{
try
{
var header = new TarHeader();
- header.ParseBuffer(headerBuf);
+ header.ParseBuffer(headerBuf, encoding);
if (!header.IsChecksumValid)
{
throw new TarException("Header checksum is invalid");
}
+
this.entryOffset = 0;
this.entrySize = header.Size;
- StringBuilder longName = null;
+ string longName = null;
if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME)
{
- byte[] nameBuffer = new byte[TarBuffer.BlockSize];
- long numToRead = this.entrySize;
-
- longName = new StringBuilder();
-
- while (numToRead > 0)
+ using (var nameBuffer = ExactMemoryPool.Shared.Rent(TarBuffer.BlockSize))
{
- int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
+ long numToRead = this.entrySize;
- if (numRead == -1)
+ var longNameBuilder = StringBuilderPool.Instance.Rent();
+
+ while (numToRead > 0)
{
- throw new InvalidHeaderException("Failed to read long name entry");
+ var length = (numToRead > TarBuffer.BlockSize ? TarBuffer.BlockSize : (int)numToRead);
+ int numRead = await ReadAsync(nameBuffer.Memory.Slice(0, length), ct, isAsync);
+
+ if (numRead == -1)
+ {
+ throw new InvalidHeaderException("Failed to read long name entry");
+ }
+
+ longNameBuilder.Append(TarHeader.ParseName(nameBuffer.Memory.Slice(0, numRead).Span,
+ encoding));
+ numToRead -= numRead;
}
- longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
- numToRead -= numRead;
- }
+ longName = longNameBuilder.ToString();
+ StringBuilderPool.Instance.Return(longNameBuilder);
- SkipToNextEntry();
- headerBuf = this.tarBuffer.ReadBlock();
+ await SkipToNextEntryAsync(ct, isAsync);
+ await this.tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
+ }
}
else if (header.TypeFlag == TarHeader.LF_GHDR)
- { // POSIX global extended header
- // Ignore things we dont understand completely for now
- SkipToNextEntry();
- headerBuf = this.tarBuffer.ReadBlock();
+ {
+ // POSIX global extended header
+ // Ignore things we dont understand completely for now
+ await SkipToNextEntryAsync(ct, isAsync);
+ await this.tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
}
else if (header.TypeFlag == TarHeader.LF_XHDR)
- { // POSIX extended header
- byte[] nameBuffer = new byte[TarBuffer.BlockSize];
+ {
+ // POSIX extended header
+ byte[] nameBuffer = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
long numToRead = this.entrySize;
var xhr = new TarExtendedHeaderReader();
while (numToRead > 0)
{
- int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
+ var length = (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead);
+ int numRead = await ReadAsync(nameBuffer.AsMemory().Slice(0, length), ct, isAsync);
if (numRead == -1)
{
@@ -511,42 +640,47 @@ public TarEntry GetNextEntry()
numToRead -= numRead;
}
+ ArrayPool.Shared.Return(nameBuffer);
+
if (xhr.Headers.TryGetValue("path", out string name))
{
- longName = new StringBuilder(name);
+ longName = name;
}
- SkipToNextEntry();
- headerBuf = this.tarBuffer.ReadBlock();
+ await SkipToNextEntryAsync(ct, isAsync);
+ await this.tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
}
else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR)
{
// TODO: could show volume name when verbose
- SkipToNextEntry();
- headerBuf = this.tarBuffer.ReadBlock();
+ await SkipToNextEntryAsync(ct, isAsync);
+ await this.tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
}
else if (header.TypeFlag != TarHeader.LF_NORMAL &&
- header.TypeFlag != TarHeader.LF_OLDNORM &&
- header.TypeFlag != TarHeader.LF_LINK &&
- header.TypeFlag != TarHeader.LF_SYMLINK &&
- header.TypeFlag != TarHeader.LF_DIR)
+ header.TypeFlag != TarHeader.LF_OLDNORM &&
+ header.TypeFlag != TarHeader.LF_LINK &&
+ header.TypeFlag != TarHeader.LF_SYMLINK &&
+ header.TypeFlag != TarHeader.LF_DIR)
{
// Ignore things we dont understand completely for now
- SkipToNextEntry();
- headerBuf = tarBuffer.ReadBlock();
+ await SkipToNextEntryAsync(ct, isAsync);
+ await tarBuffer.ReadBlockIntAsync(headerBuf, ct, isAsync);
}
if (entryFactory == null)
{
- currentEntry = new TarEntry(headerBuf);
+ currentEntry = new TarEntry(headerBuf, encoding);
+ readBuffer?.Dispose();
+
if (longName != null)
{
- currentEntry.Name = longName.ToString();
+ currentEntry.Name = longName;
}
}
else
{
currentEntry = entryFactory.CreateEntry(headerBuf);
+ readBuffer?.Dispose();
}
// Magic was checked here for 'ustar' but there are multiple valid possibilities
@@ -562,11 +696,16 @@ public TarEntry GetNextEntry()
entrySize = 0;
entryOffset = 0;
currentEntry = null;
+ readBuffer?.Dispose();
+
string errorText = string.Format("Bad header in record {0} block {1} {2}",
tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message);
throw new InvalidHeaderException(errorText);
}
}
+
+ ArrayPool.Shared.Return(headerBuf);
+
return currentEntry;
}
@@ -577,30 +716,55 @@ public TarEntry GetNextEntry()
///
/// The OutputStream into which to write the entry's data.
///
- public void CopyEntryContents(Stream outputStream)
+ ///
+ public Task CopyEntryContentsAsync(Stream outputStream, CancellationToken ct) =>
+ CopyEntryContentsAsync(outputStream, ct, true).AsTask();
+
+ ///
+ /// Copies the contents of the current tar archive entry directly into
+ /// an output stream.
+ ///
+ ///
+ /// The OutputStream into which to write the entry's data.
+ ///
+ public void CopyEntryContents(Stream outputStream) =>
+ CopyEntryContentsAsync(outputStream, CancellationToken.None, false).GetAwaiter().GetResult();
+
+ private async ValueTask CopyEntryContentsAsync(Stream outputStream, CancellationToken ct, bool isAsync)
{
- byte[] tempBuffer = new byte[32 * 1024];
+ byte[] tempBuffer = ArrayPool.Shared.Rent(32 * 1024);
while (true)
{
- int numRead = Read(tempBuffer, 0, tempBuffer.Length);
+ int numRead = await ReadAsync(tempBuffer, ct, isAsync);
if (numRead <= 0)
{
break;
}
- outputStream.Write(tempBuffer, 0, numRead);
+
+ if (isAsync)
+ {
+ await outputStream.WriteAsync(tempBuffer, 0, numRead, ct);
+ }
+ else
+ {
+ outputStream.Write(tempBuffer, 0, numRead);
+ }
}
+
+ ArrayPool.Shared.Return(tempBuffer);
}
- private void SkipToNextEntry()
+ private async ValueTask SkipToNextEntryAsync(CancellationToken ct, bool isAsync)
{
long numToSkip = entrySize - entryOffset;
if (numToSkip > 0)
{
- Skip(numToSkip);
+ await SkipAsync(numToSkip, ct, isAsync);
}
+ readBuffer?.Dispose();
readBuffer = null;
}
@@ -611,6 +775,8 @@ private void SkipToNextEntry()
///
public interface IEntryFactory
{
+ // This interface does not considering name encoding.
+ // How this interface should be?
///
/// Create an entry based on name alone
///
@@ -635,7 +801,7 @@ public interface IEntryFactory
/// Create a tar entry based on the header information passed
///
///
- /// Buffer containing header information to create an an entry from.
+ /// Buffer containing header information to create an entry from.
///
///
/// Created TarEntry or descendant class
@@ -648,6 +814,25 @@ public interface IEntryFactory
///
public class EntryFactoryAdapter : IEntryFactory
{
+ Encoding nameEncoding;
+
+ ///
+ /// Construct standard entry factory class with ASCII name encoding
+ ///
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
+ public EntryFactoryAdapter()
+ {
+ }
+
+ ///
+ /// Construct standard entry factory with name encoding
+ ///
+ /// The used for the Name fields, or null for ASCII only
+ public EntryFactoryAdapter(Encoding nameEncoding)
+ {
+ this.nameEncoding = nameEncoding;
+ }
+
///
/// Create a based on named
///
@@ -675,7 +860,7 @@ public TarEntry CreateEntryFromFile(string fileName)
/// A new
public TarEntry CreateEntry(byte[] headerBuffer)
{
- return new TarEntry(headerBuffer);
+ return new TarEntry(headerBuffer, nameEncoding);
}
}
@@ -699,7 +884,7 @@ public TarEntry CreateEntry(byte[] headerBuffer)
///
/// Buffer used with calls to Read()
///
- protected byte[] readBuffer;
+ protected IMemoryOwner readBuffer;
///
/// Working buffer
@@ -721,6 +906,8 @@ public TarEntry CreateEntry(byte[] headerBuffer)
///
private readonly Stream inputStream;
+ private readonly Encoding encoding;
+
#endregion Instance Fields
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs b/src/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs
index 09202caa7..9ce13f15d 100644
--- a/src/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs
@@ -1,5 +1,9 @@
using System;
+using System.Buffers;
using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tar
{
@@ -17,16 +21,28 @@ public class TarOutputStream : Stream
/// Construct TarOutputStream using default block factor
///
/// stream to write to
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public TarOutputStream(Stream outputStream)
: this(outputStream, TarBuffer.DefaultBlockFactor)
{
}
+ ///
+ /// Construct TarOutputStream using default block factor
+ ///
+ /// stream to write to
+ /// The used for the Name fields, or null for ASCII only
+ public TarOutputStream(Stream outputStream, Encoding nameEncoding)
+ : this(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding)
+ {
+ }
+
///
/// Construct TarOutputStream with user specified block factor
///
/// stream to write to
/// blocking factor
+ [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
public TarOutputStream(Stream outputStream, int blockFactor)
{
if (outputStream == null)
@@ -37,8 +53,30 @@ public TarOutputStream(Stream outputStream, int blockFactor)
this.outputStream = outputStream;
buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
- assemblyBuffer = new byte[TarBuffer.BlockSize];
- blockBuffer = new byte[TarBuffer.BlockSize];
+ assemblyBuffer = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
+ blockBuffer = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
+ }
+
+ ///
+ /// Construct TarOutputStream with user specified block factor
+ ///
+ /// stream to write to
+ /// blocking factor
+ /// The used for the Name fields, or null for ASCII only
+ public TarOutputStream(Stream outputStream, int blockFactor, Encoding nameEncoding)
+ {
+ if (outputStream == null)
+ {
+ throw new ArgumentNullException(nameof(outputStream));
+ }
+
+ this.outputStream = outputStream;
+ buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
+
+ assemblyBuffer = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
+ blockBuffer = ArrayPool.Shared.Rent(TarBuffer.BlockSize);
+
+ this.nameEncoding = nameEncoding;
}
#endregion Constructors
@@ -59,10 +97,7 @@ public bool IsStreamOwner
///
public override bool CanRead
{
- get
- {
- return outputStream.CanRead;
- }
+ get { return outputStream.CanRead; }
}
///
@@ -70,10 +105,7 @@ public override bool CanRead
///
public override bool CanSeek
{
- get
- {
- return outputStream.CanSeek;
- }
+ get { return outputStream.CanSeek; }
}
///
@@ -81,10 +113,7 @@ public override bool CanSeek
///
public override bool CanWrite
{
- get
- {
- return outputStream.CanWrite;
- }
+ get { return outputStream.CanWrite; }
}
///
@@ -92,10 +121,7 @@ public override bool CanWrite
///
public override long Length
{
- get
- {
- return outputStream.Length;
- }
+ get { return outputStream.Length; }
}
///
@@ -103,14 +129,8 @@ public override long Length
///
public override long Position
{
- get
- {
- return outputStream.Position;
- }
- set
- {
- outputStream.Position = value;
- }
+ get { return outputStream.Position; }
+ set { outputStream.Position = value; }
}
///
@@ -152,12 +172,29 @@ public override int ReadByte()
/// The desired number of bytes to read.
/// The total number of bytes read, or zero if at the end of the stream.
/// The number of bytes may be less than the count
- /// requested if data is not avialable.
+ /// requested if data is not available.
public override int Read(byte[] buffer, int offset, int count)
{
return outputStream.Read(buffer, offset, count);
}
+ ///
+ /// read bytes from the current stream and advance the position within the
+ /// stream by the number of bytes read.
+ ///
+ /// The buffer to store read bytes in.
+ /// The index into the buffer to being storing bytes at.
+ /// The desired number of bytes to read.
+ ///
+ /// The total number of bytes read, or zero if at the end of the stream.
+ /// The number of bytes may be less than the count
+ /// requested if data is not available.
+ public override async Task ReadAsync(byte[] buffer, int offset, int count,
+ CancellationToken cancellationToken)
+ {
+ return await outputStream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+
///
/// All buffered data is written to destination
///
@@ -166,17 +203,34 @@ public override void Flush()
outputStream.Flush();
}
+ ///
+ /// All buffered data is written to destination
+ ///
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await outputStream.FlushAsync(cancellationToken);
+ }
+
///
/// Ends the TAR archive without closing the underlying OutputStream.
/// The result is that the EOF block of nulls is written.
///
- public void Finish()
+ public void Finish() => FinishAsync(CancellationToken.None, false).GetAwaiter().GetResult();
+
+ ///
+ /// Ends the TAR archive without closing the underlying OutputStream.
+ /// The result is that the EOF block of nulls is written.
+ ///
+ public Task FinishAsync(CancellationToken cancellationToken) => FinishAsync(cancellationToken, true);
+
+ private async Task FinishAsync(CancellationToken cancellationToken, bool isAsync)
{
if (IsEntryOpen)
{
- CloseEntry();
+ await CloseEntryAsync(cancellationToken, isAsync);
}
- WriteEofBlock();
+
+ await WriteEofBlockAsync(cancellationToken, isAsync);
}
///
@@ -191,6 +245,9 @@ protected override void Dispose(bool disposing)
isClosed = true;
Finish();
buffer.Close();
+
+ ArrayPool.Shared.Return(assemblyBuffer);
+ ArrayPool.Shared.Return(blockBuffer);
}
}
@@ -215,7 +272,7 @@ public int GetRecordSize()
}
///
- /// Get a value indicating wether an entry is open, requiring more data to be written.
+ /// Get a value indicating whether an entry is open, requiring more data to be written.
///
private bool IsEntryOpen
{
@@ -234,42 +291,70 @@ private bool IsEntryOpen
///
/// The TarEntry to be written to the archive.
///
- public void PutNextEntry(TarEntry entry)
+ ///
+ public Task PutNextEntryAsync(TarEntry entry, CancellationToken cancellationToken) =>
+ PutNextEntryAsync(entry, cancellationToken, true);
+
+ ///
+ /// Put an entry on the output stream. This writes the entry's
+ /// header and positions the output stream for writing
+ /// the contents of the entry. Once this method is called, the
+ /// stream is ready for calls to write() to write the entry's
+ /// contents. Once the contents are written, closeEntry()
+ /// MUST be called to ensure that all buffered data
+ /// is completely written to the output stream.
+ ///
+ ///
+ /// The TarEntry to be written to the archive.
+ ///
+ public void PutNextEntry(TarEntry entry) =>
+ PutNextEntryAsync(entry, CancellationToken.None, false).GetAwaiter().GetResult();
+
+ private async Task PutNextEntryAsync(TarEntry entry, CancellationToken cancellationToken, bool isAsync)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
- if (entry.TarHeader.Name.Length > TarHeader.NAMELEN)
+ var namelen = nameEncoding != null
+ ? nameEncoding.GetByteCount(entry.TarHeader.Name)
+ : entry.TarHeader.Name.Length;
+
+ if (namelen > TarHeader.NAMELEN)
{
var longHeader = new TarHeader();
longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME;
longHeader.Name = longHeader.Name + "././@LongLink";
- longHeader.Mode = 420;//644 by default
+ longHeader.Mode = 420; //644 by default
longHeader.UserId = entry.UserId;
longHeader.GroupId = entry.GroupId;
longHeader.GroupName = entry.GroupName;
longHeader.UserName = entry.UserName;
longHeader.LinkName = "";
- longHeader.Size = entry.TarHeader.Name.Length + 1; // Plus one to avoid dropping last char
+ longHeader.Size = namelen + 1; // Plus one to avoid dropping last char
- longHeader.WriteHeader(blockBuffer);
- buffer.WriteBlock(blockBuffer); // Add special long filename header block
+ longHeader.WriteHeader(blockBuffer, nameEncoding);
+ // Add special long filename header block
+ await buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
int nameCharIndex = 0;
- while (nameCharIndex < entry.TarHeader.Name.Length + 1 /* we've allocated one for the null char, now we must make sure it gets written out */)
+ while
+ (nameCharIndex <
+ namelen + 1 /* we've allocated one for the null char, now we must make sure it gets written out */)
{
Array.Clear(blockBuffer, 0, blockBuffer.Length);
- TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize); // This func handles OK the extra char out of string length
+ TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0,
+ TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length
nameCharIndex += TarBuffer.BlockSize;
- buffer.WriteBlock(blockBuffer);
+
+ await buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
}
}
- entry.WriteEntryHeader(blockBuffer);
- buffer.WriteBlock(blockBuffer);
+ entry.WriteEntryHeader(blockBuffer, nameEncoding);
+ await buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
currBytes = 0;
@@ -285,13 +370,26 @@ public void PutNextEntry(TarEntry entry)
/// to the output stream before this entry is closed and the
/// next entry written.
///
- public void CloseEntry()
+ public Task CloseEntryAsync(CancellationToken cancellationToken) => CloseEntryAsync(cancellationToken, true);
+
+ ///
+ /// Close an entry. This method MUST be called for all file
+ /// entries that contain data. The reason is that we must
+ /// buffer data written to the stream in order to satisfy
+ /// the buffer's block based writes. Thus, there may be
+ /// data fragments still being assembled that must be written
+ /// to the output stream before this entry is closed and the
+ /// next entry written.
+ ///
+ public void CloseEntry() => CloseEntryAsync(CancellationToken.None, true).GetAwaiter().GetResult();
+
+ private async Task CloseEntryAsync(CancellationToken cancellationToken, bool isAsync)
{
if (assemblyBufferLength > 0)
{
Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength);
- buffer.WriteBlock(assemblyBuffer);
+ await buffer.WriteBlockAsync(assemblyBuffer, 0, cancellationToken, isAsync);
currBytes += assemblyBufferLength;
assemblyBufferLength = 0;
@@ -315,7 +413,10 @@ public void CloseEntry()
///
public override void WriteByte(byte value)
{
- Write(new byte[] { value }, 0, 1);
+ var oneByteArray = ArrayPool.Shared.Rent(1);
+ oneByteArray[0] = value;
+ Write(oneByteArray, 0, 1);
+ ArrayPool.Shared.Return(oneByteArray);
}
///
@@ -336,7 +437,32 @@ public override void WriteByte(byte value)
///
/// The number of bytes to write.
///
- public override void Write(byte[] buffer, int offset, int count)
+ public override void Write(byte[] buffer, int offset, int count) =>
+ WriteAsync(buffer, offset, count, CancellationToken.None, false).GetAwaiter().GetResult();
+
+ ///
+ /// Writes bytes to the current tar archive entry. This method
+ /// is aware of the current entry and will throw an exception if
+ /// you attempt to write bytes past the length specified for the
+ /// current entry. The method is also (painfully) aware of the
+ /// record buffering required by TarBuffer, and manages buffers
+ /// that are not a multiple of recordsize in length, including
+ /// assembling records from small buffers.
+ ///
+ ///
+ /// The buffer to write to the archive.
+ ///
+ ///
+ /// The offset in the buffer from which to get bytes.
+ ///
+ ///
+ /// The number of bytes to write.
+ ///
+ ///
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
+ WriteAsync(buffer, offset, count, cancellationToken, true);
+
+ private async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, bool isAsync)
{
if (buffer == null)
{
@@ -381,7 +507,7 @@ public override void Write(byte[] buffer, int offset, int count)
Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength);
Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen);
- this.buffer.WriteBlock(blockBuffer);
+ await this.buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
currBytes += blockBuffer.Length;
@@ -413,7 +539,7 @@ public override void Write(byte[] buffer, int offset, int count)
break;
}
- this.buffer.WriteBlock(buffer, offset);
+ await this.buffer.WriteBlockAsync(buffer, offset, cancellationToken, isAsync);
int bufferLength = blockBuffer.Length;
currBytes += bufferLength;
@@ -426,11 +552,11 @@ public override void Write(byte[] buffer, int offset, int count)
/// Write an EOF (end of archive) block to the tar archive.
/// The end of the archive is indicated by two blocks consisting entirely of zero bytes.
///
- private void WriteEofBlock()
+ private async Task WriteEofBlockAsync(CancellationToken cancellationToken, bool isAsync)
{
Array.Clear(blockBuffer, 0, blockBuffer.Length);
- buffer.WriteBlock(blockBuffer);
- buffer.WriteBlock(blockBuffer);
+ await buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
+ await buffer.WriteBlockAsync(blockBuffer, 0, cancellationToken, isAsync);
}
#region Instance Fields
@@ -446,7 +572,7 @@ private void WriteEofBlock()
private int assemblyBufferLength;
///
- /// Flag indicating wether this instance has been closed or not.
+ /// Flag indicating whether this instance has been closed or not.
///
private bool isClosed;
@@ -475,6 +601,11 @@ private void WriteEofBlock()
///
protected Stream outputStream;
+ ///
+ /// name encoding
+ ///
+ protected Encoding nameEncoding;
+
#endregion Instance Fields
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs b/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs
new file mode 100644
index 000000000..433c6a424
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Tar/TarStringExtension.cs
@@ -0,0 +1,13 @@
+using System.IO;
+using ICSharpCode.SharpZipLib.Core;
+
+namespace ICSharpCode.SharpZipLib.Tar
+{
+ internal static class TarStringExtension
+ {
+ public static string ToTarArchivePath(this string s)
+ {
+ return PathUtils.DropPathRoot(s).Replace(Path.DirectorySeparatorChar, '/');
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs
index b6d7f291b..b7c7d2a69 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs
@@ -5,6 +5,7 @@ namespace ICSharpCode.SharpZipLib.Zip.Compression
///
/// This class contains constants used for deflation.
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
public static class DeflaterConstants
{
///
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs
index 2d9b5559a..556911c40 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs
@@ -56,7 +56,7 @@ public class DeflaterEngine
///
/// Construct instance with pending buffer
- /// Adler calculation will be peformed
+ /// Adler calculation will be performed
///
///
/// Pending buffer to use
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
index 03cac7358..a9b78dd75 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
@@ -2,6 +2,9 @@
using System;
using System.IO;
using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
@@ -105,10 +108,7 @@ public virtual void Finish()
break;
}
- if (cryptoTransform_ != null)
- {
- EncryptBlock(buffer_, 0, len);
- }
+ EncryptBlock(buffer_, 0, len);
baseOutputStream_.Write(buffer_, 0, len);
}
@@ -131,6 +131,47 @@ public virtual void Finish()
}
}
+ ///
+ /// Finishes the stream by calling finish() on the deflater.
+ ///
+ /// The that can be used to cancel the operation.
+ ///
+ /// Not all input is deflated
+ ///
+ public virtual async Task FinishAsync(CancellationToken ct)
+ {
+ deflater_.Finish();
+ while (!deflater_.IsFinished)
+ {
+ int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
+ if (len <= 0)
+ {
+ break;
+ }
+
+ EncryptBlock(buffer_, 0, len);
+
+ await baseOutputStream_.WriteAsync(buffer_, 0, len, ct);
+ }
+
+ if (!deflater_.IsFinished)
+ {
+ throw new SharpZipBaseException("Can't deflate all input?");
+ }
+
+ await baseOutputStream_.FlushAsync(ct);
+
+ if (cryptoTransform_ != null)
+ {
+ if (cryptoTransform_ is ZipAESTransform)
+ {
+ AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
+ }
+ cryptoTransform_.Dispose();
+ cryptoTransform_ = null;
+ }
+ }
+
///
/// Gets or sets a flag indicating ownership of underlying stream.
/// When the flag is true will close the underlying stream also.
@@ -153,37 +194,18 @@ public bool CanPatchEntries
#region Encryption
- private string password;
-
- private ICryptoTransform cryptoTransform_;
+ ///
+ /// The CryptoTransform currently being used to encrypt the compressed data.
+ ///
+ protected ICryptoTransform cryptoTransform_;
///
/// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream.
///
protected byte[] AESAuthCode;
- ///
- /// Get/set the password used for encryption.
- ///
- /// When set to null or if the password is empty no encryption is performed
- public string Password
- {
- get
- {
- return password;
- }
- set
- {
- if ((value != null) && (value.Length == 0))
- {
- password = null;
- }
- else
- {
- password = value;
- }
- }
- }
+ ///
+ public Encoding ZipCryptoEncoding { get; set; } = StringCodec.DefaultZipCryptoEncoding;
///
/// Encrypt a block of data
@@ -199,37 +221,10 @@ public string Password
///
protected void EncryptBlock(byte[] buffer, int offset, int length)
{
+ if(cryptoTransform_ is null) return;
cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
}
- ///
- /// Initializes encryption keys based on given .
- ///
- /// The password.
- protected void InitializePassword(string password)
- {
- var pkManaged = new PkzipClassicManaged();
- byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
- cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
- }
-
- ///
- /// Initializes encryption keys based on given password.
- ///
- protected void InitializeAESPassword(ZipEntry entry, string rawPassword,
- out byte[] salt, out byte[] pwdVerifier)
- {
- salt = new byte[entry.AESSaltLen];
- // Salt needs to be cryptographically random, and unique per file
- if (_aesRnd == null)
- _aesRnd = RandomNumberGenerator.Create();
- _aesRnd.GetBytes(salt);
- int blockSize = entry.AESKeySize / 8; // bits to bytes
-
- cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true);
- pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier;
- }
-
#endregion Encryption
#region Deflation Support
@@ -254,10 +249,8 @@ private void Deflate(bool flushing)
{
break;
}
- if (cryptoTransform_ != null)
- {
- EncryptBlock(buffer_, 0, deflateCount);
- }
+
+ EncryptBlock(buffer_, 0, deflateCount);
baseOutputStream_.Write(buffer_, 0, deflateCount);
}
@@ -419,6 +412,38 @@ protected override void Dispose(bool disposing)
}
}
+#if NETSTANDARD2_1_OR_GREATER
+ ///
+ /// Calls and closes the underlying
+ /// stream when is true.
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ if (!isClosed_)
+ {
+ isClosed_ = true;
+
+ try
+ {
+ await FinishAsync(CancellationToken.None);
+ if (cryptoTransform_ != null)
+ {
+ GetAuthCodeIfAES();
+ cryptoTransform_.Dispose();
+ cryptoTransform_ = null;
+ }
+ }
+ finally
+ {
+ if (IsStreamOwner)
+ {
+ await baseOutputStream_.DisposeAsync();
+ }
+ }
+ }
+ }
+#endif
+
///
/// Get the Auth code for AES encrypted entries
///
@@ -484,12 +509,5 @@ public override void Write(byte[] buffer, int offset, int count)
private bool isClosed_;
#endregion Instance Fields
-
- #region Static Fields
-
- // Static to help ensure that multiple files within a zip will get different random salt
- private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create();
-
- #endregion Static Fields
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
index 843627d3f..5c304927a 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Security.Cryptography;
+using ICSharpCode.SharpZipLib.Encryption;
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
@@ -42,7 +43,7 @@ public InflaterInputBuffer(Stream stream, int bufferSize)
#endregion Constructors
///
- /// Get the length of bytes bytes in the
+ /// Get the length of bytes in the
///
public int RawLength
{
@@ -92,9 +93,25 @@ public byte[] ClearText
public int Available
{
get { return available; }
- set { available = value; }
+ set
+ {
+ if (cryptoTransform is ZipAESTransform ct)
+ {
+ ct.AppendFinal(value);
+ }
+
+ available = value;
+ }
}
+ ///
+ /// A limitation how much data is decrypted. If null all the data in the input buffer will be decrypted.
+ /// Setting limit is important in case the HMAC has to be calculated for each zip entry. In that case
+ /// it is not possible to decrypt all available data in the input buffer, and only the data
+ /// belonging to the current zip entry must be decrypted so that the HMAC is correctly calculated.
+ ///
+ internal int? DecryptionLimit { get; set; }
+
///
/// Call passing the current clear text buffer contents.
///
@@ -113,6 +130,11 @@ public void SetInflaterInput(Inflater inflater)
///
public void Fill()
{
+ if (cryptoTransform is ZipAESTransform ct)
+ {
+ ct.AppendAllPending();
+ }
+
rawLength = 0;
int toRead = rawData.Length;
@@ -127,13 +149,11 @@ public void Fill()
toRead -= count;
}
+ clearTextLength = rawLength;
if (cryptoTransform != null)
{
- clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
- }
- else
- {
- clearTextLength = rawLength;
+ var size = CalculateDecryptionSize(rawLength);
+ cryptoTransform.TransformBlock(rawData, 0, size, clearText, 0);
}
available = clearTextLength;
@@ -226,7 +246,7 @@ public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length)
/// Read a from the input stream.
///
/// Returns the byte read.
- public int ReadLeByte()
+ public byte ReadLeByte()
{
if (available <= 0)
{
@@ -290,7 +310,9 @@ public ICryptoTransform CryptoTransform
clearTextLength = rawLength;
if (available > 0)
{
- cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available);
+ var size = CalculateDecryptionSize(available);
+
+ cryptoTransform.TransformBlock(rawData, rawLength - available, size, clearText, rawLength - available);
}
}
else
@@ -301,6 +323,19 @@ public ICryptoTransform CryptoTransform
}
}
+ private int CalculateDecryptionSize(int availableBufferSize)
+ {
+ if (!DecryptionLimit.HasValue)
+ {
+ return availableBufferSize;
+ }
+
+ var size = Math.Min(DecryptionLimit.Value, availableBufferSize);
+ DecryptionLimit -= size;
+
+ return size;
+ }
+
#region Instance Fields
private int rawLength;
@@ -459,9 +494,10 @@ public long Skip(long count)
///
/// Clear any cryptographic state.
///
- protected void StopDecrypting()
+ protected virtual void StopDecrypting()
{
inputBuffer.CryptoTransform = null;
+ inputBuffer.DecryptionLimit = null;
}
///
@@ -596,7 +632,7 @@ public override void SetLength(long value)
/// Writes a sequence of bytes to stream and advances the current position
/// This method always throws a NotSupportedException
///
- /// Thew buffer containing data to write.
+ /// The buffer containing data to write.
/// The offset of the first byte to write.
/// The number of bytes to write.
/// Any access
@@ -704,7 +740,7 @@ public override int Read(byte[] buffer, int offset, int count)
protected long csize;
///
- /// Flag indicating wether this instance has been closed or not.
+ /// Flag indicating whether this instance has been closed or not.
///
private bool isClosed;
diff --git a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
index 319645a5b..29185cbec 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
+using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -195,6 +196,29 @@ public FastZip()
{
}
+ ///
+ /// Initialise a new instance of using the specified
+ ///
+ /// The time setting to use when creating or extracting Zip entries.
+ /// Using TimeSetting.LastAccessTime[Utc] when
+ /// creating an archive will set the file time to the moment of reading.
+ ///
+ public FastZip(TimeSetting timeSetting)
+ {
+ entryFactory_ = new ZipEntryFactory(timeSetting);
+ restoreDateTimeOnExtract_ = true;
+ }
+
+ ///
+ /// Initialise a new instance of using the specified
+ ///
+ /// The time to set all values for created or extracted Zip Entries.
+ public FastZip(DateTime time)
+ {
+ entryFactory_ = new ZipEntryFactory(time);
+ restoreDateTimeOnExtract_ = true;
+ }
+
///
/// Initialise a new instance of
///
@@ -209,7 +233,7 @@ public FastZip(FastZipEvents events)
#region Properties
///
- /// Get/set a value indicating wether empty directories should be created.
+ /// Get/set a value indicating whether empty directories should be created.
///
public bool CreateEmptyDirectories
{
@@ -226,6 +250,15 @@ public string Password
set { password_ = value; }
}
+ ///
+ /// Get / set the method of encrypting entries.
+ ///
+ ///
+ /// Only applies when is set.
+ /// Defaults to ZipCrypto for backwards compatibility purposes.
+ ///
+ public ZipEncryptionMethod EntryEncryptionMethod { get; set; } = ZipEncryptionMethod.ZipCrypto;
+
///
/// Get or set the active when creating Zip files.
///
@@ -267,7 +300,7 @@ public IEntryFactory EntryFactory
/// read Zip64 archives. However it does avoid the situation were a large file
/// is added and cannot be completed correctly.
/// NOTE: Setting the size for entries before they are added is the best solution!
- /// By default the EntryFactory used by FastZip will set fhe file size.
+ /// By default the EntryFactory used by FastZip will set the file size.
///
public UseZip64 UseZip64
{
@@ -276,7 +309,7 @@ public UseZip64 UseZip64
}
///
- /// Get/set a value indicating wether file dates and times should
+ /// Get/set a value indicating whether file dates and times should
/// be restored when extracting files from an archive.
///
/// The default value is false.
@@ -312,6 +345,29 @@ public Deflater.CompressionLevel CompressionLevel
set { compressionLevel_ = value; }
}
+ ///
+ /// Reflects the opposite of the internal , setting it to false overrides the encoding used for reading and writing zip entries
+ ///
+ public bool UseUnicode
+ {
+ get => !_stringCodec.ForceZipLegacyEncoding;
+ set => _stringCodec.ForceZipLegacyEncoding = !value;
+ }
+
+ /// Gets or sets the code page used for reading/writing zip file entries when unicode is disabled
+ public int LegacyCodePage
+ {
+ get => _stringCodec.CodePage;
+ set => _stringCodec.CodePage = value;
+ }
+
+ ///
+ public StringCodec StringCodec
+ {
+ get => _stringCodec;
+ set => _stringCodec = value;
+ }
+
#endregion Properties
#region Delegates
@@ -361,21 +417,80 @@ public void CreateZip(string zipFileName, string sourceDirectory, bool recurse,
/// The directory filter to apply.
/// The is closed after creation.
public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter)
+ {
+ CreateZip(outputStream, sourceDirectory, recurse, fileFilter, directoryFilter, false);
+ }
+
+ ///
+ /// Create a zip archive sending output to the passed.
+ ///
+ /// The stream to write archive data to.
+ /// The directory to source files from.
+ /// True to recurse directories, false for no recursion.
+ /// The file filter to apply.
+ /// The directory filter to apply.
+ /// true to leave open after the zip has been created, false to dispose it.
+ public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter, bool leaveOpen)
+ {
+ var scanner = new FileSystemScanner(fileFilter, directoryFilter);
+ CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen);
+ }
+
+ ///
+ /// Create a zip file.
+ ///
+ /// The name of the zip file to create.
+ /// The directory to source files from.
+ /// True to recurse directories, false for no recursion.
+ /// The file filter to apply.
+ /// The directory filter to apply.
+ public void CreateZip(string zipFileName, string sourceDirectory,
+ bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter)
+ {
+ CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter, false);
+ }
+
+ ///
+ /// Create a zip archive sending output to the passed.
+ ///
+ /// The stream to write archive data to.
+ /// The directory to source files from.
+ /// True to recurse directories, false for no recursion.
+ /// The file filter to apply.
+ /// The directory filter to apply.
+ /// true to leave open after the zip has been created, false to dispose it.
+ public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter, bool leaveOpen = false)
+ {
+ var scanner = new FileSystemScanner(fileFilter, directoryFilter);
+ CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen);
+ }
+
+ ///
+ /// Create a zip archive sending output to the passed.
+ ///
+ /// The stream to write archive data to.
+ /// The directory to source files from.
+ /// True to recurse directories, false for no recursion.
+ /// For performing the actual file system scan
+ /// true to leave open after the zip has been created, false to dispose it.
+ /// The is closed after creation.
+ private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen)
{
NameTransform = new ZipNameTransform(sourceDirectory);
sourceDirectory_ = sourceDirectory;
- using (outputStream_ = new ZipOutputStream(outputStream))
+ using (outputStream_ = new ZipOutputStream(outputStream, _stringCodec))
{
outputStream_.SetLevel((int)CompressionLevel);
+ outputStream_.IsStreamOwner = !leaveOpen;
+ outputStream_.NameTransform = null; // all required transforms handled by us
- if (password_ != null)
+ if (false == string.IsNullOrEmpty(password_) && EntryEncryptionMethod != ZipEncryptionMethod.None)
{
outputStream_.Password = password_;
}
outputStream_.UseZip64 = UseZip64;
- var scanner = new FileSystemScanner(fileFilter, directoryFilter);
scanner.ProcessFile += ProcessFile;
if (this.CreateEmptyDirectories)
{
@@ -533,12 +648,20 @@ private void ProcessFile(object sender, ScanEventArgs e)
{
try
{
- // The open below is equivalent to OpenRead which gaurantees that if opened the
+ // The open below is equivalent to OpenRead which guarantees that if opened the
// file will not be changed by subsequent openers, but precludes opening in some cases
// were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that.
using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read))
{
ZipEntry entry = entryFactory_.MakeFileEntry(e.Name);
+ if (_stringCodec.ForceZipLegacyEncoding)
+ {
+ entry.IsUnicodeText = false;
+ }
+
+ // Set up AES encryption for the entry if required.
+ ConfigureEntryEncryption(entry);
+
outputStream_.PutNextEntry(entry);
AddFileContents(e.Name, stream);
}
@@ -558,6 +681,26 @@ private void ProcessFile(object sender, ScanEventArgs e)
}
}
+ // Set up the encryption method to use for the specific entry.
+ private void ConfigureEntryEncryption(ZipEntry entry)
+ {
+ // Only alter the entries options if AES isn't already enabled for it
+ // (it might have been set up by the entry factory, and if so we let that take precedence)
+ if (!string.IsNullOrEmpty(Password) && entry.AESEncryptionStrength == 0)
+ {
+ switch (EntryEncryptionMethod)
+ {
+ case ZipEncryptionMethod.AES128:
+ entry.AESKeySize = 128;
+ break;
+
+ case ZipEncryptionMethod.AES256:
+ entry.AESKeySize = 256;
+ break;
+ }
+ }
+ }
+
private void AddFileContents(string name, Stream stream)
{
if (stream == null)
@@ -621,14 +764,18 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)
{
buffer_ = new byte[4096];
}
- if ((events_ != null) && (events_.Progress != null))
- {
- StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_,
- events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size);
- }
- else
+
+ using (var inputStream = zipFile_.GetInputStream(entry))
{
- StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_);
+ if ((events_ != null) && (events_.Progress != null))
+ {
+ StreamUtils.Copy(inputStream, outputStream, buffer_,
+ events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size);
+ }
+ else
+ {
+ StreamUtils.Copy(inputStream, outputStream, buffer_);
+ }
}
if (events_ != null)
@@ -639,7 +786,39 @@ private void ExtractFileEntry(ZipEntry entry, string targetName)
if (restoreDateTimeOnExtract_)
{
- File.SetLastWriteTime(targetName, entry.DateTime);
+ switch (entryFactory_.Setting)
+ {
+ case TimeSetting.CreateTime:
+ File.SetCreationTime(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.CreateTimeUtc:
+ File.SetCreationTimeUtc(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastAccessTime:
+ File.SetLastAccessTime(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastAccessTimeUtc:
+ File.SetLastAccessTimeUtc(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastWriteTime:
+ File.SetLastWriteTime(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastWriteTimeUtc:
+ File.SetLastWriteTimeUtc(targetName, entry.DateTime);
+ break;
+
+ case TimeSetting.Fixed:
+ File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime);
+ break;
+
+ default:
+ throw new ZipException("Unhandled time setting in ExtractFileEntry");
+ }
}
if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1))
@@ -687,7 +866,7 @@ private void ExtractEntry(ZipEntry entry)
// TODO: Fire delegate/throw exception were compression method not supported, or name is invalid?
- string dirName = null;
+ string dirName = string.Empty;
if (doExtraction)
{
@@ -707,7 +886,51 @@ private void ExtractEntry(ZipEntry entry)
{
try
{
- Directory.CreateDirectory(dirName);
+ continueRunning_ = events_?.OnProcessDirectory(dirName, true) ?? true;
+ if (continueRunning_)
+ {
+ Directory.CreateDirectory(dirName);
+ if (entry.IsDirectory && restoreDateTimeOnExtract_)
+ {
+ switch (entryFactory_.Setting)
+ {
+ case TimeSetting.CreateTime:
+ Directory.SetCreationTime(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.CreateTimeUtc:
+ Directory.SetCreationTimeUtc(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastAccessTime:
+ Directory.SetLastAccessTime(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastAccessTimeUtc:
+ Directory.SetLastAccessTimeUtc(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastWriteTime:
+ Directory.SetLastWriteTime(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.LastWriteTimeUtc:
+ Directory.SetLastWriteTimeUtc(dirName, entry.DateTime);
+ break;
+
+ case TimeSetting.Fixed:
+ Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime);
+ break;
+
+ default:
+ throw new ZipException("Unhandled time setting in ExtractEntry");
+ }
+ }
+ }
+ else
+ {
+ doExtraction = false;
+ }
}
catch (Exception ex)
{
@@ -771,6 +994,7 @@ private static bool NameIsValid(string name)
private INameTransform extractNameTransform_;
private UseZip64 useZip64_ = UseZip64.Dynamic;
private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION;
+ private StringCodec _stringCodec = new StringCodec();
private string password_;
diff --git a/src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs b/src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs
index bbe40c4d7..d7ec18140 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs
@@ -1,4 +1,6 @@
+using System;
using ICSharpCode.SharpZipLib.Core;
+using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -50,5 +52,16 @@ public interface IEntryFactory
/// Get/set the applicable.
///
INameTransform NameTransform { get; set; }
+
+ ///
+ /// Get the in use.
+ ///
+ TimeSetting Setting { get; }
+
+ ///
+ /// Get the value to use when is set to ,
+ /// or if not specified, the value of when the class was the initialized
+ ///
+ DateTime FixedDateTime { get; }
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs b/src/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs
index 75d97ff19..43aa61403 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs
@@ -1,6 +1,7 @@
using ICSharpCode.SharpZipLib.Core;
using System;
using System.IO;
+using System.Runtime.InteropServices;
using System.Text;
namespace ICSharpCode.SharpZipLib.Zip
@@ -81,7 +82,7 @@ public bool AllowParentTraversal
}
///
- /// Gets or sets a value indicating wether paths on incoming values should be removed.
+ /// Gets or sets a value indicating whether paths on incoming values should be removed.
///
public bool TrimIncomingPaths
{
@@ -133,7 +134,14 @@ public string TransformFile(string name)
{
name = Path.Combine(_baseDirectory, name);
- if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(_baseDirectory, StringComparison.InvariantCultureIgnoreCase))
+ // Ensure base directory ends with directory separator ('/' or '\' depending on OS)
+ var pathBase = Path.GetFullPath(_baseDirectory);
+ if (pathBase[pathBase.Length - 1] != Path.DirectorySeparatorChar)
+ {
+ pathBase += Path.DirectorySeparatorChar;
+ }
+
+ if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(pathBase, StringComparison.InvariantCultureIgnoreCase))
{
throw new InvalidNameException("Parent traversal in paths is not allowed");
}
@@ -176,7 +184,7 @@ public static string MakeValidName(string name, char replacement)
throw new ArgumentNullException(nameof(name));
}
- name = WindowsPathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString()));
+ name = PathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString()));
// Drop any leading slashes.
while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar))
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs
index 9e0a8764a..24643bf2a 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs
@@ -161,7 +161,7 @@ public enum GeneralBitFlags
Method = 0x0006,
///
- /// Bit 3 if set indicates a trailing data desciptor is appended to the entry data
+ /// Bit 3 if set indicates a trailing data descriptor is appended to the entry data
///
Descriptor = 0x0008,
@@ -231,12 +231,28 @@ public enum GeneralBitFlags
///
ReservedPkware15 = 0x8000
}
+
+ ///
+ /// Helpers for
+ ///
+ public static class GeneralBitFlagsExtensions
+ {
+ ///
+ /// This is equivalent of in .NET Core, but since the .NET FW
+ /// version is really slow (due to un-/boxing and reflection) we use this wrapper.
+ ///
+ ///
+ ///
+ ///
+ public static bool Includes(this GeneralBitFlags flagData, GeneralBitFlags flag) => (flag & flagData) != 0;
+ }
#endregion Enumerations
///
/// This class contains constants used for Zip format files
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
public static class ZipConstants
{
#region Versions
@@ -281,6 +297,11 @@ public static class ZipConstants
///
public const int VersionZip64 = 45;
+ ///
+ /// The version required for BZip2 compression (4.6 or higher)
+ ///
+ public const int VersionBZip2 = 46;
+
#endregion Versions
#region Header Sizes
@@ -345,6 +366,16 @@ public static class ZipConstants
[Obsolete("Use CryptoHeaderSize instead")]
public const int CRYPTO_HEADER_SIZE = 12;
+ ///
+ /// The number of bytes in the WinZipAes Auth Code.
+ ///
+ internal const int AESAuthCodeLength = 10;
+
+ ///
+ /// The number of bytes in the password verifier for WinZipAes.
+ ///
+ internal const int AESPasswordVerifyLength = 2;
+
///
/// The size of the Zip64 central directory locator.
///
@@ -443,12 +474,12 @@ public static class ZipConstants
public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24);
///
- /// Central header digitial signature
+ /// Central header digital signature
///
public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24);
///
- /// Central header digitial signature
+ /// Central header digital signature
///
[Obsolete("Use CentralHeaderDigitalSignaure instead")]
public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24);
@@ -465,48 +496,29 @@ public static class ZipConstants
public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
#endregion Header Signatures
+ }
+ ///
+ /// GeneralBitFlags helper extensions
+ ///
+ public static class GenericBitFlagsExtensions
+ {
///
- /// Default encoding used for string conversion. 0 gives the default system OEM code page.
- /// Using the default code page isnt the full solution neccessarily
- /// there are many variable factors, codepage 850 is often a good choice for
- /// European users, however be careful about compatability.
- ///
- [Obsolete("Use ZipStrings instead")]
- public static int DefaultCodePage
- {
- get => ZipStrings.CodePage;
- set => ZipStrings.CodePage = value;
- }
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToString instead")]
- public static string ConvertToString(byte[] data, int count)
- => ZipStrings.ConvertToString(data, count);
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToString instead")]
- public static string ConvertToString(byte[] data)
- => ZipStrings.ConvertToString(data);
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToStringExt instead")]
- public static string ConvertToStringExt(int flags, byte[] data, int count)
- => ZipStrings.ConvertToStringExt(flags, data, count);
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToStringExt instead")]
- public static string ConvertToStringExt(int flags, byte[] data)
- => ZipStrings.ConvertToStringExt(flags, data);
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToArray instead")]
- public static byte[] ConvertToArray(string str)
- => ZipStrings.ConvertToArray(str);
-
- /// Depracated wrapper for
- [Obsolete("Use ZipStrings.ConvertToArray instead")]
- public static byte[] ConvertToArray(int flags, string str)
- => ZipStrings.ConvertToArray(flags, str);
+ /// Efficiently check if any of the flags are set without enum un-/boxing
+ ///
+ ///
+ ///
+ /// Returns whether any of flags are set
+ public static bool HasAny(this GeneralBitFlags target, GeneralBitFlags flags)
+ => ((int)target & (int)flags) != 0;
+
+ ///
+ /// Efficiently check if all the flags are set without enum un-/boxing
+ ///
+ ///
+ ///
+ /// Returns whether the flags are all set
+ public static bool HasAll(this GeneralBitFlags target, GeneralBitFlags flags)
+ => ((int)target & (int)flags) == (int)flags;
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs
new file mode 100644
index 000000000..ed51559cd
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs
@@ -0,0 +1,28 @@
+namespace ICSharpCode.SharpZipLib.Zip
+{
+ ///
+ /// The method of encrypting entries when creating zip archives.
+ ///
+ public enum ZipEncryptionMethod
+ {
+ ///
+ /// No encryption will be used.
+ ///
+ None,
+
+ ///
+ /// Encrypt entries with ZipCrypto.
+ ///
+ ZipCrypto,
+
+ ///
+ /// Encrypt entries with AES 128.
+ ///
+ AES128,
+
+ ///
+ /// Encrypt entries with AES 256.
+ ///
+ AES256
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs
index d5105b5ab..08ff6d09f 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -150,7 +151,7 @@ private enum Known : byte
/// The name passed is null
///
public ZipEntry(string name)
- : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated)
+ : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated, true)
{
}
@@ -171,7 +172,7 @@ public ZipEntry(string name)
///
internal ZipEntry(string name, int versionRequiredToExtract)
: this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy,
- CompressionMethod.Deflated)
+ CompressionMethod.Deflated, true)
{
}
@@ -182,6 +183,7 @@ internal ZipEntry(string name, int versionRequiredToExtract)
/// Version and HostSystem Information
/// Minimum required zip feature version required to extract this entry
/// Compression method for this entry.
+ /// Whether the entry uses unicode for name and comment
///
/// The name passed is null
///
@@ -193,7 +195,7 @@ internal ZipEntry(string name, int versionRequiredToExtract)
/// It is not generally useful, use the constructor specifying the name only.
///
internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo,
- CompressionMethod method)
+ CompressionMethod method, bool unicode)
{
if (name == null)
{
@@ -216,7 +218,7 @@ internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo,
this.versionToExtract = (ushort)versionRequiredToExtract;
this.method = method;
- IsUnicodeText = ZipStrings.UseUnicode;
+ IsUnicodeText = unicode;
}
///
@@ -238,7 +240,7 @@ public ZipEntry(ZipEntry entry)
size = entry.size;
compressedSize = entry.compressedSize;
crc = entry.crc;
- dosTime = entry.dosTime;
+ dateTime = entry.DateTime;
method = entry.method;
comment = entry.comment;
versionToExtract = entry.versionToExtract;
@@ -261,15 +263,9 @@ public ZipEntry(ZipEntry entry)
#endregion Constructors
///
- /// Get a value indicating wether the entry has a CRC value available.
+ /// Get a value indicating whether the entry has a CRC value available.
///
- public bool HasCrc
- {
- get
- {
- return (known & Known.Crc) != 0;
- }
- }
+ public bool HasCrc => (known & Known.Crc) != 0;
///
/// Get/Set flag indicating if entry is encrypted.
@@ -278,45 +274,19 @@ public bool HasCrc
/// This is an assistant that interprets the flags property.
public bool IsCrypted
{
- get
- {
- return (flags & 1) != 0;
- }
- set
- {
- if (value)
- {
- flags |= 1;
- }
- else
- {
- flags &= ~1;
- }
- }
+ get => this.HasFlag(GeneralBitFlags.Encrypted);
+ set => this.SetFlag(GeneralBitFlags.Encrypted, value);
}
///
- /// Get / set a flag indicating wether entry name and comment text are
+ /// Get / set a flag indicating whether entry name and comment text are
/// encoded in unicode UTF8.
///
/// This is an assistant that interprets the flags property.
public bool IsUnicodeText
{
- get
- {
- return (flags & (int)GeneralBitFlags.UnicodeText) != 0;
- }
- set
- {
- if (value)
- {
- flags |= (int)GeneralBitFlags.UnicodeText;
- }
- else
- {
- flags &= ~(int)GeneralBitFlags.UnicodeText;
- }
- }
+ get => this.HasFlag(GeneralBitFlags.UnicodeText);
+ set => this.SetFlag(GeneralBitFlags.UnicodeText, value);
}
///
@@ -324,15 +294,8 @@ public bool IsUnicodeText
///
internal byte CryptoCheckValue
{
- get
- {
- return cryptoCheckValue_;
- }
-
- set
- {
- cryptoCheckValue_ = value;
- }
+ get => cryptoCheckValue_;
+ set => cryptoCheckValue_ = value;
}
///
@@ -368,14 +331,8 @@ internal byte CryptoCheckValue
///
public int Flags
{
- get
- {
- return flags;
- }
- set
- {
- flags = value;
- }
+ get => flags;
+ set => flags = value;
}
///
@@ -384,14 +341,8 @@ public int Flags
/// This is only valid when the entry is part of a
public long ZipFileIndex
{
- get
- {
- return zipFileIndex;
- }
- set
- {
- zipFileIndex = value;
- }
+ get => zipFileIndex;
+ set => zipFileIndex = value;
}
///
@@ -399,34 +350,18 @@ public long ZipFileIndex
///
public long Offset
{
- get
- {
- return offset;
- }
- set
- {
- offset = value;
- }
+ get => offset;
+ set => offset = value;
}
///
/// Get/Set external file attributes as an integer.
- /// The values of this are operating system dependant see
+ /// The values of this are operating system dependent see
/// HostSystem for details
///
public int ExternalFileAttributes
{
- get
- {
- if ((known & Known.ExternalAttributes) == 0)
- {
- return -1;
- }
- else
- {
- return externalFileAttributes;
- }
- }
+ get => (known & Known.ExternalAttributes) == 0 ? -1 : externalFileAttributes;
set
{
@@ -440,25 +375,14 @@ public int ExternalFileAttributes
/// The value / 10 indicates the major version number, and
/// the value mod 10 is the minor version number
///
- public int VersionMadeBy
- {
- get
- {
- return (versionMadeBy & 0xff);
- }
- }
+ public int VersionMadeBy => versionMadeBy & 0xff;
///
/// Get a value indicating this entry is for a DOS/Windows system.
///
public bool IsDOSEntry
- {
- get
- {
- return ((HostSystem == (int)HostSystemID.Msdos) ||
- (HostSystem == (int)HostSystemID.WindowsNT));
- }
- }
+ => (HostSystem == (int)HostSystemID.Msdos)
+ || (HostSystem == (int)HostSystemID.WindowsNT);
///
/// Test the external attributes for this to
@@ -481,7 +405,7 @@ private bool HasDosAttributes(int attributes)
}
///
- /// Gets the compatability information for the external file attribute
+ /// Gets the compatibility information for the external file attribute
/// If the external file attributes are compatible with MS-DOS and can be read
/// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value
/// will be non-zero and identify the host system on which the attributes are compatible.
@@ -519,10 +443,7 @@ private bool HasDosAttributes(int attributes)
///
public int HostSystem
{
- get
- {
- return (versionMadeBy >> 8) & 0xff;
- }
+ get => (versionMadeBy >> 8) & 0xff;
set
{
@@ -567,38 +488,26 @@ public int Version
{
// Return recorded version if known.
if (versionToExtract != 0)
- {
- return versionToExtract & 0x00ff; // Only lower order byte. High order is O/S file system.
- }
- else
- {
- int result = 10;
- if (AESKeySize > 0)
- {
- result = ZipConstants.VERSION_AES; // Ver 5.1 = AES
- }
- else if (CentralHeaderRequiresZip64)
- {
- result = ZipConstants.VersionZip64;
- }
- else if (CompressionMethod.Deflated == method)
- {
- result = 20;
- }
- else if (IsDirectory == true)
- {
- result = 20;
- }
- else if (IsCrypted == true)
- {
- result = 20;
- }
- else if (HasDosAttributes(0x08))
- {
- result = 11;
- }
- return result;
- }
+ // Only lower order byte. High order is O/S file system.
+ return versionToExtract & 0x00ff;
+
+ if (AESKeySize > 0)
+ // Ver 5.1 = AES
+ return ZipConstants.VERSION_AES;
+
+ if (CompressionMethod.BZip2 == method)
+ return ZipConstants.VersionBZip2;
+
+ if (CentralHeaderRequiresZip64)
+ return ZipConstants.VersionZip64;
+
+ if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted)
+ return 20;
+
+ if (HasDosAttributes(0x08))
+ return 11;
+
+ return 10;
}
}
@@ -606,37 +515,22 @@ public int Version
/// Get a value indicating whether this entry can be decompressed by the library.
///
/// This is based on the and
- /// wether the compression method is supported.
- public bool CanDecompress
- {
- get
- {
- return (Version <= ZipConstants.VersionMadeBy) &&
- ((Version == 10) ||
- (Version == 11) ||
- (Version == 20) ||
- (Version == 45) ||
- (Version == 51)) &&
- IsCompressionMethodSupported();
- }
- }
+ /// whether the compression method is supported.
+ public bool CanDecompress
+ => Version <= ZipConstants.VersionMadeBy
+ && (Version == 10 || Version == 11 || Version == 20 || Version == 45 || Version == 46 || Version == 51)
+ && IsCompressionMethodSupported();
///
/// Force this entry to be recorded using Zip64 extensions.
///
- public void ForceZip64()
- {
- forceZip64_ = true;
- }
+ public void ForceZip64() => forceZip64_ = true;
///
- /// Get a value indicating wether Zip64 extensions were forced.
+ /// Get a value indicating whether Zip64 extensions were forced.
///
/// A value of true if Zip64 extensions have been forced on; false if not.
- public bool IsZip64Forced()
- {
- return forceZip64_;
- }
+ public bool IsZip64Forced() => forceZip64_;
///
/// Gets a value indicating if the entry requires Zip64 extensions
@@ -655,7 +549,7 @@ public bool LocalHeaderRequiresZip64
if ((versionToExtract == 0) && IsCrypted)
{
- trueCompressedSize += ZipConstants.CryptoHeaderSize;
+ trueCompressedSize += (ulong)this.EncryptionOverheadSize;
}
// TODO: A better estimation of the true limit based on compression overhead should be used
@@ -670,15 +564,10 @@ public bool LocalHeaderRequiresZip64
}
///
- /// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored.
+ /// Get a value indicating whether the central directory entry requires Zip64 extensions to be stored.
///
- public bool CentralHeaderRequiresZip64
- {
- get
- {
- return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue);
- }
- }
+ public bool CentralHeaderRequiresZip64
+ => LocalHeaderRequiresZip64 || (offset >= uint.MaxValue);
///
/// Get/Set DosTime value.
@@ -694,20 +583,54 @@ public long DosTime
{
return 0;
}
- else
+
+ var year = (uint)DateTime.Year;
+ var month = (uint)DateTime.Month;
+ var day = (uint)DateTime.Day;
+ var hour = (uint)DateTime.Hour;
+ var minute = (uint)DateTime.Minute;
+ var second = (uint)DateTime.Second;
+
+ if (year < 1980)
+ {
+ year = 1980;
+ month = 1;
+ day = 1;
+ hour = 0;
+ minute = 0;
+ second = 0;
+ }
+ else if (year > 2107)
{
- return dosTime;
+ year = 2107;
+ month = 12;
+ day = 31;
+ hour = 23;
+ minute = 59;
+ second = 59;
}
+
+ return ((year - 1980) & 0x7f) << 25 |
+ (month << 21) |
+ (day << 16) |
+ (hour << 11) |
+ (minute << 5) |
+ (second >> 1);
}
set
{
unchecked
{
- dosTime = (uint)value;
+ var dosTime = (uint)value;
+ uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
+ uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
+ uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
+ uint mon = Math.Max(1, Math.Min(12, ((uint)(value >> 21) & 0xf)));
+ uint year = ((dosTime >> 25) & 0x7f) + 1980;
+ int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f)));
+ DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Unspecified);
}
-
- known |= Known.Time;
}
}
@@ -719,51 +642,12 @@ public long DosTime
///
public DateTime DateTime
{
- get
- {
- uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
- uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
- uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
- uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
- uint year = ((dosTime >> 25) & 0x7f) + 1980;
- int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
- return new System.DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
- }
+ get => dateTime;
set
{
- var year = (uint)value.Year;
- var month = (uint)value.Month;
- var day = (uint)value.Day;
- var hour = (uint)value.Hour;
- var minute = (uint)value.Minute;
- var second = (uint)value.Second;
-
- if (year < 1980)
- {
- year = 1980;
- month = 1;
- day = 1;
- hour = 0;
- minute = 0;
- second = 0;
- }
- else if (year > 2107)
- {
- year = 2107;
- month = 12;
- day = 31;
- hour = 23;
- minute = 59;
- second = 59;
- }
-
- DosTime = ((year - 1980) & 0x7f) << 25 |
- (month << 21) |
- (day << 16) |
- (hour << 11) |
- (minute << 5) |
- (second >> 1);
+ dateTime = value;
+ known |= Known.Time;
}
}
@@ -778,10 +662,8 @@ public DateTime DateTime
///
public string Name
{
- get
- {
- return name;
- }
+ get => name;
+ internal set => name = value;
}
///
@@ -791,17 +673,14 @@ public string Name
/// The size or -1 if unknown.
///
/// Setting the size before adding an entry to an archive can help
- /// avoid compatability problems with some archivers which dont understand Zip64 extensions.
+ /// avoid compatibility problems with some archivers which don't understand Zip64 extensions.
public long Size
{
- get
- {
- return (known & Known.Size) != 0 ? (long)size : -1L;
- }
+ get => (known & Known.Size) != 0 ? (long)size : -1L;
set
{
- this.size = (ulong)value;
- this.known |= Known.Size;
+ size = (ulong)value;
+ known |= Known.Size;
}
}
@@ -813,14 +692,11 @@ public long Size
///
public long CompressedSize
{
- get
- {
- return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L;
- }
+ get => (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L;
set
{
- this.compressedSize = (ulong)value;
- this.known |= Known.CompressedSize;
+ compressedSize = (ulong)value;
+ known |= Known.CompressedSize;
}
}
@@ -835,13 +711,10 @@ public long CompressedSize
///
public long Crc
{
- get
- {
- return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L;
- }
+ get => (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L;
set
{
- if (((ulong)crc & 0xffffffff00000000L) != 0)
+ if ((crc & 0xffffffff00000000L) != 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
@@ -851,28 +724,15 @@ public long Crc
}
///
- /// Gets/Sets the compression method. Only Deflated and Stored are supported.
+ /// Gets/Sets the compression method.
///
///
/// The compression method for this entry
///
- ///
- ///
public CompressionMethod CompressionMethod
{
- get
- {
- return method;
- }
-
- set
- {
- if (!IsCompressionMethodSupported(value))
- {
- throw new NotSupportedException("Compression method not supported");
- }
- this.method = value;
- }
+ get => method;
+ set => method = value;
}
///
@@ -880,13 +740,8 @@ public CompressionMethod CompressionMethod
/// Returns same value as CompressionMethod except when AES encrypting, which
/// places 99 in the method and places the real method in the extra data.
///
- internal CompressionMethod CompressionMethodForHeader
- {
- get
- {
- return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method;
- }
- }
+ internal CompressionMethod CompressionMethodForHeader
+ => (AESKeySize > 0) ? CompressionMethod.WinZipAES : method;
///
/// Gets/Sets the extra data.
@@ -899,12 +754,9 @@ internal CompressionMethod CompressionMethodForHeader
///
public byte[] ExtraData
{
- get
- {
- // TODO: This is slightly safer but less efficient. Think about wether it should change.
- // return (byte[]) extra.Clone();
- return extra;
- }
+ // TODO: This is slightly safer but less efficient. Think about whether it should change.
+ // return (byte[]) extra.Clone();
+ get => extra;
set
{
@@ -973,45 +825,54 @@ public int AESKeySize
}
///
- /// AES Encryption strength for storage in extra data in entry header.
- /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
+ /// Gets the AES Version
+ /// 1: AE-1
+ /// 2: AE-2
///
- internal byte AESEncryptionStrength
+ public int AESVersion
{
get
{
- return (byte)_aesEncryptionStrength;
+ return _aesVer;
}
}
+ ///
+ /// AES Encryption strength for storage in extra data in entry header.
+ /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
+ ///
+ internal byte AESEncryptionStrength => (byte)_aesEncryptionStrength;
+
///
/// Returns the length of the salt, in bytes
///
- internal int AESSaltLen
- {
- get
- {
- // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.
- return AESKeySize / 16;
- }
- }
+ /// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.
+ internal int AESSaltLen => AESKeySize / 16;
///
/// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode)
///
- internal int AESOverheadSize
- {
- get
- {
- // File format:
- // Bytes Content
- // Variable Salt value
- // 2 Password verification value
- // Variable Encrypted file data
- // 10 Authentication code
- return 12 + AESSaltLen;
- }
- }
+ /// File format:
+ /// Bytes | Content
+ /// ---------+---------------------------
+ /// Variable | Salt value
+ /// 2 | Password verification value
+ /// Variable | Encrypted file data
+ /// 10 | Authentication code
+ internal int AESOverheadSize => 12 + AESSaltLen;
+
+ ///
+ /// Number of extra bytes required to hold the encryption header fields.
+ ///
+ internal int EncryptionOverheadSize =>
+ !IsCrypted
+ // Entry is not encrypted - no overhead
+ ? 0
+ : _aesEncryptionStrength == 0
+ // Entry is encrypted using ZipCrypto
+ ? ZipConstants.CryptoHeaderSize
+ // Entry is encrypted using AES
+ : AESOverheadSize;
///
/// Process extra data fields updating the entry based on the contents.
@@ -1059,7 +920,7 @@ internal void ProcessExtraData(bool localHeader)
// flag 13 is set indicating masking, the value stored for the
// uncompressed size in the Local Header will be zero.
//
- // Othewise there is problem with minizip implementation
+ // Otherwise there is problem with minizip implementation
if (size == uint.MaxValue)
{
size = (ulong)extraData.ReadLong();
@@ -1088,14 +949,14 @@ internal void ProcessExtraData(bool localHeader)
}
}
- DateTime = GetDateTime(extraData);
+ DateTime = GetDateTime(extraData) ?? DateTime;
if (method == CompressionMethod.WinZipAES)
{
ProcessAESExtraData(extraData);
}
}
- private DateTime GetDateTime(ZipExtraData extraData)
+ private static DateTime? GetDateTime(ZipExtraData extraData)
{
// Check for NT timestamp
// NOTE: Disable by default to match behavior of InfoZIP
@@ -1107,26 +968,13 @@ private DateTime GetDateTime(ZipExtraData extraData)
// Check for Unix timestamp
ExtendedUnixData unixData = extraData.GetData();
- if (unixData != null &&
- // Only apply modification time, but require all other values to be present
- // This is done to match InfoZIP's behaviour
- ((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) &&
- ((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) &&
- ((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0))
+ if (unixData != null && unixData.Include.HasFlag(ExtendedUnixData.Flags.ModificationTime))
return unixData.ModificationTime;
- // Fall back to DOS time
- uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
- uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
- uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
- uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
- uint year = ((dosTime >> 25) & 0x7f) + 1980;
- int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
- return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc);
+ return null;
}
// For AES the method in the entry is 99, and the real compression method is in the extradata
- //
private void ProcessAESExtraData(ZipExtraData extraData)
{
if (extraData.Find(0x9901))
@@ -1154,7 +1002,7 @@ private void ProcessAESExtraData(ZipExtraData extraData)
///
/// Gets/Sets the entry comment.
///
- ///
+ ///
/// If comment is longer than 0xffff.
///
///
@@ -1162,14 +1010,11 @@ private void ProcessAESExtraData(ZipExtraData extraData)
///
///
/// A comment is only available for entries when read via the class.
- /// The class doesnt have the comment data available.
+ /// The class doesn't have the comment data available.
///
public string Comment
{
- get
- {
- return comment;
- }
+ get => comment;
set
{
// This test is strictly incorrect as the length is in characters
@@ -1178,7 +1023,7 @@ public string Comment
// is definitely invalid, shorter comments may also have an invalid length
// where there are multi-byte characters
// The full test is not possible here however as the code page to apply conversions with
- // isnt available.
+ // isn't available.
if ((value != null) && (value.Length > 0xffff))
{
throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535");
@@ -1198,19 +1043,9 @@ public string Comment
/// Currently only dos/windows attributes are tested in this manner.
/// The trailing slash convention should always be followed.
///
- public bool IsDirectory
- {
- get
- {
- int nameLength = name.Length;
- bool result =
- ((nameLength > 0) &&
- ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) ||
- HasDosAttributes(16)
- ;
- return result;
- }
- }
+ public bool IsDirectory
+ => name.Length > 0
+ && (name[name.Length - 1] == '/' || name[name.Length - 1] == '\\') || HasDosAttributes(16);
///
/// Get a value of true if the entry appears to be a file; false otherwise
@@ -1219,22 +1054,13 @@ public bool IsDirectory
/// This only takes account of DOS/Windows attributes. Other operating systems are ignored.
/// For linux and others the result may be incorrect.
///
- public bool IsFile
- {
- get
- {
- return !IsDirectory && !HasDosAttributes(8);
- }
- }
+ public bool IsFile => !IsDirectory && !HasDosAttributes(8);
///
/// Test entry to see if data can be extracted.
///
/// Returns true if data can be extracted for this entry; false otherwise.
- public bool IsCompressionMethodSupported()
- {
- return IsCompressionMethodSupported(CompressionMethod);
- }
+ public bool IsCompressionMethodSupported() => IsCompressionMethodSupported(CompressionMethod);
#region ICloneable Members
@@ -1262,10 +1088,7 @@ public object Clone()
/// Gets a string representation of this ZipEntry.
///
/// A readable textual representation of this
- public override string ToString()
- {
- return name;
- }
+ public override string ToString() => name;
///
/// Test a compression method to see if this library
@@ -1273,17 +1096,15 @@ public override string ToString()
///
/// The compression method to test.
/// Returns true if the compression method is supported; false otherwise
- public static bool IsCompressionMethodSupported(CompressionMethod method)
- {
- return
- (method == CompressionMethod.Deflated) ||
- (method == CompressionMethod.Stored);
- }
+ public static bool IsCompressionMethodSupported(CompressionMethod method)
+ => method == CompressionMethod.Deflated
+ || method == CompressionMethod.Stored
+ || method == CompressionMethod.BZip2;
///
/// Cleans a name making it conform to Zip file conventions.
/// Devices names ('c:\') and UNC share names ('\\server\share') are removed
- /// and forward slashes ('\') are converted to back slashes ('/').
+ /// and back slashes ('\') are converted to forward slashes ('/').
/// Names are made relative by trimming leading slashes which is compatible
/// with the ZIP naming convention.
///
@@ -1328,7 +1149,7 @@ public static string CleanName(string name)
private ulong compressedSize;
private ushort versionToExtract; // Version required to extract (library handles <= 2.0)
private uint crc;
- private uint dosTime;
+ private DateTime dateTime;
private CompressionMethod method = CompressionMethod.Deflated;
private byte[] extra;
@@ -1341,7 +1162,7 @@ public static string CleanName(string name)
private bool forceZip64_;
private byte cryptoCheckValue_;
- private int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used.
+ private int _aesVer; // Version number (1 = AE-1, 2 = AE-2)
private int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256
#endregion Instance Fields
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs
new file mode 100644
index 000000000..927e94cfe
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ICSharpCode.SharpZipLib.Zip
+{
+ ///
+ /// General ZipEntry helper extensions
+ ///
+ public static class ZipEntryExtensions
+ {
+ ///
+ /// Efficiently check if a flag is set without enum un-/boxing
+ ///
+ ///
+ ///
+ /// Returns whether the flag was set
+ public static bool HasFlag(this ZipEntry entry, GeneralBitFlags flag)
+ => (entry.Flags & (int) flag) != 0;
+
+ ///
+ /// Efficiently set a flag without enum un-/boxing
+ ///
+ ///
+ ///
+ /// Whether the passed flag should be set (1) or cleared (0)
+ public static void SetFlag(this ZipEntry entry, GeneralBitFlags flag, bool enabled = true)
+ => entry.Flags = enabled
+ ? entry.Flags | (int) flag
+ : entry.Flags & ~(int) flag;
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs
index d5750f0a6..ccbb26968 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs
@@ -68,7 +68,7 @@ public enum TimeSetting
public ZipEntryFactory()
{
nameTransform_ = new ZipNameTransform();
- isUnicodeText_ = ZipStrings.UseUnicode;
+ isUnicodeText_ = true;
}
///
@@ -162,7 +162,7 @@ public int SetAttributes
}
///
- /// Get set a value indicating wether unidoce text should be set on.
+ /// Get set a value indicating whether unicode text should be set on.
///
public bool IsUnicodeText
{
@@ -364,7 +364,7 @@ public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem)
private INameTransform nameTransform_;
private DateTime fixedDateTime_ = DateTime.Now;
- private TimeSetting timeSetting_;
+ private TimeSetting timeSetting_ = TimeSetting.LastWriteTime;
private bool isUnicodeText_;
private int getAttributes_ = -1;
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs
index 9e0e8037f..cc2e74490 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs
@@ -1,9 +1,10 @@
using System;
using System.IO;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Zip
{
- // TODO: Sort out wether tagged data is useful and what a good implementation might look like.
+ // TODO: Sort out whether tagged data is useful and what a good implementation might look like.
// Its just a sketch of an idea at the moment.
///
@@ -14,7 +15,7 @@ public interface ITaggedData
///
/// Get the ID for this tagged data value.
///
- short TagID { get; }
+ ushort TagID { get; }
///
/// Set the contents of this instance from the data passed.
@@ -40,7 +41,7 @@ public class RawTaggedData : ITaggedData
/// Initialise a new instance.
///
/// The tag ID.
- public RawTaggedData(short tag)
+ public RawTaggedData(ushort tag)
{
_tag = tag;
}
@@ -50,7 +51,7 @@ public RawTaggedData(short tag)
///
/// Get the ID for this tagged data value.
///
- public short TagID
+ public ushort TagID
{
get { return _tag; }
set { _tag = value; }
@@ -99,7 +100,7 @@ public byte[] Data
///
/// The tag ID for this instance.
///
- private short _tag;
+ private ushort _tag;
private byte[] _data;
@@ -138,7 +139,7 @@ public enum Flags : byte
///
/// Get the ID
///
- public short TagID
+ public ushort TagID
{
get { return 0x5455; }
}
@@ -152,16 +153,15 @@ public short TagID
public void SetData(byte[] data, int index, int count)
{
using (MemoryStream ms = new MemoryStream(data, index, count, false))
- using (ZipHelperStream helperStream = new ZipHelperStream(ms))
{
// bit 0 if set, modification time is present
// bit 1 if set, access time is present
// bit 2 if set, creation time is present
- _flags = (Flags)helperStream.ReadByte();
+ _flags = (Flags)ms.ReadByte();
if (((_flags & Flags.ModificationTime) != 0))
{
- int iTime = helperStream.ReadLEInt();
+ int iTime = ms.ReadLEInt();
_modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
new TimeSpan(0, 0, 0, iTime, 0);
@@ -172,7 +172,7 @@ public void SetData(byte[] data, int index, int count)
if ((_flags & Flags.AccessTime) != 0)
{
- int iTime = helperStream.ReadLEInt();
+ int iTime = ms.ReadLEInt();
_lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
new TimeSpan(0, 0, 0, iTime, 0);
@@ -180,7 +180,7 @@ public void SetData(byte[] data, int index, int count)
if ((_flags & Flags.CreateTime) != 0)
{
- int iTime = helperStream.ReadLEInt();
+ int iTime = ms.ReadLEInt();
_createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
new TimeSpan(0, 0, 0, iTime, 0);
@@ -195,27 +195,25 @@ public void SetData(byte[] data, int index, int count)
public byte[] GetData()
{
using (MemoryStream ms = new MemoryStream())
- using (ZipHelperStream helperStream = new ZipHelperStream(ms))
{
- helperStream.IsStreamOwner = false;
- helperStream.WriteByte((byte)_flags); // Flags
+ ms.WriteByte((byte)_flags); // Flags
if ((_flags & Flags.ModificationTime) != 0)
{
TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var seconds = (int)span.TotalSeconds;
- helperStream.WriteLEInt(seconds);
+ ms.WriteLEInt(seconds);
}
if ((_flags & Flags.AccessTime) != 0)
{
TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var seconds = (int)span.TotalSeconds;
- helperStream.WriteLEInt(seconds);
+ ms.WriteLEInt(seconds);
}
if ((_flags & Flags.CreateTime) != 0)
{
TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var seconds = (int)span.TotalSeconds;
- helperStream.WriteLEInt(seconds);
+ ms.WriteLEInt(seconds);
}
return ms.ToArray();
}
@@ -327,7 +325,7 @@ public class NTTaggedData : ITaggedData
///
/// Get the ID for this tagged data value.
///
- public short TagID
+ public ushort TagID
{
get { return 10; }
}
@@ -341,24 +339,23 @@ public short TagID
public void SetData(byte[] data, int index, int count)
{
using (MemoryStream ms = new MemoryStream(data, index, count, false))
- using (ZipHelperStream helperStream = new ZipHelperStream(ms))
{
- helperStream.ReadLEInt(); // Reserved
- while (helperStream.Position < helperStream.Length)
+ ms.ReadLEInt(); // Reserved
+ while (ms.Position < ms.Length)
{
- int ntfsTag = helperStream.ReadLEShort();
- int ntfsLength = helperStream.ReadLEShort();
+ int ntfsTag = ms.ReadLEShort();
+ int ntfsLength = ms.ReadLEShort();
if (ntfsTag == 1)
{
if (ntfsLength >= 24)
{
- long lastModificationTicks = helperStream.ReadLELong();
+ long lastModificationTicks = ms.ReadLELong();
_lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks);
- long lastAccessTicks = helperStream.ReadLELong();
+ long lastAccessTicks = ms.ReadLELong();
_lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks);
- long createTimeTicks = helperStream.ReadLELong();
+ long createTimeTicks = ms.ReadLELong();
_createTime = DateTime.FromFileTimeUtc(createTimeTicks);
}
break;
@@ -366,7 +363,7 @@ public void SetData(byte[] data, int index, int count)
else
{
// An unknown NTFS tag so simply skip it.
- helperStream.Seek(ntfsLength, SeekOrigin.Current);
+ ms.Seek(ntfsLength, SeekOrigin.Current);
}
}
}
@@ -379,15 +376,13 @@ public void SetData(byte[] data, int index, int count)
public byte[] GetData()
{
using (MemoryStream ms = new MemoryStream())
- using (ZipHelperStream helperStream = new ZipHelperStream(ms))
- {
- helperStream.IsStreamOwner = false;
- helperStream.WriteLEInt(0); // Reserved
- helperStream.WriteLEShort(1); // Tag
- helperStream.WriteLEShort(24); // Length = 3 x 8.
- helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc());
- helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc());
- helperStream.WriteLELong(_createTime.ToFileTimeUtc());
+ {
+ ms.WriteLEInt(0); // Reserved
+ ms.WriteLEShort(1); // Tag
+ ms.WriteLEShort(24); // Length = 3 x 8.
+ ms.WriteLELong(_lastModificationTime.ToFileTimeUtc());
+ ms.WriteLELong(_lastAccessTime.ToFileTimeUtc());
+ ms.WriteLELong(_createTime.ToFileTimeUtc());
return ms.ToArray();
}
}
@@ -521,7 +516,7 @@ public ZipExtraData(byte[] data)
{
if (data == null)
{
- _data = new byte[0];
+ _data = Empty.Array();
}
else
{
@@ -552,7 +547,7 @@ public void Clear()
{
if ((_data == null) || (_data.Length != 0))
{
- _data = new byte[0];
+ _data = Empty.Array();
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
index 9a7f64e9f..3abe9516b 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
@@ -130,7 +130,7 @@ public enum TestOperation
}
///
- /// Status returned returned by during testing.
+ /// Status returned by during testing.
///
/// TestArchive
public class TestStatus
@@ -191,7 +191,7 @@ public long BytesTested
}
///
- /// Get a value indicating wether the last entry test was valid.
+ /// Get a value indicating whether the last entry test was valid.
///
public bool EntryValid
{
@@ -318,7 +318,7 @@ public class ZipFile : IEnumerable, IDisposable
#region KeyHandling
///
- /// Delegate for handling keys/password setting during compresion/decompression.
+ /// Delegate for handling keys/password setting during compression/decompression.
///
public delegate void KeysRequiredEventHandler(
object sender,
@@ -367,14 +367,15 @@ public string Password
}
else
{
- rawPassword_ = value;
- key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(value));
+ key = PkzipClassic.GenerateKeys(ZipCryptoEncoding.GetBytes(value));
}
+
+ rawPassword_ = value;
}
}
///
- /// Get a value indicating wether encryption keys are currently available.
+ /// Get a value indicating whether encryption keys are currently available.
///
private bool HaveKeys
{
@@ -389,6 +390,7 @@ private bool HaveKeys
/// Opens a Zip file with the given name for reading.
///
/// The name of the file to open.
+ ///
/// The argument supplied is null.
///
/// An i/o error occurs
@@ -396,13 +398,18 @@ private bool HaveKeys
///
/// The file doesn't contain a valid zip archive.
///
- public ZipFile(string name)
+ public ZipFile(string name, StringCodec stringCodec = null)
{
name_ = name ?? throw new ArgumentNullException(nameof(name));
baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read);
isStreamOwner = true;
+ if (stringCodec != null)
+ {
+ _stringCodec = stringCodec;
+ }
+
try
{
ReadEntries();
@@ -538,7 +545,7 @@ public ZipFile(Stream stream, bool leaveOpen)
}
else
{
- entries_ = new ZipEntry[0];
+ entries_ = Empty.Array();
isNewArchive_ = true;
}
}
@@ -548,7 +555,7 @@ public ZipFile(Stream stream, bool leaveOpen)
///
internal ZipFile()
{
- entries_ = new ZipEntry[0];
+ entries_ = Empty.Array();
isNewArchive_ = true;
}
@@ -653,7 +660,7 @@ public bool IsStreamOwner
}
///
- /// Get a value indicating wether
+ /// Get a value indicating whether
/// this archive is embedded in another file or not.
///
public bool IsEmbeddedArchive
@@ -724,6 +731,21 @@ public ZipEntry this[int index]
}
}
+
+ ///
+ public Encoding ZipCryptoEncoding
+ {
+ get => _stringCodec.ZipCryptoEncoding;
+ set => _stringCodec.ZipCryptoEncoding = value;
+ }
+
+ ///
+ public StringCodec StringCodec
+ {
+ get => _stringCodec;
+ set => _stringCodec = value;
+ }
+
#endregion Properties
#region Input Handling
@@ -882,6 +904,10 @@ public Stream GetInputStream(long entryIndex)
result = new InflaterInputStream(result, new Inflater(true));
break;
+ case CompressionMethod.BZip2:
+ result = new BZip2.BZip2InputStream(result);
+ break;
+
default:
throw new ZipException("Unsupported compression method " + method);
}
@@ -955,6 +981,9 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
if (testing && testData && this[entryIndex].IsFile)
{
+ // Don't check CRC for AES encrypted archives
+ var checkCRC = this[entryIndex].AESKeySize == 0;
+
if (resultHandler != null)
{
status.SetOperation(TestOperation.EntryData);
@@ -970,7 +999,10 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
int bytesRead;
while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0)
{
- crc.Update(new ArraySegment(buffer, 0, bytesRead));
+ if (checkCRC)
+ {
+ crc.Update(new ArraySegment(buffer, 0, bytesRead));
+ }
if (resultHandler != null)
{
@@ -981,7 +1013,7 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
}
}
- if (this[entryIndex].Crc != crc.Value)
+ if (checkCRC && this[entryIndex].Crc != crc.Value)
{
status.AddError();
@@ -992,22 +1024,24 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0)
{
- var helper = new ZipHelperStream(baseStream_);
var data = new DescriptorData();
- helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data);
- if (this[entryIndex].Crc != data.Crc)
+ ZipFormat.ReadDataDescriptor(baseStream_, this[entryIndex].LocalHeaderRequiresZip64, data);
+ if (checkCRC && this[entryIndex].Crc != data.Crc)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor CRC mismatch");
}
if (this[entryIndex].CompressedSize != data.CompressedSize)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor compressed size mismatch");
}
if (this[entryIndex].Size != data.Size)
{
status.AddError();
+ resultHandler?.Invoke(status, "Descriptor size mismatch");
}
}
}
@@ -1050,6 +1084,7 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
[Flags]
private enum HeaderTest
{
+ None = 0x0,
Extract = 0x01, // Check that this header represents an entry whose data can be extracted
Header = 0x02, // Check that this header contents are valid
}
@@ -1076,13 +1111,12 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
if (signature != ZipConstants.LocalHeaderSignature)
{
- throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}",
- entryAbsOffset, ZipConstants.LocalHeaderSignature, signature));
+ throw new ZipException($"Wrong local header signature at 0x{entryAbsOffset:x}, expected 0x{ZipConstants.LocalHeaderSignature:x8}, actual 0x{signature:x8}");
}
var extractVersion = (short)(ReadLEUshort() & 0x00ff);
- var localFlags = (short)ReadLEUshort();
- var compressionMethod = (short)ReadLEUshort();
+ var localFlags = (GeneralBitFlags)ReadLEUshort();
+ var compressionMethod = (CompressionMethod)ReadLEUshort();
var fileTime = (short)ReadLEUshort();
var fileDate = (short)ReadLEUshort();
uint crcValue = ReadLEUint();
@@ -1100,7 +1134,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
var localExtraData = new ZipExtraData(extraData);
// Extra data / zip64 checks
- if (localExtraData.Find(1))
+ if (localExtraData.Find(headerID: 1))
{
// 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64
// and size or compressedSize = MaxValue, due to rogue creators.
@@ -1108,15 +1142,15 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
size = localExtraData.ReadLong();
compressedSize = localExtraData.ReadLong();
- if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0)
+ if (localFlags.HasAny(GeneralBitFlags.Descriptor))
{
// These may be valid if patched later
- if ((size != -1) && (size != entry.Size))
+ if ((size != 0) && (size != entry.Size))
{
throw new ZipException("Size invalid for descriptor");
}
- if ((compressedSize != -1) && (compressedSize != entry.CompressedSize))
+ if ((compressedSize != 0) && (compressedSize != entry.CompressedSize))
{
throw new ZipException("Compressed size invalid for descriptor");
}
@@ -1141,15 +1175,19 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
throw new ZipException("Compression method not supported");
}
- if ((extractVersion > ZipConstants.VersionMadeBy)
- || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)))
+ if (extractVersion > ZipConstants.VersionMadeBy
+ || (extractVersion > 20 && extractVersion < ZipConstants.VersionZip64))
{
- throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
+ throw new ZipException($"Version required to extract this entry not supported ({extractVersion})");
}
- if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0)
+ const GeneralBitFlags notSupportedFlags = GeneralBitFlags.Patched
+ | GeneralBitFlags.StrongEncryption
+ | GeneralBitFlags.EnhancedCompress
+ | GeneralBitFlags.HeaderMasked;
+ if (localFlags.HasAny(notSupportedFlags))
{
- throw new ZipException("The library does not support the zip version required to extract this entry");
+ throw new ZipException($"The library does not support the zip features required to extract this entry ({localFlags & notSupportedFlags:F})");
}
}
}
@@ -1173,51 +1211,53 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
(extractVersion != 63)
)
{
- throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion));
+ throw new ZipException($"Version required to extract this entry is invalid ({extractVersion})");
}
+ var localEncoding = _stringCodec.ZipInputEncoding(localFlags);
+
// Local entry flags dont have reserved bit set on.
- if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0)
+ if (localFlags.HasAny(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15))
{
throw new ZipException("Reserved bit flags cannot be set.");
}
// Encryption requires extract version >= 20
- if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20))
+ if (localFlags.HasAny(GeneralBitFlags.Encrypted) && extractVersion < 20)
{
- throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
+ throw new ZipException($"Version required to extract this entry is too low for encryption ({extractVersion})");
}
// Strong encryption requires encryption flag to be set and extract version >= 50.
- if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
+ if (localFlags.HasAny(GeneralBitFlags.StrongEncryption))
{
- if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0)
+ if (!localFlags.HasAny(GeneralBitFlags.Encrypted))
{
throw new ZipException("Strong encryption flag set but encryption flag is not set");
}
if (extractVersion < 50)
{
- throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
+ throw new ZipException($"Version required to extract this entry is too low for encryption ({extractVersion})");
}
}
// Patched entries require extract version >= 27
- if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27))
+ if (localFlags.HasAny(GeneralBitFlags.Patched) && extractVersion < 27)
{
- throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
+ throw new ZipException($"Patched data requires higher version than ({extractVersion})");
}
// Central header flags match local entry flags.
- if (localFlags != entry.Flags)
+ if ((int)localFlags != entry.Flags)
{
- throw new ZipException("Central header/local header flags mismatch");
+ throw new ZipException($"Central header/local header flags mismatch ({(GeneralBitFlags)entry.Flags:F} vs {localFlags:F})");
}
// Central header compression method matches local entry
- if (entry.CompressionMethod != (CompressionMethod)compressionMethod)
+ if (entry.CompressionMethodForHeader != compressionMethod)
{
- throw new ZipException("Central header/local header compression method mismatch");
+ throw new ZipException($"Central header/local header compression method mismatch ({entry.CompressionMethodForHeader:G} vs {compressionMethod:G})");
}
if (entry.Version != extractVersion)
@@ -1226,7 +1266,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
}
// Strong encryption and extract version match
- if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
+ if (localFlags.HasAny(GeneralBitFlags.StrongEncryption))
{
if (extractVersion < 62)
{
@@ -1234,15 +1274,15 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
}
}
- if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0)
+ if (localFlags.HasAny(GeneralBitFlags.HeaderMasked))
{
- if ((fileTime != 0) || (fileDate != 0))
+ if (fileTime != 0 || fileDate != 0)
{
throw new ZipException("Header masked set but date/time values non-zero");
}
}
- if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0)
+ if (!localFlags.HasAny(GeneralBitFlags.Descriptor))
{
if (crcValue != (uint)entry.Crc)
{
@@ -1251,8 +1291,8 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
}
// Crc valid for empty entry.
- // This will also apply to streamed entries where size isnt known and the header cant be patched
- if ((size == 0) && (compressedSize == 0))
+ // This will also apply to streamed entries where size isn't known and the header cant be patched
+ if (size == 0 && compressedSize == 0)
{
if (crcValue != 0)
{
@@ -1268,7 +1308,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
}
// Name data has already been read convert it and compare.
- string localName = ZipStrings.ConvertToStringExt(localFlags, nameData);
+ string localName = localEncoding.GetString(nameData);
// Central directory and local entry name match
if (localName != entry.Name)
@@ -1288,7 +1328,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
// If so until details are known we will be strict.
if (entry.IsCrypted)
{
- if (compressedSize > ZipConstants.CryptoHeaderSize + 2)
+ if (compressedSize > entry.EncryptionOverheadSize + 2)
{
throw new ZipException("Directory compressed size invalid");
}
@@ -1296,7 +1336,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
else if (compressedSize > 2)
{
// When not compressed the directory size can validly be 2 bytes
- // if the true size wasnt known when data was originally being written.
+ // if the true size wasn't known when data was originally being written.
// NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes
throw new ZipException("Directory compressed size invalid");
}
@@ -1312,23 +1352,18 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
// Size can be verified only if it is known in the local header.
// it will always be known in the central header.
- if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) ||
+ if (!localFlags.HasAny(GeneralBitFlags.Descriptor) ||
((size > 0 || compressedSize > 0) && entry.Size > 0))
{
- if ((size != 0)
- && (size != entry.Size))
+ if (size != 0 && size != entry.Size)
{
- throw new ZipException(
- string.Format("Size mismatch between central header({0}) and local header({1})",
- entry.Size, size));
+ throw new ZipException($"Size mismatch between central header ({entry.Size}) and local header ({size})");
}
- if ((compressedSize != 0)
+ if (compressedSize != 0
&& (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1))
{
- throw new ZipException(
- string.Format("Compressed size mismatch between central header({0}) and local header({1})",
- entry.CompressedSize, compressedSize));
+ throw new ZipException($"Compressed size mismatch between central header({entry.CompressedSize}) and local header({compressedSize})");
}
}
@@ -1564,14 +1599,11 @@ public void CommitUpdate()
else
{
// Create an empty archive if none existed originally.
- if (entries_.Length == 0)
- {
- byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_);
- using (ZipHelperStream zhs = new ZipHelperStream(baseStream_))
- {
- zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment);
- }
- }
+ if (entries_.Length != 0) return;
+ byte[] theComment = (newComment_ != null)
+ ? newComment_.RawComment
+ : _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_);
+ ZipFormat.WriteEndOfCentralDirectory(baseStream_, 0, 0, 0, theComment);
}
}
finally
@@ -1604,7 +1636,7 @@ public void SetComment(string comment)
CheckUpdating();
- newComment_ = new ZipString(comment);
+ newComment_ = new ZipString(comment, _stringCodec.ZipArchiveCommentEncoding);
if (newComment_.RawLength > 0xffff)
{
@@ -1625,7 +1657,7 @@ private void AddUpdate(ZipUpdate update)
{
contentsEdited_ = true;
- int index = FindExistingUpdate(update.Entry.Name);
+ int index = FindExistingUpdate(update.Entry.Name, isEntryName: true);
if (index >= 0)
{
@@ -1896,9 +1928,9 @@ public void AddDirectory(string directoryName)
/// Check if the specified compression method is supported for adding a new entry.
///
/// The compression method for the new entry.
- private void CheckSupportedCompressionMethod(CompressionMethod compressionMethod)
+ private static void CheckSupportedCompressionMethod(CompressionMethod compressionMethod)
{
- if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored)
+ if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored && compressionMethod != CompressionMethod.BZip2)
{
throw new NotImplementedException("Compression method not supported");
}
@@ -1916,11 +1948,9 @@ public void Modify(ZipEntry original, ZipEntry updated)
if ( original == null ) {
throw new ArgumentNullException("original");
}
-
if ( updated == null ) {
throw new ArgumentNullException("updated");
}
-
CheckUpdating();
contentsEdited_ = true;
updates_.Add(new ZipUpdate(original, updated));
@@ -2093,7 +2123,7 @@ private void WriteLocalEntryHeader(ZipUpdate update)
break;
case UseZip64.Off:
- // Do nothing. The entry itself may be using Zip64 independantly.
+ // Do nothing. The entry itself may be using Zip64 independently.
break;
}
}
@@ -2134,7 +2164,8 @@ private void WriteLocalEntryHeader(ZipUpdate update)
WriteLEInt((int)entry.Size);
}
- byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
+ var entryEncoding = _stringCodec.ZipInputEncoding(entry.Flags);
+ byte[] name = entryEncoding.GetBytes(entry.Name);
if (name.Length > 0xFFFF)
{
@@ -2241,7 +2272,8 @@ private int WriteCentralDirectoryHeader(ZipEntry entry)
WriteLEInt((int)entry.Size);
}
- byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
+ var entryEncoding = _stringCodec.ZipInputEncoding(entry.Flags);
+ byte[] name = entryEncoding.GetBytes(entry.Name);
if (name.Length > 0xFFFF)
{
@@ -2325,7 +2357,7 @@ private int WriteCentralDirectoryHeader(ZipEntry entry)
baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
}
- byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0];
+ byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : Empty.Array();
if (rawComment.Length > 0)
{
@@ -2381,26 +2413,37 @@ private byte[] GetBuffer()
private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
{
- int bytesToCopy = GetDescriptorSize(update);
+ // Don't include the signature size to allow copy without seeking
+ var bytesToCopy = GetDescriptorSize(update, false);
+
+ // Don't touch the source stream if no descriptor is present
+ if (bytesToCopy == 0) return;
- if (bytesToCopy > 0)
+ var buffer = GetBuffer();
+
+ // Copy the first 4 bytes of the descriptor
+ source.Read(buffer, 0, sizeof(int));
+ dest.Write(buffer, 0, sizeof(int));
+
+ if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature)
{
- byte[] buffer = GetBuffer();
+ // The initial bytes wasn't the descriptor, reduce the pending byte count
+ bytesToCopy -= buffer.Length;
+ }
- while (bytesToCopy > 0)
- {
- int readSize = Math.Min(buffer.Length, bytesToCopy);
+ while (bytesToCopy > 0)
+ {
+ int readSize = Math.Min(buffer.Length, bytesToCopy);
- int bytesRead = source.Read(buffer, 0, readSize);
- if (bytesRead > 0)
- {
- dest.Write(buffer, 0, bytesRead);
- bytesToCopy -= bytesRead;
- }
- else
- {
- throw new ZipException("Unxpected end of stream");
- }
+ int bytesRead = source.Read(buffer, 0, readSize);
+ if (bytesRead > 0)
+ {
+ dest.Write(buffer, 0, bytesRead);
+ bytesToCopy -= bytesRead;
+ }
+ else
+ {
+ throw new ZipException("Unxpected end of stream");
}
}
}
@@ -2459,32 +2502,37 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
/// Get the size of the source descriptor for a .
///
/// The update to get the size for.
- /// The descriptor size, zero if there isnt one.
- private int GetDescriptorSize(ZipUpdate update)
+ /// Whether to include the signature size
+ /// The descriptor size, zero if there isn't one.
+ private static int GetDescriptorSize(ZipUpdate update, bool includingSignature)
{
- int result = 0;
- if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
- {
- result = ZipConstants.DataDescriptorSize - 4;
- if (update.Entry.LocalHeaderRequiresZip64)
- {
- result = ZipConstants.Zip64DataDescriptorSize - 4;
- }
- }
- return result;
+ if (!((GeneralBitFlags)update.Entry.Flags).HasAny(GeneralBitFlags.Descriptor))
+ return 0;
+
+ var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64
+ ? ZipConstants.Zip64DataDescriptorSize
+ : ZipConstants.DataDescriptorSize;
+
+ return includingSignature
+ ? descriptorWithSignature
+ : descriptorWithSignature - sizeof(int);
}
private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
{
- int bytesToCopy = GetDescriptorSize(update);
+ var buffer = GetBuffer(); ;
+
+ stream.Position = sourcePosition;
+ stream.Read(buffer, 0, sizeof(int));
+ var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature;
+
+ var bytesToCopy = GetDescriptorSize(update, sourceHasSignature);
while (bytesToCopy > 0)
{
- var readSize = (int)bytesToCopy;
- byte[] buffer = GetBuffer();
-
stream.Position = sourcePosition;
- int bytesRead = stream.Read(buffer, 0, readSize);
+
+ var bytesRead = stream.Read(buffer, 0, bytesToCopy);
if (bytesRead > 0)
{
stream.Position = destinationPosition;
@@ -2495,7 +2543,7 @@ private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long
}
else
{
- throw new ZipException("Unxpected end of stream");
+ throw new ZipException("Unexpected end of stream");
}
}
}
@@ -2554,13 +2602,9 @@ private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc
private int FindExistingUpdate(ZipEntry entry)
{
int result = -1;
- string convertedName = entry.IsDirectory
- ? GetTransformedDirectoryName(entry.Name)
- : GetTransformedFileName(entry.Name);
-
- if (updateIndex_.ContainsKey(convertedName))
+ if (updateIndex_.ContainsKey(entry.Name))
{
- result = (int)updateIndex_[convertedName];
+ result = (int)updateIndex_[entry.Name];
}
/*
// This is slow like the coming of the next ice age but takes less storage and may be useful
@@ -2578,11 +2622,11 @@ private int FindExistingUpdate(ZipEntry entry)
return result;
}
- private int FindExistingUpdate(string fileName)
+ private int FindExistingUpdate(string fileName, bool isEntryName = false)
{
int result = -1;
- string convertedName = GetTransformedFileName(fileName);
+ string convertedName = !isEntryName ? GetTransformedFileName(fileName) : fileName;
if (updateIndex_.ContainsKey(convertedName))
{
@@ -2621,17 +2665,34 @@ private Stream GetOutputStream(ZipEntry entry)
switch (entry.CompressionMethod)
{
case CompressionMethod.Stored:
- result = new UncompressedStream(result);
+ if (!entry.IsCrypted)
+ {
+ // If there is an encryption stream in use, that can be returned directly
+ // otherwise, wrap the base stream in an UncompressedStream instead of returning it directly
+ result = new UncompressedStream(result);
+ }
break;
case CompressionMethod.Deflated:
var dos = new DeflaterOutputStream(result, new Deflater(9, true))
{
- IsStreamOwner = false
+ // If there is an encryption stream in use, then we want that to be disposed when the deflator stream is disposed
+ // If not, then we don't want it to dispose the base stream
+ IsStreamOwner = entry.IsCrypted
};
result = dos;
break;
+ case CompressionMethod.BZip2:
+ var bzos = new BZip2.BZip2OutputStream(result)
+ {
+ // If there is an encryption stream in use, then we want that to be disposed when the BZip2OutputStream stream is disposed
+ // If not, then we don't want it to dispose the base stream
+ IsStreamOwner = entry.IsCrypted
+ };
+ result = bzos;
+ break;
+
default:
throw new ZipException("Unknown compression method " + entry.CompressionMethod);
}
@@ -2652,6 +2713,8 @@ private void AddEntry(ZipFile workFile, ZipUpdate update)
}
}
+ var useCrc = update.Entry.AESKeySize == 0;
+
if (source != null)
{
using (source)
@@ -2676,7 +2739,7 @@ private void AddEntry(ZipFile workFile, ZipUpdate update)
using (Stream output = workFile.GetOutputStream(update.OutEntry))
{
- CopyBytes(update, output, source, sourceStreamLength, true);
+ CopyBytes(update, output, source, sourceStreamLength, useCrc);
}
long dataEnd = workFile.baseStream_.Position;
@@ -2684,8 +2747,7 @@ private void AddEntry(ZipFile workFile, ZipUpdate update)
if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor)
{
- var helper = new ZipHelperStream(workFile.baseStream_);
- helper.WriteDataDescriptor(update.OutEntry);
+ ZipFormat.WriteDataDescriptor(workFile.baseStream_, update.OutEntry);
}
}
}
@@ -2739,6 +2801,7 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
// Clumsy way of handling retrieving the original name and extra data length for now.
// TODO: Stop re-reading name and data length in CopyEntryDirect.
+
uint nameLength = ReadLEUshort();
uint extraLength = ReadLEUshort();
@@ -2747,14 +2810,25 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
if (skipOver)
{
if (update.OffsetBasedSize != -1)
+ {
destinationPosition += update.OffsetBasedSize;
+ }
else
- // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) archives.
- // WinZip produces a warning on these entries:
- // "caution: value of lrec.csize (compressed size) changed from ..."
- destinationPosition +=
- (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
- update.Entry.CompressedSize + GetDescriptorSize(update);
+ {
+ // Skip entry header
+ destinationPosition += (sourcePosition - entryDataOffset) + NameLengthOffset;
+
+ // Skip entry compressed data
+ destinationPosition += update.Entry.CompressedSize;
+
+ // Seek to end of entry to check for descriptor signature
+ baseStream_.Seek(destinationPosition, SeekOrigin.Begin);
+
+ var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature;
+
+ // Skip descriptor and it's signature (if present)
+ destinationPosition += GetDescriptorSize(update, descriptorHasSignature);
+ }
}
else
{
@@ -2810,15 +2884,11 @@ private void UpdateCommentOnly()
{
long baseLength = baseStream_.Length;
- ZipHelperStream updateFile = null;
+ Stream updateFile;
if (archiveStorage_.UpdateMode == FileUpdateMode.Safe)
{
- Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_);
- updateFile = new ZipHelperStream(copyStream)
- {
- IsStreamOwner = true
- };
+ updateFile = archiveStorage_.MakeTemporaryCopy(baseStream_);
baseStream_.Dispose();
baseStream_ = null;
@@ -2835,21 +2905,21 @@ private void UpdateCommentOnly()
// Need to tidy up the archive storage interface and contract basically.
baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_);
- updateFile = new ZipHelperStream(baseStream_);
+ updateFile = baseStream_;
}
else
{
baseStream_.Dispose();
baseStream_ = null;
- updateFile = new ZipHelperStream(Name);
+ updateFile = new FileStream(Name, FileMode.Open, FileAccess.ReadWrite);
}
}
- using (updateFile)
+ try
{
long locatedCentralDirOffset =
- updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
- baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
+ ZipFormat.LocateBlockWithSignature(updateFile, ZipConstants.EndOfCentralDirectorySignature,
+ baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
if (locatedCentralDirOffset < 0)
{
throw new ZipException("Cannot find central directory");
@@ -2864,6 +2934,11 @@ private void UpdateCommentOnly()
updateFile.Write(rawComment, 0, rawComment.Length);
updateFile.SetLength(updateFile.Position);
}
+ finally
+ {
+ if(updateFile != baseStream_)
+ updateFile.Dispose();
+ }
if (archiveStorage_.UpdateMode == FileUpdateMode.Safe)
{
@@ -3025,11 +3100,9 @@ private void RunUpdates()
}
}
- byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_);
- using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_))
- {
- zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
- }
+ byte[] theComment = newComment_?.RawComment ?? _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment_);
+ ZipFormat.WriteEndOfCentralDirectory(workFile.baseStream_, updateCount_,
+ sizeEntries, centralDirOffset, theComment);
endOfStream = workFile.baseStream_.Position;
@@ -3291,7 +3364,7 @@ private void DisposeInternal(bool disposing)
if (!isDisposed_)
{
isDisposed_ = true;
- entries_ = new ZipEntry[0];
+ entries_ = Empty.Array();
if (IsStreamOwner && (baseStream_ != null))
{
@@ -3370,13 +3443,8 @@ private ulong ReadLEUlong()
#endregion Reading
// NOTE this returns the offset of the first byte after the signature.
- private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
- {
- using (ZipHelperStream les = new ZipHelperStream(baseStream_))
- {
- return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
- }
- }
+ private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
+ => ZipFormat.LocateBlockWithSignature(baseStream_, signature, endLocation, minimumBlockSize, maximumVariableData);
///
/// Search for and read the central directory of a zip file filling the entries array.
@@ -3394,7 +3462,7 @@ private void ReadEntries()
//
// The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
// This should be compatible with both SFX and ZIP files but has only been tested for Zip files
- // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then
+ // If a SFX file has the Zip data attached as a resource and there are other resources occurring later then
// this could be invalid.
// Could also speed this up by reading memory in larger blocks.
@@ -3425,7 +3493,7 @@ private void ReadEntries()
byte[] comment = new byte[commentSize];
StreamUtils.ReadFully(baseStream_, comment);
- comment_ = ZipStrings.ConvertToString(comment);
+ comment_ = _stringCodec.ZipArchiveCommentEncoding.GetString(comment);
}
else
{
@@ -3433,20 +3501,16 @@ private void ReadEntries()
}
bool isZip64 = false;
- bool requireZip64 = false;
-
+
// Check if zip64 header information is required.
- if ((thisDiskNumber == 0xffff) ||
- (startCentralDirDisk == 0xffff) ||
- (entriesForThisDisk == 0xffff) ||
- (entriesForWholeCentralDir == 0xffff) ||
- (centralDirSize == 0xffffffff) ||
- (offsetOfCentralDir == 0xffffffff))
- {
- requireZip64 = true;
- }
-
- // #357 - always check for the existance of the Zip64 central directory.
+ bool requireZip64 = thisDiskNumber == 0xffff ||
+ startCentralDirDisk == 0xffff ||
+ entriesForThisDisk == 0xffff ||
+ entriesForWholeCentralDir == 0xffff ||
+ centralDirSize == 0xffffffff ||
+ offsetOfCentralDir == 0xffffffff;
+
+ // #357 - always check for the existence of the Zip64 central directory.
// #403 - Take account of the fixed size of the locator when searching.
// Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature,
// rather than the data following the signature.
@@ -3480,7 +3544,7 @@ private void ReadEntries()
if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature)
{
- throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
+ throw new ZipException($"Invalid Zip64 Central directory signature at {offset64:X}");
}
// NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
@@ -3535,18 +3599,23 @@ private void ReadEntries()
int extraLen = ReadLEUshort();
int commentLen = ReadLEUshort();
- int diskStartNo = ReadLEUshort(); // Not currently used
- int internalAttributes = ReadLEUshort(); // Not currently used
+
+ // ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream
+ int diskStartNo = ReadLEUshort();
+ // ReSharper disable once UnusedVariable, Currently unused but needs to be read to offset the stream
+ int internalAttributes = ReadLEUshort();
uint externalAttributes = ReadLEUint();
long offset = ReadLEUint();
byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
+ var entryEncoding = _stringCodec.ZipInputEncoding(bitFlags);
StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
- string name = ZipStrings.ConvertToStringExt(bitFlags, buffer, nameLen);
+ string name = entryEncoding.GetString(buffer, 0, nameLen);
+ var unicode = entryEncoding.IsZipUnicode();
- var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method)
+ var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method, unicode)
{
Crc = crc & 0xffffffffL,
Size = size & 0xffffffffL,
@@ -3558,7 +3627,7 @@ private void ReadEntries()
ExternalFileAttributes = (int)externalAttributes
};
- if ((bitFlags & 8) == 0)
+ if (!entry.HasFlag(GeneralBitFlags.Descriptor))
{
entry.CryptoCheckValue = (byte)(crc >> 24);
}
@@ -3579,7 +3648,7 @@ private void ReadEntries()
if (commentLen > 0)
{
StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
- entry.Comment = ZipStrings.ConvertToStringExt(bitFlags, buffer, commentLen);
+ entry.Comment = entryEncoding.GetString(buffer, 0, commentLen);
}
entries_[i] = entry;
@@ -3601,9 +3670,15 @@ private void ReadEntries()
///
private long LocateEntry(ZipEntry entry)
{
- return TestLocalHeader(entry, HeaderTest.Extract);
+ return TestLocalHeader(entry, SkipLocalEntryTestsOnLocate ? HeaderTest.None : HeaderTest.Extract);
}
+ ///
+ /// Skip the verification of the local header when reading an archive entry. Set this to attempt to read the
+ /// entries even if the headers should indicate that doing so would fail or produce an unexpected output.
+ ///
+ public bool SkipLocalEntryTestsOnLocate { get; set; } = false;
+
private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
{
CryptoStream result = null;
@@ -3612,23 +3687,23 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
{
if (entry.Version >= ZipConstants.VERSION_AES)
{
- //
+ // Issue #471 - accept an empty string as a password, but reject null.
OnKeysRequired(entry.Name);
- if (HaveKeys == false)
+ if (rawPassword_ == null)
{
throw new ZipException("No password available for AES encrypted stream");
}
int saltLen = entry.AESSaltLen;
byte[] saltBytes = new byte[saltLen];
- int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen);
- if (saltIn != saltLen)
- throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
- //
+ int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, offset: 0, saltLen);
+
+ if (saltIn != saltLen) throw new ZipException($"AES Salt expected {saltLen} git {saltIn}");
+
byte[] pwdVerifyRead = new byte[2];
StreamUtils.ReadFully(baseStream, pwdVerifyRead);
int blockSize = entry.AESKeySize / 8; // bits to bytes
- var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false);
+ var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, writeMode: false);
byte[] pwdVerifyCalc = decryptor.PwdVerifier;
if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
throw new ZipException("Invalid password for AES");
@@ -3641,8 +3716,7 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
}
else
{
- if ((entry.Version < ZipConstants.VersionStrongEncryption)
- || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
+ if (entry.Version < ZipConstants.VersionStrongEncryption || !entry.HasFlag(GeneralBitFlags.StrongEncryption))
{
var classicManaged = new PkzipClassicManaged();
@@ -3667,31 +3741,29 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
{
- CryptoStream result = null;
- if ((entry.Version < ZipConstants.VersionStrongEncryption)
- || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
- {
- var classicManaged = new PkzipClassicManaged();
+ if (entry.Version >= ZipConstants.VersionStrongEncryption &&
+ entry.HasFlag(GeneralBitFlags.StrongEncryption)) return null;
- OnKeysRequired(entry.Name);
- if (HaveKeys == false)
- {
- throw new ZipException("No password available for encrypted stream");
- }
+ var classicManaged = new PkzipClassicManaged();
- // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
- // which doesnt do this.
- result = new CryptoStream(new UncompressedStream(baseStream),
- classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
+ OnKeysRequired(entry.Name);
+ if (HaveKeys == false)
+ {
+ throw new ZipException("No password available for encrypted stream");
+ }
- if ((entry.Crc < 0) || (entry.Flags & 8) != 0)
- {
- WriteEncryptionHeader(result, entry.DosTime << 16);
- }
- else
- {
- WriteEncryptionHeader(result, entry.Crc);
- }
+ // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
+ // which doesnt do this.
+ var result = new CryptoStream(new UncompressedStream(baseStream),
+ classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
+
+ if (entry.Crc < 0 || entry.HasFlag(GeneralBitFlags.Descriptor))
+ {
+ WriteEncryptionHeader(result, entry.DosTime << 16);
+ }
+ else
+ {
+ WriteEncryptionHeader(result, entry.Crc);
}
return result;
}
@@ -3709,10 +3781,12 @@ private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEn
private static void WriteEncryptionHeader(Stream stream, long crcValue)
{
byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
- var rnd = new Random();
- rnd.NextBytes(cryptBuffer);
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(cryptBuffer);
+ }
cryptBuffer[11] = (byte)(crcValue >> 24);
- stream.Write(cryptBuffer, 0, cryptBuffer.Length);
+ stream.Write(cryptBuffer, offset: 0, cryptBuffer.Length);
}
#endregion Internal routines
@@ -3721,7 +3795,7 @@ private static void WriteEncryptionHeader(Stream stream, long crcValue)
private bool isDisposed_;
private string name_;
- private string comment_;
+ private string comment_ = string.Empty;
private string rawPassword_;
private Stream baseStream_;
private bool isStreamOwner;
@@ -3729,6 +3803,7 @@ private static void WriteEncryptionHeader(Stream stream, long crcValue)
private ZipEntry[] entries_;
private byte[] key;
private bool isNewArchive_;
+ private StringCodec _stringCodec = ZipStrings.GetStringCodec();
// Default is dynamic which is not backwards compatible and can cause problems
// with XP's built in compression which cant read Zip64 archives.
@@ -3767,19 +3842,23 @@ private class ZipString
/// Initialise a with a string.
///
/// The textual string form.
- public ZipString(string comment)
+ ///
+ public ZipString(string comment, Encoding encoding)
{
comment_ = comment;
isSourceString_ = true;
+ _encoding = encoding;
}
///
/// Initialise a using a string in its binary 'raw' form.
///
///
- public ZipString(byte[] rawString)
+ ///
+ public ZipString(byte[] rawString, Encoding encoding)
{
rawComment_ = rawString;
+ _encoding = encoding;
}
#endregion Constructors
@@ -3788,10 +3867,7 @@ public ZipString(byte[] rawString)
/// Get a value indicating the original source of data for this instance.
/// True if the source was a string; false if the source was binary data.
///
- public bool IsSourceString
- {
- get { return isSourceString_; }
- }
+ public bool IsSourceString => isSourceString_;
///
/// Get the length of the comment when represented as raw bytes.
@@ -3836,7 +3912,7 @@ private void MakeTextAvailable()
{
if (comment_ == null)
{
- comment_ = ZipStrings.ConvertToString(rawComment_);
+ comment_ = _encoding.GetString(rawComment_);
}
}
@@ -3844,7 +3920,7 @@ private void MakeBytesAvailable()
{
if (rawComment_ == null)
{
- rawComment_ = ZipStrings.ConvertToArray(comment_);
+ rawComment_ = _encoding.GetBytes(comment_);
}
}
@@ -3853,7 +3929,7 @@ private void MakeBytesAvailable()
///
/// The to convert to a string.
/// The textual equivalent for the input value.
- static public implicit operator string(ZipString zipString)
+ public static implicit operator string(ZipString zipString)
{
zipString.MakeTextAvailable();
return zipString.comment_;
@@ -3864,6 +3940,7 @@ static public implicit operator string(ZipString zipString)
private string comment_;
private byte[] rawComment_;
private readonly bool isSourceString_;
+ private readonly Encoding _encoding;
#endregion Instance Fields
}
@@ -4003,12 +4080,12 @@ public override long Position
///
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
///
- /// The sum of offset and count is larger than the buffer length.
- /// Methods were called after the stream was closed.
- /// The stream does not support reading.
- /// buffer is null.
- /// An I/O error occurs.
- /// offset or count is negative.
+ /// The sum of offset and count is larger than the buffer length.
+ /// Methods were called after the stream was closed.
+ /// The stream does not support reading.
+ /// buffer is null.
+ /// An I/O error occurs.
+ /// offset or count is negative.
public override int Read(byte[] buffer, int offset, int count)
{
return 0;
@@ -4018,13 +4095,13 @@ public override int Read(byte[] buffer, int offset, int count)
/// Sets the position within the current stream.
///
/// A byte offset relative to the origin parameter.
- /// A value of type indicating the reference point used to obtain the new position.
+ /// A value of type indicating the reference point used to obtain the new position.
///
/// The new position within the current stream.
///
- /// An I/O error occurs.
- /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
- /// Methods were called after the stream was closed.
+ /// An I/O error occurs.
+ /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
+ /// Methods were called after the stream was closed.
public override long Seek(long offset, SeekOrigin origin)
{
return 0;
@@ -4034,9 +4111,9 @@ public override long Seek(long offset, SeekOrigin origin)
/// Sets the length of the current stream.
///
/// The desired length of the current stream in bytes.
- /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
- /// An I/O error occurs.
- /// Methods were called after the stream was closed.
+ /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
+ /// An I/O error occurs.
+ /// Methods were called after the stream was closed.
public override void SetLength(long value)
{
}
@@ -4047,12 +4124,12 @@ public override void SetLength(long value)
/// An array of bytes. This method copies count bytes from buffer to the current stream.
/// The zero-based byte offset in buffer at which to begin copying bytes to the current stream.
/// The number of bytes to be written to the current stream.
- /// An I/O error occurs.
- /// The stream does not support writing.
- /// Methods were called after the stream was closed.
- /// buffer is null.
- /// The sum of offset and count is greater than the buffer length.
- /// offset or count is negative.
+ /// An I/O error occurs.
+ /// The stream does not support writing.
+ /// Methods were called after the stream was closed.
+ /// buffer is null.
+ /// The sum of offset and count is greater than the buffer length.
+ /// offset or count is negative.
public override void Write(byte[] buffer, int offset, int count)
{
baseStream_.Write(buffer, offset, count);
@@ -4132,12 +4209,12 @@ public override int ReadByte()
///
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
///
- /// The sum of offset and count is larger than the buffer length.
- /// Methods were called after the stream was closed.
- /// The stream does not support reading.
- /// buffer is null.
- /// An I/O error occurs.
- /// offset or count is negative.
+ /// The sum of offset and count is larger than the buffer length.
+ /// Methods were called after the stream was closed.
+ /// The stream does not support reading.
+ /// buffer is null.
+ /// An I/O error occurs.
+ /// offset or count is negative.
public override int Read(byte[] buffer, int offset, int count)
{
lock (baseStream_)
@@ -4171,12 +4248,12 @@ public override int Read(byte[] buffer, int offset, int count)
/// An array of bytes. This method copies count bytes from buffer to the current stream.
/// The zero-based byte offset in buffer at which to begin copying bytes to the current stream.
/// The number of bytes to be written to the current stream.
- /// An I/O error occurs.
- /// The stream does not support writing.
- /// Methods were called after the stream was closed.
- /// buffer is null.
- /// The sum of offset and count is greater than the buffer length.
- /// offset or count is negative.
+ /// An I/O error occurs.
+ /// The stream does not support writing.
+ /// Methods were called after the stream was closed.
+ /// buffer is null.
+ /// The sum of offset and count is greater than the buffer length.
+ /// offset or count is negative.
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
@@ -4186,9 +4263,9 @@ public override void Write(byte[] buffer, int offset, int count)
/// When overridden in a derived class, sets the length of the current stream.
///
/// The desired length of the current stream in bytes.
- /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
- /// An I/O error occurs.
- /// Methods were called after the stream was closed.
+ /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
+ /// An I/O error occurs.
+ /// Methods were called after the stream was closed.
public override void SetLength(long value)
{
throw new NotSupportedException();
@@ -4198,13 +4275,13 @@ public override void SetLength(long value)
/// When overridden in a derived class, sets the position within the current stream.
///
/// A byte offset relative to the origin parameter.
- /// A value of type indicating the reference point used to obtain the new position.
+ /// A value of type indicating the reference point used to obtain the new position.
///
/// The new position within the current stream.
///
- /// An I/O error occurs.
- /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
- /// Methods were called after the stream was closed.
+ /// An I/O error occurs.
+ /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
+ /// Methods were called after the stream was closed.
public override long Seek(long offset, SeekOrigin origin)
{
long newPos = readPos_;
@@ -4229,7 +4306,7 @@ public override long Seek(long offset, SeekOrigin origin)
throw new ArgumentException("Negative position is invalid");
}
- if (newPos >= end_)
+ if (newPos > end_)
{
throw new IOException("Cannot seek past end");
}
@@ -4240,7 +4317,7 @@ public override long Seek(long offset, SeekOrigin origin)
///
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
///
- /// An I/O error occurs.
+ /// An I/O error occurs.
public override void Flush()
{
// Nothing to do.
@@ -4251,9 +4328,9 @@ public override void Flush()
///
///
/// The current position within the stream.
- /// An I/O error occurs.
- /// The stream does not support seeking.
- /// Methods were called after the stream was closed.
+ /// An I/O error occurs.
+ /// The stream does not support seeking.
+ /// Methods were called after the stream was closed.
public override long Position
{
get { return readPos_ - start_; }
@@ -4266,7 +4343,7 @@ public override long Position
throw new ArgumentException("Negative position is invalid");
}
- if (newPos >= end_)
+ if (newPos > end_)
{
throw new InvalidOperationException("Cannot seek past end");
}
@@ -4279,8 +4356,8 @@ public override long Position
///
///
/// A long value representing the length of the stream in bytes.
- /// A class derived from Stream does not support seeking.
- /// Methods were called after the stream was closed.
+ /// A class derived from Stream does not support seeking.
+ /// Methods were called after the stream was closed.
public override long Length
{
get { return length_; }
@@ -4380,7 +4457,7 @@ public interface IDynamicDataSource
public class StaticDiskDataSource : IStaticDataSource
{
///
- /// Initialise a new instnace of
+ /// Initialise a new instance of
///
/// The name of the file to obtain data from.
public StaticDiskDataSource(string fileName)
@@ -4393,7 +4470,7 @@ public StaticDiskDataSource(string fileName)
///
/// Get a providing data.
///
- /// Returns a provising data.
+ /// Returns a providing data.
public Stream GetSource()
{
return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read);
@@ -4604,18 +4681,8 @@ public DiskArchiveStorage(ZipFile file)
/// Returns the temporary output stream.
public override Stream GetTemporaryOutput()
{
- if (temporaryName_ != null)
- {
- temporaryName_ = GetTempFileName(temporaryName_, true);
- temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
- }
- else
- {
- // Determine where to place files based on internal strategy.
- // Currently this is always done in system temp directory.
- temporaryName_ = Path.GetTempFileName();
- temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
- }
+ temporaryName_ = PathUtils.GetTempFileName(temporaryName_);
+ temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
return temporaryStream_;
}
@@ -4634,7 +4701,7 @@ public override Stream ConvertTemporaryToFinal()
Stream result = null;
- string moveTempName = GetTempFileName(fileName_, false);
+ string moveTempName = PathUtils.GetTempFileName(fileName_);
bool newFileCreated = false;
try
@@ -4673,7 +4740,7 @@ public override Stream MakeTemporaryCopy(Stream stream)
{
stream.Dispose();
- temporaryName_ = GetTempFileName(fileName_, true);
+ temporaryName_ = PathUtils.GetTempFileName(fileName_);
File.Copy(fileName_, temporaryName_, true);
temporaryStream_ = new FileStream(temporaryName_,
@@ -4723,54 +4790,6 @@ public override void Dispose()
#endregion IArchiveStorage Members
- #region Internal routines
-
- private static string GetTempFileName(string original, bool makeTempFile)
- {
- string result = null;
-
- if (original == null)
- {
- result = Path.GetTempFileName();
- }
- else
- {
- int counter = 0;
- int suffixSeed = DateTime.Now.Second;
-
- while (result == null)
- {
- counter += 1;
- string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter);
- if (!File.Exists(newName))
- {
- if (makeTempFile)
- {
- try
- {
- // Try and create the file.
- using (FileStream stream = File.Create(newName))
- {
- }
- result = newName;
- }
- catch
- {
- suffixSeed = DateTime.Now.Second;
- }
- }
- else
- {
- result = newName;
- }
- }
- }
- }
- return result;
- }
-
- #endregion Internal routines
-
#region Instance Fields
private Stream temporaryStream_;
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs
new file mode 100644
index 000000000..cf78ef54f
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs
@@ -0,0 +1,598 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Core;
+
+namespace ICSharpCode.SharpZipLib.Zip
+{
+ ///
+ /// Holds data pertinent to a data descriptor.
+ ///
+ public class DescriptorData
+ {
+ private long _crc;
+
+ ///
+ /// Get /set the compressed size of data.
+ ///
+ public long CompressedSize { get; set; }
+
+ ///
+ /// Get / set the uncompressed size of data
+ ///
+ public long Size { get; set; }
+
+ ///
+ /// Get /set the crc value.
+ ///
+ public long Crc
+ {
+ get => _crc;
+ set => _crc = (value & 0xffffffff);
+ }
+ }
+
+ internal struct EntryPatchData
+ {
+ public long SizePatchOffset { get; set; }
+
+ public long CrcPatchOffset { get; set; }
+ }
+
+ ///
+ /// This class assists with writing/reading from Zip files.
+ ///
+ internal static class ZipFormat
+ {
+ // Write the local file header
+ // TODO: ZipFormat.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage
+ internal static int WriteLocalHeader(Stream stream, ZipEntry entry, out EntryPatchData patchData,
+ bool headerInfoAvailable, bool patchEntryHeader, long streamOffset, StringCodec stringCodec)
+ {
+ patchData = new EntryPatchData();
+
+ stream.WriteLEInt(ZipConstants.LocalHeaderSignature);
+ stream.WriteLEShort(entry.Version);
+ stream.WriteLEShort(entry.Flags);
+ stream.WriteLEShort((byte)entry.CompressionMethodForHeader);
+ stream.WriteLEInt((int)entry.DosTime);
+
+ if (headerInfoAvailable)
+ {
+ stream.WriteLEInt((int)entry.Crc);
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ stream.WriteLEInt(-1);
+ stream.WriteLEInt(-1);
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.CompressedSize + entry.EncryptionOverheadSize);
+ stream.WriteLEInt((int)entry.Size);
+ }
+ }
+ else
+ {
+ if (patchEntryHeader)
+ patchData.CrcPatchOffset = streamOffset + stream.Position;
+
+ stream.WriteLEInt(0); // Crc
+
+ if (patchEntryHeader)
+ patchData.SizePatchOffset = streamOffset + stream.Position;
+
+ // For local header both sizes appear in Zip64 Extended Information
+ if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
+ {
+ stream.WriteLEInt(-1);
+ stream.WriteLEInt(-1);
+ }
+ else
+ {
+ stream.WriteLEInt(0); // Compressed size
+ stream.WriteLEInt(0); // Uncompressed size
+ }
+ }
+
+ byte[] name = stringCodec.ZipOutputEncoding.GetBytes(entry.Name);
+
+ if (name.Length > 0xFFFF)
+ {
+ throw new ZipException("Entry name too long.");
+ }
+
+ var ed = new ZipExtraData(entry.ExtraData);
+
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ ed.StartNewEntry();
+ if (headerInfoAvailable)
+ {
+ ed.AddLeLong(entry.Size);
+ ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize);
+ }
+ else
+ {
+ // If the sizes are stored in the descriptor, the local Zip64 sizes should be 0
+ ed.AddLeLong(0);
+ ed.AddLeLong(0);
+ }
+ ed.AddNewEntry(1);
+
+ if (!ed.Find(1))
+ {
+ throw new ZipException("Internal error cant find extra data");
+ }
+
+ patchData.SizePatchOffset = ed.CurrentReadIndex;
+ }
+ else
+ {
+ ed.Delete(1);
+ }
+
+ if (entry.AESKeySize > 0)
+ {
+ AddExtraDataAES(entry, ed);
+ }
+ byte[] extra = ed.GetEntryData();
+
+ stream.WriteLEShort(name.Length);
+ stream.WriteLEShort(extra.Length);
+
+ if (name.Length > 0)
+ {
+ stream.Write(name, 0, name.Length);
+ }
+
+ if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
+ {
+ patchData.SizePatchOffset += streamOffset + stream.Position;
+ }
+
+ if (extra.Length > 0)
+ {
+ stream.Write(extra, 0, extra.Length);
+ }
+
+ return ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
+ }
+
+ ///
+ /// Locates a block with the desired .
+ ///
+ ///
+ /// The signature to find.
+ /// Location, marking the end of block.
+ /// Minimum size of the block.
+ /// The maximum variable data.
+ /// Returns the offset of the first byte after the signature; -1 if not found
+ internal static long LocateBlockWithSignature(Stream stream, int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
+ {
+ long pos = endLocation - minimumBlockSize;
+ if (pos < 0)
+ {
+ return -1;
+ }
+
+ long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
+
+ // TODO: This loop could be optimized for speed.
+ do
+ {
+ if (pos < giveUpMarker)
+ {
+ return -1;
+ }
+ stream.Seek(pos--, SeekOrigin.Begin);
+ } while (stream.ReadLEInt() != signature);
+
+ return stream.Position;
+ }
+
+ ///
+ public static async Task WriteZip64EndOfCentralDirectoryAsync(Stream stream, long noOfEntries,
+ long sizeEntries, long centralDirOffset, CancellationToken cancellationToken)
+ {
+ await stream.WriteProcToStreamAsync(s => WriteZip64EndOfCentralDirectory(s, noOfEntries, sizeEntries, centralDirOffset), cancellationToken);
+ }
+
+ ///
+ /// Write Zip64 end of central directory records (File header and locator).
+ ///
+ ///
+ /// The number of entries in the central directory.
+ /// The size of entries in the central directory.
+ /// The offset of the central directory.
+ internal static void WriteZip64EndOfCentralDirectory(Stream stream, long noOfEntries, long sizeEntries, long centralDirOffset)
+ {
+ long centralSignatureOffset = centralDirOffset + sizeEntries;
+ stream.WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature);
+ stream.WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12)
+ stream.WriteLEShort(ZipConstants.VersionMadeBy); // Version made by
+ stream.WriteLEShort(ZipConstants.VersionZip64); // Version to extract
+ stream.WriteLEInt(0); // Number of this disk
+ stream.WriteLEInt(0); // number of the disk with the start of the central directory
+ stream.WriteLELong(noOfEntries); // No of entries on this disk
+ stream.WriteLELong(noOfEntries); // Total No of entries in central directory
+ stream.WriteLELong(sizeEntries); // Size of the central directory
+ stream.WriteLELong(centralDirOffset); // offset of start of central directory
+ // zip64 extensible data sector not catered for here (variable size)
+
+ // Write the Zip64 end of central directory locator
+ stream.WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature);
+
+ // no of the disk with the start of the zip64 end of central directory
+ stream.WriteLEInt(0);
+
+ // relative offset of the zip64 end of central directory record
+ stream.WriteLELong(centralSignatureOffset);
+
+ // total number of disks
+ stream.WriteLEInt(1);
+ }
+
+ ///
+ public static async Task WriteEndOfCentralDirectoryAsync(Stream stream, long noOfEntries, long sizeEntries,
+ long start, byte[] comment, CancellationToken cancellationToken)
+ => await stream.WriteProcToStreamAsync(s
+ => WriteEndOfCentralDirectory(s, noOfEntries, sizeEntries, start, comment), cancellationToken);
+
+ ///
+ /// Write the required records to end the central directory.
+ ///
+ ///
+ /// The number of entries in the directory.
+ /// The size of the entries in the directory.
+ /// The start of the central directory.
+ /// The archive comment. (This can be null).
+
+ internal static void WriteEndOfCentralDirectory(Stream stream, long noOfEntries, long sizeEntries, long start, byte[] comment)
+ {
+ if (noOfEntries >= 0xffff ||
+ start >= 0xffffffff ||
+ sizeEntries >= 0xffffffff)
+ {
+ WriteZip64EndOfCentralDirectory(stream, noOfEntries, sizeEntries, start);
+ }
+
+ stream.WriteLEInt(ZipConstants.EndOfCentralDirectorySignature);
+
+ // TODO: ZipFile Multi disk handling not done
+ stream.WriteLEShort(0); // number of this disk
+ stream.WriteLEShort(0); // no of disk with start of central dir
+
+ // Number of entries
+ if (noOfEntries >= 0xffff)
+ {
+ stream.WriteLEUshort(0xffff); // Zip64 marker
+ stream.WriteLEUshort(0xffff);
+ }
+ else
+ {
+ stream.WriteLEShort((short)noOfEntries); // entries in central dir for this disk
+ stream.WriteLEShort((short)noOfEntries); // total entries in central directory
+ }
+
+ // Size of the central directory
+ if (sizeEntries >= 0xffffffff)
+ {
+ stream.WriteLEUint(0xffffffff); // Zip64 marker
+ }
+ else
+ {
+ stream.WriteLEInt((int)sizeEntries);
+ }
+
+ // offset of start of central directory
+ if (start >= 0xffffffff)
+ {
+ stream.WriteLEUint(0xffffffff); // Zip64 marker
+ }
+ else
+ {
+ stream.WriteLEInt((int)start);
+ }
+
+ var commentLength = comment?.Length ?? 0;
+
+ if (commentLength > 0xffff)
+ {
+ throw new ZipException($"Comment length ({commentLength}) is larger than 64K");
+ }
+
+ stream.WriteLEShort(commentLength);
+
+ if (commentLength > 0)
+ {
+ stream.Write(comment, 0, commentLength);
+ }
+ }
+
+
+
+ ///
+ /// Write a data descriptor.
+ ///
+ ///
+ /// The entry to write a descriptor for.
+ /// Returns the number of descriptor bytes written.
+ internal static int WriteDataDescriptor(Stream stream, ZipEntry entry)
+ {
+ if (entry == null)
+ {
+ throw new ArgumentNullException(nameof(entry));
+ }
+
+ int result = 0;
+
+ // Add data descriptor if flagged as required
+ if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
+ {
+ // The signature is not PKZIP originally but is now described as optional
+ // in the PKZIP Appnote documenting the format.
+ stream.WriteLEInt(ZipConstants.DataDescriptorSignature);
+ stream.WriteLEInt(unchecked((int)(entry.Crc)));
+
+ result += 8;
+
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ stream.WriteLELong(entry.CompressedSize);
+ stream.WriteLELong(entry.Size);
+ result += 16;
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.CompressedSize);
+ stream.WriteLEInt((int)entry.Size);
+ result += 8;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Read data descriptor at the end of compressed data.
+ ///
+ ///
+ /// if set to true [zip64].
+ /// The data to fill in.
+ /// Returns the number of bytes read in the descriptor.
+ internal static void ReadDataDescriptor(Stream stream, bool zip64, DescriptorData data)
+ {
+ int intValue = stream.ReadLEInt();
+
+ // In theory this may not be a descriptor according to PKZIP appnote.
+ // In practice its always there.
+ if (intValue != ZipConstants.DataDescriptorSignature)
+ {
+ throw new ZipException("Data descriptor signature not found");
+ }
+
+ data.Crc = stream.ReadLEInt();
+
+ if (zip64)
+ {
+ data.CompressedSize = stream.ReadLELong();
+ data.Size = stream.ReadLELong();
+ }
+ else
+ {
+ data.CompressedSize = stream.ReadLEInt();
+ data.Size = stream.ReadLEInt();
+ }
+ }
+
+ internal static int WriteEndEntry(Stream stream, ZipEntry entry, StringCodec stringCodec)
+ {
+ stream.WriteLEInt(ZipConstants.CentralHeaderSignature);
+ stream.WriteLEShort((entry.HostSystem << 8) | entry.VersionMadeBy);
+ stream.WriteLEShort(entry.Version);
+ stream.WriteLEShort(entry.Flags);
+ stream.WriteLEShort((short)entry.CompressionMethodForHeader);
+ stream.WriteLEInt((int)entry.DosTime);
+ stream.WriteLEInt((int)entry.Crc);
+
+ if (entry.IsZip64Forced() ||
+ (entry.CompressedSize >= uint.MaxValue))
+ {
+ stream.WriteLEInt(-1);
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.CompressedSize);
+ }
+
+ if (entry.IsZip64Forced() ||
+ (entry.Size >= uint.MaxValue))
+ {
+ stream.WriteLEInt(-1);
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.Size);
+ }
+
+ byte[] name = stringCodec.ZipOutputEncoding.GetBytes(entry.Name);
+
+ if (name.Length > 0xffff)
+ {
+ throw new ZipException("Name too long.");
+ }
+
+ var ed = new ZipExtraData(entry.ExtraData);
+
+ if (entry.CentralHeaderRequiresZip64)
+ {
+ ed.StartNewEntry();
+ if (entry.IsZip64Forced() ||
+ (entry.Size >= 0xffffffff))
+ {
+ ed.AddLeLong(entry.Size);
+ }
+
+ if (entry.IsZip64Forced() ||
+ (entry.CompressedSize >= 0xffffffff))
+ {
+ ed.AddLeLong(entry.CompressedSize);
+ }
+
+ if (entry.Offset >= 0xffffffff)
+ {
+ ed.AddLeLong(entry.Offset);
+ }
+
+ ed.AddNewEntry(1);
+ }
+ else
+ {
+ ed.Delete(1);
+ }
+
+ if (entry.AESKeySize > 0)
+ {
+ AddExtraDataAES(entry, ed);
+ }
+ byte[] extra = ed.GetEntryData();
+
+ byte[] entryComment = !(entry.Comment is null)
+ ? stringCodec.ZipOutputEncoding.GetBytes(entry.Comment)
+ : Empty.Array();
+
+ if (entryComment.Length > 0xffff)
+ {
+ throw new ZipException("Comment too long.");
+ }
+
+ stream.WriteLEShort(name.Length);
+ stream.WriteLEShort(extra.Length);
+ stream.WriteLEShort(entryComment.Length);
+ stream.WriteLEShort(0); // disk number
+ stream.WriteLEShort(0); // internal file attributes
+ // external file attributes
+
+ if (entry.ExternalFileAttributes != -1)
+ {
+ stream.WriteLEInt(entry.ExternalFileAttributes);
+ }
+ else
+ {
+ if (entry.IsDirectory)
+ { // mark entry as directory (from nikolam.AT.perfectinfo.com)
+ stream.WriteLEInt(16);
+ }
+ else
+ {
+ stream.WriteLEInt(0);
+ }
+ }
+
+ if (entry.Offset >= uint.MaxValue)
+ {
+ stream.WriteLEInt(-1);
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.Offset);
+ }
+
+ if (name.Length > 0)
+ {
+ stream.Write(name, 0, name.Length);
+ }
+
+ if (extra.Length > 0)
+ {
+ stream.Write(extra, 0, extra.Length);
+ }
+
+ if (entryComment.Length > 0)
+ {
+ stream.Write(entryComment, 0, entryComment.Length);
+ }
+
+ return ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
+ }
+
+ internal static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
+ {
+ // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
+ const int VENDOR_VERSION = 2;
+ // Vendor ID is the two ASCII characters "AE".
+ const int VENDOR_ID = 0x4541; //not 6965;
+ extraData.StartNewEntry();
+ // Pack AES extra data field see http://www.winzip.com/aes_info.htm
+ //extraData.AddLeShort(7); // Data size (currently 7)
+ extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2
+ extraData.AddLeShort(VENDOR_ID); // "AE"
+ extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256
+ extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
+ extraData.AddNewEntry(0x9901);
+ }
+
+ internal static async Task PatchLocalHeaderAsync(Stream stream, ZipEntry entry,
+ EntryPatchData patchData, CancellationToken ct)
+ {
+ var initialPos = stream.Position;
+
+ // Update CRC
+ stream.Seek(patchData.CrcPatchOffset, SeekOrigin.Begin);
+ await stream.WriteLEIntAsync((int)entry.Crc, ct);
+
+ // Update Sizes
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ if (patchData.SizePatchOffset == -1)
+ {
+ throw new ZipException("Entry requires zip64 but this has been turned off");
+ }
+ // Seek to the Zip64 Extra Data
+ stream.Seek(patchData.SizePatchOffset, SeekOrigin.Begin);
+
+ // Note: The order of the size fields is reversed when compared to the local header!
+ await stream.WriteLELongAsync(entry.Size, ct);
+ await stream.WriteLELongAsync(entry.CompressedSize, ct);
+ }
+ else
+ {
+ await stream.WriteLEIntAsync((int)entry.CompressedSize, ct);
+ await stream.WriteLEIntAsync((int)entry.Size, ct);
+ }
+
+ stream.Seek(initialPos, SeekOrigin.Begin);
+ }
+
+ internal static void PatchLocalHeaderSync(Stream stream, ZipEntry entry,
+ EntryPatchData patchData)
+ {
+ var initialPos = stream.Position;
+ stream.Seek(patchData.CrcPatchOffset, SeekOrigin.Begin);
+ stream.WriteLEInt((int)entry.Crc);
+
+ if (entry.LocalHeaderRequiresZip64)
+ {
+ if (patchData.SizePatchOffset == -1)
+ {
+ throw new ZipException("Entry requires zip64 but this has been turned off");
+ }
+
+ // Seek to the Zip64 Extra Data
+ stream.Seek(patchData.SizePatchOffset, SeekOrigin.Begin);
+
+ // Note: The order of the size fields is reversed when compared to the local header!
+ stream.WriteLELong(entry.Size);
+ stream.WriteLELong(entry.CompressedSize);
+ }
+ else
+ {
+ stream.WriteLEInt((int)entry.CompressedSize);
+ stream.WriteLEInt((int)entry.Size);
+ }
+
+ stream.Seek(initialPos, SeekOrigin.Begin);
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
index 03567b3bf..e69de29bb 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs
@@ -1,629 +0,0 @@
-using System;
-using System.IO;
-
-namespace ICSharpCode.SharpZipLib.Zip
-{
- ///
- /// Holds data pertinent to a data descriptor.
- ///
- public class DescriptorData
- {
- ///
- /// Get /set the compressed size of data.
- ///
- public long CompressedSize
- {
- get { return compressedSize; }
- set { compressedSize = value; }
- }
-
- ///
- /// Get / set the uncompressed size of data
- ///
- public long Size
- {
- get { return size; }
- set { size = value; }
- }
-
- ///
- /// Get /set the crc value.
- ///
- public long Crc
- {
- get { return crc; }
- set { crc = (value & 0xffffffff); }
- }
-
- #region Instance Fields
-
- private long size;
- private long compressedSize;
- private long crc;
-
- #endregion Instance Fields
- }
-
- internal class EntryPatchData
- {
- public long SizePatchOffset
- {
- get { return sizePatchOffset_; }
- set { sizePatchOffset_ = value; }
- }
-
- public long CrcPatchOffset
- {
- get { return crcPatchOffset_; }
- set { crcPatchOffset_ = value; }
- }
-
- #region Instance Fields
-
- private long sizePatchOffset_;
- private long crcPatchOffset_;
-
- #endregion Instance Fields
- }
-
- ///
- /// This class assists with writing/reading from Zip files.
- ///
- internal class ZipHelperStream : Stream
- {
- #region Constructors
-
- ///
- /// Initialise an instance of this class.
- ///
- /// The name of the file to open.
- public ZipHelperStream(string name)
- {
- stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite);
- isOwner_ = true;
- }
-
- ///
- /// Initialise a new instance of .
- ///
- /// The stream to use.
- public ZipHelperStream(Stream stream)
- {
- stream_ = stream;
- }
-
- #endregion Constructors
-
- ///
- /// Get / set a value indicating wether the the underlying stream is owned or not.
- ///
- /// If the stream is owned it is closed when this instance is closed.
- public bool IsStreamOwner
- {
- get { return isOwner_; }
- set { isOwner_ = value; }
- }
-
- #region Base Stream Methods
-
- public override bool CanRead
- {
- get { return stream_.CanRead; }
- }
-
- public override bool CanSeek
- {
- get { return stream_.CanSeek; }
- }
-
- public override bool CanTimeout
- {
- get { return stream_.CanTimeout; }
- }
-
- public override long Length
- {
- get { return stream_.Length; }
- }
-
- public override long Position
- {
- get { return stream_.Position; }
- set { stream_.Position = value; }
- }
-
- public override bool CanWrite
- {
- get { return stream_.CanWrite; }
- }
-
- public override void Flush()
- {
- stream_.Flush();
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- return stream_.Seek(offset, origin);
- }
-
- public override void SetLength(long value)
- {
- stream_.SetLength(value);
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- return stream_.Read(buffer, offset, count);
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- stream_.Write(buffer, offset, count);
- }
-
- ///
- /// Close the stream.
- ///
- ///
- /// The underlying stream is closed only if is true.
- ///
- protected override void Dispose(bool disposing)
- {
- Stream toClose = stream_;
- stream_ = null;
- if (isOwner_ && (toClose != null))
- {
- isOwner_ = false;
- toClose.Dispose();
- }
- }
-
- #endregion Base Stream Methods
-
- // Write the local file header
- // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage
- private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
- {
- CompressionMethod method = entry.CompressionMethod;
- bool headerInfoAvailable = true; // How to get this?
- bool patchEntryHeader = false;
-
- WriteLEInt(ZipConstants.LocalHeaderSignature);
-
- WriteLEShort(entry.Version);
- WriteLEShort(entry.Flags);
- WriteLEShort((byte)method);
- WriteLEInt((int)entry.DosTime);
-
- if (headerInfoAvailable == true)
- {
- WriteLEInt((int)entry.Crc);
- if (entry.LocalHeaderRequiresZip64)
- {
- WriteLEInt(-1);
- WriteLEInt(-1);
- }
- else
- {
- WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
- WriteLEInt((int)entry.Size);
- }
- }
- else
- {
- if (patchData != null)
- {
- patchData.CrcPatchOffset = stream_.Position;
- }
- WriteLEInt(0); // Crc
-
- if (patchData != null)
- {
- patchData.SizePatchOffset = stream_.Position;
- }
-
- // For local header both sizes appear in Zip64 Extended Information
- if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
- {
- WriteLEInt(-1);
- WriteLEInt(-1);
- }
- else
- {
- WriteLEInt(0); // Compressed size
- WriteLEInt(0); // Uncompressed size
- }
- }
-
- byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
-
- if (name.Length > 0xFFFF)
- {
- throw new ZipException("Entry name too long.");
- }
-
- var ed = new ZipExtraData(entry.ExtraData);
-
- if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader))
- {
- ed.StartNewEntry();
- if (headerInfoAvailable)
- {
- ed.AddLeLong(entry.Size);
- ed.AddLeLong(entry.CompressedSize);
- }
- else
- {
- ed.AddLeLong(-1);
- ed.AddLeLong(-1);
- }
- ed.AddNewEntry(1);
-
- if (!ed.Find(1))
- {
- throw new ZipException("Internal error cant find extra data");
- }
-
- if (patchData != null)
- {
- patchData.SizePatchOffset = ed.CurrentReadIndex;
- }
- }
- else
- {
- ed.Delete(1);
- }
-
- byte[] extra = ed.GetEntryData();
-
- WriteLEShort(name.Length);
- WriteLEShort(extra.Length);
-
- if (name.Length > 0)
- {
- stream_.Write(name, 0, name.Length);
- }
-
- if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
- {
- patchData.SizePatchOffset += stream_.Position;
- }
-
- if (extra.Length > 0)
- {
- stream_.Write(extra, 0, extra.Length);
- }
- }
-
- ///
- /// Locates a block with the desired .
- ///
- /// The signature to find.
- /// Location, marking the end of block.
- /// Minimum size of the block.
- /// The maximum variable data.
- /// Eeturns the offset of the first byte after the signature; -1 if not found
- public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
- {
- long pos = endLocation - minimumBlockSize;
- if (pos < 0)
- {
- return -1;
- }
-
- long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
-
- // TODO: This loop could be optimised for speed.
- do
- {
- if (pos < giveUpMarker)
- {
- return -1;
- }
- Seek(pos--, SeekOrigin.Begin);
- } while (ReadLEInt() != signature);
-
- return Position;
- }
-
- ///
- /// Write Zip64 end of central directory records (File header and locator).
- ///
- /// The number of entries in the central directory.
- /// The size of entries in the central directory.
- /// The offset of the dentral directory.
- public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset)
- {
- long centralSignatureOffset = centralDirOffset + sizeEntries;
- WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature);
- WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12)
- WriteLEShort(ZipConstants.VersionMadeBy); // Version made by
- WriteLEShort(ZipConstants.VersionZip64); // Version to extract
- WriteLEInt(0); // Number of this disk
- WriteLEInt(0); // number of the disk with the start of the central directory
- WriteLELong(noOfEntries); // No of entries on this disk
- WriteLELong(noOfEntries); // Total No of entries in central directory
- WriteLELong(sizeEntries); // Size of the central directory
- WriteLELong(centralDirOffset); // offset of start of central directory
- // zip64 extensible data sector not catered for here (variable size)
-
- // Write the Zip64 end of central directory locator
- WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature);
-
- // no of the disk with the start of the zip64 end of central directory
- WriteLEInt(0);
-
- // relative offset of the zip64 end of central directory record
- WriteLELong(centralSignatureOffset);
-
- // total number of disks
- WriteLEInt(1);
- }
-
- ///
- /// Write the required records to end the central directory.
- ///
- /// The number of entries in the directory.
- /// The size of the entries in the directory.
- /// The start of the central directory.
- /// The archive comment. (This can be null).
- public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries,
- long startOfCentralDirectory, byte[] comment)
- {
- if ((noOfEntries >= 0xffff) ||
- (startOfCentralDirectory >= 0xffffffff) ||
- (sizeEntries >= 0xffffffff))
- {
- WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory);
- }
-
- WriteLEInt(ZipConstants.EndOfCentralDirectorySignature);
-
- // TODO: ZipFile Multi disk handling not done
- WriteLEShort(0); // number of this disk
- WriteLEShort(0); // no of disk with start of central dir
-
- // Number of entries
- if (noOfEntries >= 0xffff)
- {
- WriteLEUshort(0xffff); // Zip64 marker
- WriteLEUshort(0xffff);
- }
- else
- {
- WriteLEShort((short)noOfEntries); // entries in central dir for this disk
- WriteLEShort((short)noOfEntries); // total entries in central directory
- }
-
- // Size of the central directory
- if (sizeEntries >= 0xffffffff)
- {
- WriteLEUint(0xffffffff); // Zip64 marker
- }
- else
- {
- WriteLEInt((int)sizeEntries);
- }
-
- // offset of start of central directory
- if (startOfCentralDirectory >= 0xffffffff)
- {
- WriteLEUint(0xffffffff); // Zip64 marker
- }
- else
- {
- WriteLEInt((int)startOfCentralDirectory);
- }
-
- int commentLength = (comment != null) ? comment.Length : 0;
-
- if (commentLength > 0xffff)
- {
- throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength));
- }
-
- WriteLEShort(commentLength);
-
- if (commentLength > 0)
- {
- Write(comment, 0, comment.Length);
- }
- }
-
- #region LE value reading/writing
-
- ///
- /// Read an unsigned short in little endian byte order.
- ///
- /// Returns the value read.
- ///
- /// An i/o error occurs.
- ///
- ///
- /// The file ends prematurely
- ///
- public int ReadLEShort()
- {
- int byteValue1 = stream_.ReadByte();
-
- if (byteValue1 < 0)
- {
- throw new EndOfStreamException();
- }
-
- int byteValue2 = stream_.ReadByte();
- if (byteValue2 < 0)
- {
- throw new EndOfStreamException();
- }
-
- return byteValue1 | (byteValue2 << 8);
- }
-
- ///
- /// Read an int in little endian byte order.
- ///
- /// Returns the value read.
- ///
- /// An i/o error occurs.
- ///
- ///
- /// The file ends prematurely
- ///
- public int ReadLEInt()
- {
- return ReadLEShort() | (ReadLEShort() << 16);
- }
-
- ///
- /// Read a long in little endian byte order.
- ///
- /// The value read.
- public long ReadLELong()
- {
- return (uint)ReadLEInt() | ((long)ReadLEInt() << 32);
- }
-
- ///
- /// Write an unsigned short in little endian byte order.
- ///
- /// The value to write.
- public void WriteLEShort(int value)
- {
- stream_.WriteByte((byte)(value & 0xff));
- stream_.WriteByte((byte)((value >> 8) & 0xff));
- }
-
- ///
- /// Write a ushort in little endian byte order.
- ///
- /// The value to write.
- public void WriteLEUshort(ushort value)
- {
- stream_.WriteByte((byte)(value & 0xff));
- stream_.WriteByte((byte)(value >> 8));
- }
-
- ///
- /// Write an int in little endian byte order.
- ///
- /// The value to write.
- public void WriteLEInt(int value)
- {
- WriteLEShort(value);
- WriteLEShort(value >> 16);
- }
-
- ///
- /// Write a uint in little endian byte order.
- ///
- /// The value to write.
- public void WriteLEUint(uint value)
- {
- WriteLEUshort((ushort)(value & 0xffff));
- WriteLEUshort((ushort)(value >> 16));
- }
-
- ///
- /// Write a long in little endian byte order.
- ///
- /// The value to write.
- public void WriteLELong(long value)
- {
- WriteLEInt((int)value);
- WriteLEInt((int)(value >> 32));
- }
-
- ///
- /// Write a ulong in little endian byte order.
- ///
- /// The value to write.
- public void WriteLEUlong(ulong value)
- {
- WriteLEUint((uint)(value & 0xffffffff));
- WriteLEUint((uint)(value >> 32));
- }
-
- #endregion LE value reading/writing
-
- ///
- /// Write a data descriptor.
- ///
- /// The entry to write a descriptor for.
- /// Returns the number of descriptor bytes written.
- public int WriteDataDescriptor(ZipEntry entry)
- {
- if (entry == null)
- {
- throw new ArgumentNullException(nameof(entry));
- }
-
- int result = 0;
-
- // Add data descriptor if flagged as required
- if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
- {
- // The signature is not PKZIP originally but is now described as optional
- // in the PKZIP Appnote documenting trhe format.
- WriteLEInt(ZipConstants.DataDescriptorSignature);
- WriteLEInt(unchecked((int)(entry.Crc)));
-
- result += 8;
-
- if (entry.LocalHeaderRequiresZip64)
- {
- WriteLELong(entry.CompressedSize);
- WriteLELong(entry.Size);
- result += 16;
- }
- else
- {
- WriteLEInt((int)entry.CompressedSize);
- WriteLEInt((int)entry.Size);
- result += 8;
- }
- }
-
- return result;
- }
-
- ///
- /// Read data descriptor at the end of compressed data.
- ///
- /// if set to true [zip64].
- /// The data to fill in.
- /// Returns the number of bytes read in the descriptor.
- public void ReadDataDescriptor(bool zip64, DescriptorData data)
- {
- int intValue = ReadLEInt();
-
- // In theory this may not be a descriptor according to PKZIP appnote.
- // In practise its always there.
- if (intValue != ZipConstants.DataDescriptorSignature)
- {
- throw new ZipException("Data descriptor signature not found");
- }
-
- data.Crc = ReadLEInt();
-
- if (zip64)
- {
- data.CompressedSize = ReadLELong();
- data.Size = ReadLELong();
- }
- else
- {
- data.CompressedSize = ReadLEInt();
- data.Size = ReadLEInt();
- }
- }
-
- #region Instance Fields
-
- private bool isOwner_;
- private Stream stream_;
-
- #endregion Instance Fields
- }
-}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
index c6ac79faf..bde176788 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
@@ -3,6 +3,7 @@
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
+using System.Diagnostics;
using System.IO;
namespace ICSharpCode.SharpZipLib.Zip
@@ -73,9 +74,10 @@ public class ZipInputStream : InflaterInputStream
private ZipEntry entry;
private long size;
- private CompressionMethod method;
private int flags;
private string password;
+ private readonly StringCodec _stringCodec = ZipStrings.GetStringCodec();
+ private ZipAESTransform cryptoTransform;
#endregion Instance Fields
@@ -126,13 +128,35 @@ public string Password
///
/// The entry can only be decompressed if the library supports the zip features required to extract it.
/// See the ZipEntry Version property for more details.
+ ///
+ /// Since uses the local headers for extraction, entries with no compression combined with the
+ /// flag set, cannot be extracted as the end of the entry data cannot be deduced.
///
- public bool CanDecompressEntry
+ public bool CanDecompressEntry
+ => entry != null
+ && IsEntryCompressionMethodSupported(entry)
+ && entry.CanDecompress
+ && (!entry.HasFlag(GeneralBitFlags.Descriptor) || entry.CompressionMethod != CompressionMethod.Stored || entry.IsCrypted);
+
+ ///
+ /// Is the compression method for the specified entry supported?
+ ///
+ /// the entry to check.
+ /// true if the compression method is supported, false if not.
+ private static bool IsEntryCompressionMethodSupported(ZipEntry entry)
{
- get
- {
- return (entry != null) && entry.CanDecompress;
- }
+ var entryCompressionMethodForHeader = entry.CompressionMethodForHeader;
+ var entryCompressionMethod = entry.CompressionMethod;
+
+ var compressionMethodSupported =
+ entryCompressionMethod == CompressionMethod.Deflated ||
+ entryCompressionMethod == CompressionMethod.Stored;
+ var entryCompressionMethodForHeaderSupported =
+ entryCompressionMethodForHeader == CompressionMethod.Deflated ||
+ entryCompressionMethodForHeader == CompressionMethod.Stored ||
+ entryCompressionMethodForHeader == CompressionMethod.WinZipAES;
+
+ return compressionMethodSupported && entryCompressionMethodForHeaderSupported;
}
///
@@ -163,35 +187,16 @@ public ZipEntry GetNextEntry()
CloseEntry();
}
- int header = inputBuffer.ReadLeInt();
-
- if (header == ZipConstants.CentralHeaderSignature ||
- header == ZipConstants.EndOfCentralDirectorySignature ||
- header == ZipConstants.CentralHeaderDigitalSignature ||
- header == ZipConstants.ArchiveExtraDataSignature ||
- header == ZipConstants.Zip64CentralFileHeaderSignature)
+ if (!SkipUntilNextEntry())
{
- // No more individual entries exist
Dispose();
return null;
}
- // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
- // Spanning signature is same as descriptor signature and is untested as yet.
- if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature))
- {
- header = inputBuffer.ReadLeInt();
- }
-
- if (header != ZipConstants.LocalHeaderSignature)
- {
- throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
- }
-
var versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
flags = inputBuffer.ReadLeShort();
- method = (CompressionMethod)inputBuffer.ReadLeShort();
+ var method = (CompressionMethod)inputBuffer.ReadLeShort();
var dostime = (uint)inputBuffer.ReadLeInt();
int crc2 = inputBuffer.ReadLeInt();
csize = inputBuffer.ReadLeInt();
@@ -204,9 +209,11 @@ public ZipEntry GetNextEntry()
byte[] buffer = new byte[nameLen];
inputBuffer.ReadRawBuffer(buffer);
- string name = ZipStrings.ConvertToStringExt(flags, buffer);
+ var entryEncoding = _stringCodec.ZipInputEncoding(flags);
+ string name = entryEncoding.GetString(buffer);
+ var unicode = entryEncoding.IsZipUnicode();
- entry = new ZipEntry(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, method)
+ entry = new ZipEntry(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, method, unicode)
{
Flags = flags,
};
@@ -265,13 +272,24 @@ public ZipEntry GetNextEntry()
size = entry.Size;
}
- if (method == CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
+ if (method == CompressionMethod.Stored)
+ {
+ if (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))
+ {
+ throw new ZipException("Stored, but compressed != uncompressed");
+ }
+ }
+ else if (method == CompressionMethod.WinZipAES && entry.CompressionMethod == CompressionMethod.Stored)
{
- throw new ZipException("Stored, but compressed != uncompressed");
+ var sizeWithoutAesOverhead = csize - (entry.AESSaltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
+ if (sizeWithoutAesOverhead != size)
+ {
+ throw new ZipException("Stored, but compressed != uncompressed");
+ }
}
// Determine how to handle reading of data if this is attempted.
- if (entry.IsCompressionMethodSupported())
+ if (IsEntryCompressionMethodSupported(entry))
{
internalReader = new ReadDataHandler(InitialRead);
}
@@ -283,6 +301,54 @@ public ZipEntry GetNextEntry()
return entry;
}
+ ///
+ /// Reads bytes from the input stream until either a local file header signature, or another signature
+ /// indicating that no more entries should be present, is found.
+ ///
+ /// Thrown if the end of the input stream is reached without any signatures found
+ /// Returns whether the found signature is for a local entry header
+ private bool SkipUntilNextEntry()
+ {
+ // First let's skip all null bytes since it's the sane padding to add when updating an entry with smaller size
+ var paddingSkipped = 0;
+ while(inputBuffer.ReadLeByte() == 0) {
+ paddingSkipped++;
+ }
+
+ // Last byte read was not actually consumed, restore the offset
+ inputBuffer.Available += 1;
+ if(paddingSkipped > 0) {
+ Debug.WriteLine("Skipped {0} null byte(s) before reading signature", paddingSkipped);
+ }
+
+ var offset = 0;
+ // Read initial header quad directly after the last entry
+ var header = (uint)inputBuffer.ReadLeInt();
+ do
+ {
+ switch (header)
+ {
+ case ZipConstants.CentralHeaderSignature:
+ case ZipConstants.EndOfCentralDirectorySignature:
+ case ZipConstants.CentralHeaderDigitalSignature:
+ case ZipConstants.ArchiveExtraDataSignature:
+ case ZipConstants.Zip64CentralFileHeaderSignature:
+ Debug.WriteLine("Non-entry signature found at offset {0,2}: 0x{1:x8}", offset, header);
+ // No more individual entries exist
+ return false;
+
+ case ZipConstants.LocalHeaderSignature:
+ Debug.WriteLine("Entry local header signature found at offset {0,2}: 0x{1:x8}", offset, header);
+ return true;
+ default:
+ // Current header quad did not match any signature, shift in another byte
+ header = (uint) (inputBuffer.ReadLeByte() << 24) | (header >> 8);
+ offset++;
+ break;
+ }
+ } while (true); // Loop until we either get an EOF exception or we find the next signature
+ }
+
///
/// Read data descriptor at the end of compressed data.
///
@@ -309,13 +375,50 @@ private void ReadDataDescriptor()
entry.Size = size;
}
+ ///
+ /// Complete any decryption processing and clear any cryptographic state.
+ ///
+ private void StopDecrypting(bool testAESAuthCode)
+ {
+ StopDecrypting();
+
+ if (testAESAuthCode && entry.AESKeySize != 0)
+ {
+ byte[] authBytes = new byte[ZipConstants.AESAuthCodeLength];
+ int authBytesRead = inputBuffer.ReadRawBuffer(authBytes, 0, authBytes.Length);
+
+ if (authBytesRead < ZipConstants.AESAuthCodeLength)
+ {
+ throw new ZipException("Internal error missed auth code"); // Coding bug
+ }
+
+ // Final block done. Check Auth code.
+ byte[] calcAuthCode = this.cryptoTransform.GetAuthCode();
+ for (int i = 0; i < ZipConstants.AESAuthCodeLength; i++)
+ {
+ if (calcAuthCode[i] != authBytes[i])
+ {
+ throw new ZipException("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. The file may be damaged or tampered.");
+ }
+ }
+
+ // Dispose the transform?
+ }
+ }
+
///
/// Complete cleanup as the final part of closing.
///
/// True if the crc value should be tested
private void CompleteCloseEntry(bool testCrc)
{
- StopDecrypting();
+ StopDecrypting(testCrc);
+
+ // AE-2 does not have a CRC by specification. Do not check CRC in this case.
+ if (entry.AESKeySize != 0 && entry.AESVersion == 2)
+ {
+ testCrc = false;
+ }
if ((flags & 8) != 0)
{
@@ -332,7 +435,7 @@ private void CompleteCloseEntry(bool testCrc)
crc.Reset();
- if (method == CompressionMethod.Deflated)
+ if (entry.CompressionMethod == CompressionMethod.Deflated)
{
inf.Reset();
}
@@ -359,8 +462,8 @@ public void CloseEntry()
{
return;
}
-
- if (method == CompressionMethod.Deflated)
+
+ if (entry.CompressionMethod == CompressionMethod.Deflated)
{
if ((flags & 8) != 0)
{
@@ -375,11 +478,13 @@ public void CloseEntry()
}
csize -= inf.TotalIn;
+
inputBuffer.Available += inf.RemainingInput;
}
if ((inputBuffer.Available > csize) && (csize >= 0))
{
+ // Buffer can contain entire entry data. Internally offsetting position inside buffer
inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
}
else
@@ -477,6 +582,14 @@ private int ReadingNotSupported(byte[] destination, int offset, int count)
throw new ZipException("The compression method for this entry is not supported");
}
+ ///
+ /// Handle attempts to read from this entry by throwing an exception
+ ///
+ private int StoredDescriptorEntry(byte[] destination, int offset, int count) =>
+ throw new StreamUnsupportedException(
+ "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream");
+
+
///
/// Perform the initial read on an entry which may include
/// reading encryption headers and setting up inflation.
@@ -487,10 +600,7 @@ private int ReadingNotSupported(byte[] destination, int offset, int count)
/// The actual number of bytes read.
private int InitialRead(byte[] destination, int offset, int count)
{
- if (!CanDecompressEntry)
- {
- throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")");
- }
+ var usesDescriptor = (entry.Flags & (int)GeneralBitFlags.Descriptor) != 0;
// Handle encryption if required.
if (entry.IsCrypted)
@@ -500,27 +610,69 @@ private int InitialRead(byte[] destination, int offset, int count)
throw new ZipException("No password set.");
}
- // Generate and set crypto transform...
- var managed = new PkzipClassicManaged();
- byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
+ if (entry.AESKeySize == 0)
+ {
+ // Generate and set crypto transform...
+ var managed = new PkzipClassicManaged();
+ byte[] key = PkzipClassic.GenerateKeys(_stringCodec.ZipCryptoEncoding.GetBytes(password));
- inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
+ inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
+ inputBuffer.DecryptionLimit = null;
- byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
- inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
+ byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
+ inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
- if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
- {
- throw new ZipException("Invalid password");
- }
+ if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
+ {
+ throw new ZipException("Invalid password");
+ }
- if (csize >= ZipConstants.CryptoHeaderSize)
- {
- csize -= ZipConstants.CryptoHeaderSize;
+ if (csize >= ZipConstants.CryptoHeaderSize)
+ {
+ csize -= ZipConstants.CryptoHeaderSize;
+ }
+ else if (!usesDescriptor)
+ {
+ throw new ZipException($"Entry compressed size {csize} too small for encryption");
+ }
}
- else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
+ else
{
- throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
+ int saltLen = entry.AESSaltLen;
+ byte[] saltBytes = new byte[saltLen];
+ int saltIn = inputBuffer.ReadRawBuffer(saltBytes, 0, saltLen);
+
+ if (saltIn != saltLen)
+ {
+ throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
+ }
+
+ //
+ byte[] pwdVerifyRead = new byte[ZipConstants.AESPasswordVerifyLength];
+ int pwdBytesRead = inputBuffer.ReadRawBuffer(pwdVerifyRead, 0, pwdVerifyRead.Length);
+
+ if (pwdBytesRead != pwdVerifyRead.Length)
+ {
+ throw new EndOfStreamException();
+ }
+
+ int blockSize = entry.AESKeySize / 8; // bits to bytes
+
+ var decryptor = new ZipAESTransform(password, saltBytes, blockSize, false);
+ decryptor.ManualHmac = csize <= 0;
+ byte[] pwdVerifyCalc = decryptor.PwdVerifier;
+ if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
+ {
+ throw new ZipException("Invalid password for AES");
+ }
+
+ // The AES data has saltLen+AESPasswordVerifyLength bytes as a header, and AESAuthCodeLength bytes
+ // as a footer.
+ csize -= (saltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
+ inputBuffer.DecryptionLimit = csize >= 0 ? (int?)csize : null;
+
+ inputBuffer.CryptoTransform = decryptor;
+ this.cryptoTransform = decryptor;
}
}
else
@@ -528,21 +680,33 @@ private int InitialRead(byte[] destination, int offset, int count)
inputBuffer.CryptoTransform = null;
}
- if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0))
+ if (csize > 0 || usesDescriptor)
{
- if ((method == CompressionMethod.Deflated) && (inputBuffer.Available > 0))
+ if (entry.CompressionMethod == CompressionMethod.Deflated && inputBuffer.Available > 0)
{
inputBuffer.SetInflaterInput(inf);
}
- internalReader = new ReadDataHandler(BodyRead);
+ // It's not possible to know how many bytes to read when using "Stored" compression (unless using encryption)
+ if (!entry.IsCrypted && entry.CompressionMethod == CompressionMethod.Stored && usesDescriptor)
+ {
+ internalReader = StoredDescriptorEntry;
+ return StoredDescriptorEntry(destination, offset, count);
+ }
+
+ if (!CanDecompressEntry)
+ {
+ internalReader = ReadingNotSupported;
+ return ReadingNotSupported(destination, offset, count);
+ }
+
+ internalReader = BodyRead;
return BodyRead(destination, offset, count);
}
- else
- {
- internalReader = new ReadDataHandler(ReadingNotAvailable);
- return 0;
- }
+
+
+ internalReader = ReadingNotAvailable;
+ return 0;
}
///
@@ -584,8 +748,8 @@ public override int Read(byte[] buffer, int offset, int count)
///
/// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream.
///
- ///
- /// An i/o error occured.
+ ///
+ /// An i/o error occurred.
///
///
/// The deflated stream is corrupted.
@@ -612,7 +776,7 @@ private int BodyRead(byte[] buffer, int offset, int count)
bool finished = false;
- switch (method)
+ switch (entry.CompressionMethod)
{
case CompressionMethod.Deflated:
count = base.Read(buffer, offset, count);
@@ -663,6 +827,8 @@ private int BodyRead(byte[] buffer, int offset, int count)
}
}
break;
+ default:
+ throw new InvalidOperationException("Internal Error: Unsupported compression method encountered.");
}
if (count > 0)
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs
index 1b5e01a68..f91b20c3d 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs
@@ -93,19 +93,10 @@ public string TransformFile(string name)
}
name = name.Replace(@"\", "/");
- name = WindowsPathUtils.DropPathRoot(name);
+ name = PathUtils.DropPathRoot(name);
- // Drop any leading slashes.
- while ((name.Length > 0) && (name[0] == '/'))
- {
- name = name.Remove(0, 1);
- }
-
- // Drop any trailing slashes.
- while ((name.Length > 0) && (name[name.Length - 1] == '/'))
- {
- name = name.Remove(name.Length - 1, 1);
- }
+ // Drop any leading and trailing slashes.
+ name = name.Trim('/');
// Convert consecutive // characters to /
int index = name.IndexOf("//", StringComparison.Ordinal);
@@ -247,4 +238,76 @@ public static bool IsValidName(string name)
#endregion Class Fields
}
+
+ ///
+ /// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention.
+ /// Strips path roots and puts directory separators in the correct format ('/')
+ ///
+ public class PathTransformer : INameTransform
+ {
+ ///
+ /// Initialize a new instance of
+ ///
+ public PathTransformer()
+ {
+ }
+
+ ///
+ /// Transform a windows directory name according to the Zip file naming conventions.
+ ///
+ /// The directory name to transform.
+ /// The transformed name.
+ public string TransformDirectory(string name)
+ {
+ name = TransformFile(name);
+
+ if (name.Length > 0)
+ {
+ if (!name.EndsWith("/", StringComparison.Ordinal))
+ {
+ name += "/";
+ }
+ }
+ else
+ {
+ throw new ZipException("Cannot have an empty directory name");
+ }
+
+ return name;
+ }
+
+ ///
+ /// Transform a windows file name according to the Zip file naming conventions.
+ ///
+ /// The file name to transform.
+ /// The transformed name.
+ public string TransformFile(string name)
+ {
+ if (name != null)
+ {
+ // Put separators in the expected format.
+ name = name.Replace(@"\", "/");
+
+ // Remove the path root.
+ name = PathUtils.DropPathRoot(name);
+
+ // Drop any leading and trailing slashes.
+ name = name.Trim('/');
+
+ // Convert consecutive // characters to /
+ int index = name.IndexOf("//", StringComparison.Ordinal);
+ while (index >= 0)
+ {
+ name = name.Remove(index, 1);
+ index = name.IndexOf("//", StringComparison.Ordinal);
+ }
+ }
+ else
+ {
+ name = string.Empty;
+ }
+
+ return name;
+ }
+ }
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
index b9f1965dd..eaff7684d 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs
@@ -1,9 +1,14 @@
using ICSharpCode.SharpZipLib.Checksum;
+using ICSharpCode.SharpZipLib.Core;
+using ICSharpCode.SharpZipLib.Encryption;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -75,6 +80,11 @@ public ZipOutputStream(Stream baseOutputStream, int bufferSize)
{
}
+ internal ZipOutputStream(Stream baseOutputStream, StringCodec stringCodec) : this(baseOutputStream)
+ {
+ _stringCodec = stringCodec;
+ }
+
#endregion Constructors
///
@@ -95,13 +105,12 @@ public bool IsFinished
///
/// The comment text for the entire archive.
///
- ///
+ ///
/// The converted comment is longer than 0xffff bytes.
///
public void SetComment(string comment)
{
- // TODO: Its not yet clear how to handle unicode comments here.
- byte[] commentBytes = ZipStrings.ConvertToArray(comment);
+ byte[] commentBytes = _stringCodec.ZipArchiveCommentEncoding.GetBytes(comment);
if (commentBytes.Length > 0xffff)
{
throw new ArgumentOutOfRangeException(nameof(comment));
@@ -146,6 +155,35 @@ public UseZip64 UseZip64
set { useZip64_ = value; }
}
+ ///
+ /// Used for transforming the names of entries added by .
+ /// Defaults to , set to null to disable transforms and use names as supplied.
+ ///
+ public INameTransform NameTransform { get; set; } = new PathTransformer();
+
+ ///
+ /// Get/set the password used for encryption.
+ ///
+ /// When set to null or if the password is empty no encryption is performed
+ public string Password
+ {
+ get
+ {
+ return password;
+ }
+ set
+ {
+ if ((value != null) && (value.Length == 0))
+ {
+ password = null;
+ }
+ else
+ {
+ password = value;
+ }
+ }
+ }
+
///
/// Write an unsigned short in little endian byte order.
///
@@ -182,6 +220,15 @@ private void WriteLeLong(long value)
}
}
+ // Apply any configured transforms/cleaning to the name of the supplied entry.
+ private void TransformEntryName(ZipEntry entry)
+ {
+ if (NameTransform == null) return;
+ entry.Name = entry.IsDirectory
+ ? NameTransform.TransformDirectory(entry.Name)
+ : NameTransform.TransformFile(entry.Name);
+ }
+
///
/// Starts a new Zip entry. It automatically closes the previous
/// entry if present.
@@ -196,7 +243,7 @@ private void WriteLeLong(long value)
/// if entry passed is null.
///
///
- /// if an I/O error occured.
+ /// if an I/O error occurred.
///
///
/// if stream was finished
@@ -206,29 +253,137 @@ private void WriteLeLong(long value)
/// Entry name is too long
/// Finish has already been called
///
+ ///
+ /// The Compression method specified for the entry is unsupported.
+ ///
public void PutNextEntry(ZipEntry entry)
{
- if (entry == null)
+ if (curEntry != null)
{
- throw new ArgumentNullException(nameof(entry));
+ CloseEntry();
}
- if (entries == null)
+ PutNextEntry(baseOutputStream_, entry);
+
+ if (entry.IsCrypted)
{
- throw new InvalidOperationException("ZipOutputStream was finished");
+ WriteOutput(GetEntryEncryptionHeader(entry));
}
+ }
- if (curEntry != null)
+ ///
+ /// Starts a new passthrough Zip entry. It automatically closes the previous
+ /// entry if present.
+ /// Passthrough entry is an entry that is created from compressed data.
+ /// It is useful to avoid recompression to save CPU resources if compressed data is already disposable.
+ /// All entry elements bar name, crc, size and compressed size are optional, but must be correct if present.
+ /// Compression should be set to Deflated.
+ ///
+ ///
+ /// the entry.
+ ///
+ ///
+ /// if entry passed is null.
+ ///
+ ///
+ /// if an I/O error occurred.
+ ///
+ ///
+ /// if stream was finished.
+ ///
+ ///
+ /// Crc is not set
+ /// Size is not set
+ /// CompressedSize is not set
+ /// CompressionMethod is not Deflate
+ /// Too many entries in the Zip file
+ /// Entry name is too long
+ /// Finish has already been called
+ ///
+ ///
+ /// The Compression method specified for the entry is unsupported
+ /// Entry is encrypted
+ ///
+ public void PutNextPassthroughEntry(ZipEntry entry)
+ {
+ if(curEntry != null)
{
CloseEntry();
}
+ if(entry.Crc < 0)
+ {
+ throw new ZipException("Crc must be set for passthrough entry");
+ }
+
+ if(entry.Size < 0)
+ {
+ throw new ZipException("Size must be set for passthrough entry");
+ }
+
+ if(entry.CompressedSize < 0)
+ {
+ throw new ZipException("CompressedSize must be set for passthrough entry");
+ }
+
+ if(entry.CompressionMethod != CompressionMethod.Deflated)
+ {
+ throw new NotImplementedException("Only Deflated entries are supported for passthrough");
+ }
+
+ if(!string.IsNullOrEmpty(Password))
+ {
+ throw new NotImplementedException("Encrypted passthrough entries are not supported");
+ }
+
+ PutNextEntry(baseOutputStream_, entry, 0, true);
+ }
+
+
+ private void WriteOutput(byte[] bytes)
+ => baseOutputStream_.Write(bytes, 0, bytes.Length);
+
+ private Task WriteOutputAsync(byte[] bytes)
+ => baseOutputStream_.WriteAsync(bytes, 0, bytes.Length);
+
+ private byte[] GetEntryEncryptionHeader(ZipEntry entry) =>
+ entry.AESKeySize > 0
+ ? InitializeAESPassword(entry, Password)
+ : CreateZipCryptoHeader(entry.Crc < 0 ? entry.DosTime << 16 : entry.Crc);
+
+ internal void PutNextEntry(Stream stream, ZipEntry entry, long streamOffset = 0, bool passthroughEntry = false)
+ {
+ if (entry == null)
+ {
+ throw new ArgumentNullException(nameof(entry));
+ }
+
+ if (entries == null)
+ {
+ throw new InvalidOperationException("ZipOutputStream was finished");
+ }
+
if (entries.Count == int.MaxValue)
{
throw new ZipException("Too many entries for Zip file");
}
CompressionMethod method = entry.CompressionMethod;
+
+ // Check that the compression is one that we support
+ if (method != CompressionMethod.Deflated && method != CompressionMethod.Stored)
+ {
+ throw new NotImplementedException("Compression method not supported");
+ }
+
+ // A password must have been set in order to add AES encrypted entries
+ if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password))
+ {
+ throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added");
+ }
+
+ entryIsPassthrough = passthroughEntry;
+
int compressionLevel = defaultCompressionLevel;
// Clear flags that the library manages internally
@@ -238,7 +393,7 @@ public void PutNextEntry(ZipEntry entry)
bool headerInfoAvailable;
// No need to compress - definitely no data.
- if (entry.Size == 0)
+ if (entry.Size == 0 && !entryIsPassthrough)
{
entry.CompressedSize = entry.Size;
entry.Crc = 0;
@@ -288,6 +443,15 @@ public void PutNextEntry(ZipEntry entry)
if (Password != null)
{
entry.IsCrypted = true;
+
+ // In case of AES the CRC is always 0 according to specification.
+ // This also prevents that a data descriptor is needed for the CRC as it is the case for other
+ // password protected zip files. AES uses a 10 Byte auth code instead of the CRC
+ if (entry.AESKeySize != 0)
+ {
+ entry.Crc = 0;
+ }
+
if (entry.Crc < 0)
{
// Need to append a data descriptor as the crc isnt available for use
@@ -301,163 +465,87 @@ public void PutNextEntry(ZipEntry entry)
entry.CompressionMethod = (CompressionMethod)method;
curMethod = method;
- sizePatchPos = -1;
if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
{
entry.ForceZip64();
}
- // Write the local file header
- WriteLeInt(ZipConstants.LocalHeaderSignature);
-
- WriteLeShort(entry.Version);
- WriteLeShort(entry.Flags);
- WriteLeShort((byte)entry.CompressionMethodForHeader);
- WriteLeInt((int)entry.DosTime);
-
- // TODO: Refactor header writing. Its done in several places.
- if (headerInfoAvailable)
- {
- WriteLeInt((int)entry.Crc);
- if (entry.LocalHeaderRequiresZip64)
- {
- WriteLeInt(-1);
- WriteLeInt(-1);
- }
- else
- {
- WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
- WriteLeInt((int)entry.Size);
- }
- }
- else
- {
- if (patchEntryHeader)
- {
- crcPatchPos = baseOutputStream_.Position;
- }
- WriteLeInt(0); // Crc
-
- if (patchEntryHeader)
- {
- sizePatchPos = baseOutputStream_.Position;
- }
+ // Apply any required transforms to the entry name
+ TransformEntryName(entry);
- // For local header both sizes appear in Zip64 Extended Information
- if (entry.LocalHeaderRequiresZip64 || patchEntryHeader)
- {
- WriteLeInt(-1);
- WriteLeInt(-1);
- }
- else
- {
- WriteLeInt(0); // Compressed size
- WriteLeInt(0); // Uncompressed size
- }
- }
-
- byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
-
- if (name.Length > 0xFFFF)
- {
- throw new ZipException("Entry name too long.");
- }
-
- var ed = new ZipExtraData(entry.ExtraData);
-
- if (entry.LocalHeaderRequiresZip64)
- {
- ed.StartNewEntry();
- if (headerInfoAvailable)
- {
- ed.AddLeLong(entry.Size);
- ed.AddLeLong(entry.CompressedSize);
- }
- else
- {
- ed.AddLeLong(-1);
- ed.AddLeLong(-1);
- }
- ed.AddNewEntry(1);
-
- if (!ed.Find(1))
- {
- throw new ZipException("Internal error cant find extra data");
- }
-
- if (patchEntryHeader)
- {
- sizePatchPos = ed.CurrentReadIndex;
- }
- }
- else
- {
- ed.Delete(1);
- }
-
- if (entry.AESKeySize > 0)
- {
- AddExtraDataAES(entry, ed);
- }
- byte[] extra = ed.GetEntryData();
-
- WriteLeShort(name.Length);
- WriteLeShort(extra.Length);
-
- if (name.Length > 0)
- {
- baseOutputStream_.Write(name, 0, name.Length);
- }
-
- if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
- {
- sizePatchPos += baseOutputStream_.Position;
- }
+ // Write the local file header
+ offset += ZipFormat.WriteLocalHeader(stream, entry, out var entryPatchData,
+ headerInfoAvailable, patchEntryHeader, streamOffset, _stringCodec);
- if (extra.Length > 0)
- {
- baseOutputStream_.Write(extra, 0, extra.Length);
- }
+ patchData = entryPatchData;
- offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
// Fix offsetOfCentraldir for AES
if (entry.AESKeySize > 0)
offset += entry.AESOverheadSize;
// Activate the entry.
curEntry = entry;
+ size = 0;
+
+ if(entryIsPassthrough)
+ return;
+
crc.Reset();
if (method == CompressionMethod.Deflated)
{
deflater_.Reset();
deflater_.SetLevel(compressionLevel);
}
- size = 0;
+ }
- if (entry.IsCrypted)
- {
- if (entry.AESKeySize > 0)
- {
- WriteAESHeader(entry);
- }
- else
- {
- if (entry.Crc < 0)
- { // so testing Zip will says its ok
- WriteEncryptionHeader(entry.DosTime << 16);
- }
- else
- {
- WriteEncryptionHeader(entry.Crc);
- }
- }
- }
+ ///
+ /// Starts a new Zip entry. It automatically closes the previous
+ /// entry if present.
+ /// All entry elements bar name are optional, but must be correct if present.
+ /// If the compression method is stored and the output is not patchable
+ /// the compression for that entry is automatically changed to deflate level 0
+ ///
+ ///
+ /// the entry.
+ ///
+ /// The that can be used to cancel the operation.
+ ///
+ /// if entry passed is null.
+ ///
+ ///
+ /// if an I/O error occured.
+ ///
+ ///
+ /// if stream was finished
+ ///
+ ///
+ /// Too many entries in the Zip file
+ /// Entry name is too long
+ /// Finish has already been called
+ ///
+ ///
+ /// The Compression method specified for the entry is unsupported.
+ ///
+ public async Task PutNextEntryAsync(ZipEntry entry, CancellationToken ct = default)
+ {
+ if (curEntry != null) await CloseEntryAsync(ct);
+ var position = CanPatchEntries ? baseOutputStream_.Position : -1;
+ await baseOutputStream_.WriteProcToStreamAsync(s =>
+ {
+ PutNextEntry(s, entry, position);
+ }, ct);
+
+ if (!entry.IsCrypted) return;
+ await WriteOutputAsync(GetEntryEncryptionHeader(entry));
}
///
/// Closes the current entry, updating header and footer information as required
///
+ ///
+ /// Invalid entry field values.
+ ///
///
/// An I/O error occurs.
///
@@ -465,12 +553,54 @@ public void PutNextEntry(ZipEntry entry)
/// No entry is active.
///
public void CloseEntry()
+ {
+ WriteEntryFooter(baseOutputStream_);
+
+ // Patch the header if possible
+ if (patchEntryHeader)
+ {
+ patchEntryHeader = false;
+ ZipFormat.PatchLocalHeaderSync(baseOutputStream_, curEntry, patchData);
+ }
+
+ entries.Add(curEntry);
+ curEntry = null;
+ }
+
+ ///
+ public async Task CloseEntryAsync(CancellationToken ct)
+ {
+ await baseOutputStream_.WriteProcToStreamAsync(WriteEntryFooter, ct);
+
+ // Patch the header if possible
+ if (patchEntryHeader)
+ {
+ patchEntryHeader = false;
+ await ZipFormat.PatchLocalHeaderAsync(baseOutputStream_, curEntry, patchData, ct);
+ }
+
+ entries.Add(curEntry);
+ curEntry = null;
+ }
+
+ internal void WriteEntryFooter(Stream stream)
{
if (curEntry == null)
{
throw new InvalidOperationException("No open entry");
}
+ if(entryIsPassthrough)
+ {
+ if(curEntry.CompressedSize != size)
+ {
+ throw new ZipException($"compressed size was {size}, but {curEntry.CompressedSize} expected");
+ }
+
+ offset += size;
+ return;
+ }
+
long csize = size;
// First finish the deflater, if appropriate
@@ -488,7 +618,7 @@ public void CloseEntry()
}
else if (curMethod == CompressionMethod.Stored)
{
- // This is done by Finsh() for Deflated entries, but we need to do it
+ // This is done by Finish() for Deflated entries, but we need to do it
// ourselves for Stored ones
base.GetAuthCodeIfAES();
}
@@ -496,7 +626,20 @@ public void CloseEntry()
// Write the AES Authentication Code (a hash of the compressed and encrypted data)
if (curEntry.AESKeySize > 0)
{
- baseOutputStream_.Write(AESAuthCode, 0, 10);
+ stream.Write(AESAuthCode, 0, 10);
+ // Always use 0 as CRC for AE-2 format
+ curEntry.Crc = 0;
+ }
+ else
+ {
+ if (curEntry.Crc < 0)
+ {
+ curEntry.Crc = crc.Value;
+ }
+ else if (curEntry.Crc != crc.Value)
+ {
+ throw new ZipException($"crc was {crc.Value}, but {curEntry.Crc} was expected");
+ }
}
if (curEntry.Size < 0)
@@ -505,7 +648,7 @@ public void CloseEntry()
}
else if (curEntry.Size != size)
{
- throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
+ throw new ZipException($"size was {size}, but {curEntry.Size} was expected");
}
if (curEntry.CompressedSize < 0)
@@ -514,137 +657,104 @@ public void CloseEntry()
}
else if (curEntry.CompressedSize != csize)
{
- throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
- }
-
- if (curEntry.Crc < 0)
- {
- curEntry.Crc = crc.Value;
- }
- else if (curEntry.Crc != crc.Value)
- {
- throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
+ throw new ZipException($"compressed size was {csize}, but {curEntry.CompressedSize} expected");
}
offset += csize;
if (curEntry.IsCrypted)
{
- if (curEntry.AESKeySize > 0)
- {
- curEntry.CompressedSize += curEntry.AESOverheadSize;
- }
- else
- {
- curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
- }
- }
-
- // Patch the header if possible
- if (patchEntryHeader)
- {
- patchEntryHeader = false;
-
- long curPos = baseOutputStream_.Position;
- baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
- WriteLeInt((int)curEntry.Crc);
-
- if (curEntry.LocalHeaderRequiresZip64)
- {
- if (sizePatchPos == -1)
- {
- throw new ZipException("Entry requires zip64 but this has been turned off");
- }
-
- baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
- WriteLeLong(curEntry.Size);
- WriteLeLong(curEntry.CompressedSize);
- }
- else
- {
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
- }
- baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
+ curEntry.CompressedSize += curEntry.EncryptionOverheadSize;
}
// Add data descriptor if flagged as required
if ((curEntry.Flags & 8) != 0)
{
- WriteLeInt(ZipConstants.DataDescriptorSignature);
- WriteLeInt(unchecked((int)curEntry.Crc));
+ stream.WriteLEInt(ZipConstants.DataDescriptorSignature);
+ stream.WriteLEInt(unchecked((int)curEntry.Crc));
if (curEntry.LocalHeaderRequiresZip64)
{
- WriteLeLong(curEntry.CompressedSize);
- WriteLeLong(curEntry.Size);
+ stream.WriteLELong(curEntry.CompressedSize);
+ stream.WriteLELong(curEntry.Size);
offset += ZipConstants.Zip64DataDescriptorSize;
}
else
{
- WriteLeInt((int)curEntry.CompressedSize);
- WriteLeInt((int)curEntry.Size);
+ stream.WriteLEInt((int)curEntry.CompressedSize);
+ stream.WriteLEInt((int)curEntry.Size);
offset += ZipConstants.DataDescriptorSize;
}
}
-
- entries.Add(curEntry);
- curEntry = null;
}
- private void WriteEncryptionHeader(long crcValue)
+
+
+ // File format for AES:
+ // Size (bytes) Content
+ // ------------ -------
+ // Variable Salt value
+ // 2 Password verification value
+ // Variable Encrypted file data
+ // 10 Authentication code
+ //
+ // Value in the "compressed size" fields of the local file header and the central directory entry
+ // is the total size of all the items listed above. In other words, it is the total size of the
+ // salt value, password verification value, encrypted data, and authentication code.
+
+ ///
+ /// Initializes encryption keys based on given password.
+ ///
+ protected byte[] InitializeAESPassword(ZipEntry entry, string rawPassword)
+ {
+ var salt = new byte[entry.AESSaltLen];
+ // Salt needs to be cryptographically random, and unique per file
+ if (_aesRnd == null)
+ _aesRnd = RandomNumberGenerator.Create();
+ _aesRnd.GetBytes(salt);
+ int blockSize = entry.AESKeySize / 8; // bits to bytes
+
+ cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true);
+
+ var headBytes = new byte[salt.Length + 2];
+
+ Array.Copy(salt, headBytes, salt.Length);
+ Array.Copy(((ZipAESTransform)cryptoTransform_).PwdVerifier, 0,
+ headBytes, headBytes.Length - 2, 2);
+
+ return headBytes;
+ }
+
+ private byte[] CreateZipCryptoHeader(long crcValue)
{
offset += ZipConstants.CryptoHeaderSize;
- InitializePassword(Password);
+ InitializeZipCryptoPassword(Password);
byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
- var rnd = new Random();
- rnd.NextBytes(cryptBuffer);
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(cryptBuffer);
+ }
+
cryptBuffer[11] = (byte)(crcValue >> 24);
EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
- baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
- }
- private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
- {
- // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
- const int VENDOR_VERSION = 2;
- // Vendor ID is the two ASCII characters "AE".
- const int VENDOR_ID = 0x4541; //not 6965;
- extraData.StartNewEntry();
- // Pack AES extra data field see http://www.winzip.com/aes_info.htm
- //extraData.AddLeShort(7); // Data size (currently 7)
- extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2
- extraData.AddLeShort(VENDOR_ID); // "AE"
- extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256
- extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
- extraData.AddNewEntry(0x9901);
+ return cryptBuffer;
}
-
- // Replaces WriteEncryptionHeader for AES
- //
- private void WriteAESHeader(ZipEntry entry)
+
+ ///
+ /// Initializes encryption keys based on given .
+ ///
+ /// The password.
+ private void InitializeZipCryptoPassword(string password)
{
- byte[] salt;
- byte[] pwdVerifier;
- InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
- // File format for AES:
- // Size (bytes) Content
- // ------------ -------
- // Variable Salt value
- // 2 Password verification value
- // Variable Encrypted file data
- // 10 Authentication code
- //
- // Value in the "compressed size" fields of the local file header and the central directory entry
- // is the total size of all the items listed above. In other words, it is the total size of the
- // salt value, password verification value, encrypted data, and authentication code.
- baseOutputStream_.Write(salt, 0, salt.Length);
- baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
+ var pkManaged = new PkzipClassicManaged();
+ byte[] key = PkzipClassic.GenerateKeys(ZipCryptoEncoding.GetBytes(password));
+ cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
}
-
+
///
/// Writes the given buffer to the current entry.
///
@@ -680,35 +790,38 @@ public override void Write(byte[] buffer, int offset, int count)
throw new ArgumentException("Invalid offset/count combination");
}
- crc.Update(new ArraySegment(buffer, offset, count));
+ if (curEntry.AESKeySize == 0 && !entryIsPassthrough)
+ {
+ // Only update CRC if AES is not enabled and entry is not a passthrough one
+ crc.Update(new ArraySegment(buffer, offset, count));
+ }
+
size += count;
- switch (curMethod)
+ if(curMethod == CompressionMethod.Stored || entryIsPassthrough)
{
- case CompressionMethod.Deflated:
- base.Write(buffer, offset, count);
- break;
-
- case CompressionMethod.Stored:
- if (Password != null)
- {
- CopyAndEncrypt(buffer, offset, count);
- }
- else
- {
- baseOutputStream_.Write(buffer, offset, count);
- }
- break;
+ if (Password != null)
+ {
+ CopyAndEncrypt(buffer, offset, count);
+ }
+ else
+ {
+ baseOutputStream_.Write(buffer, offset, count);
+ }
+ }
+ else
+ {
+ base.Write(buffer, offset, count);
}
}
private void CopyAndEncrypt(byte[] buffer, int offset, int count)
{
- const int CopyBufferSize = 4096;
- byte[] localBuffer = new byte[CopyBufferSize];
+ const int copyBufferSize = 4096;
+ byte[] localBuffer = new byte[copyBufferSize];
while (count > 0)
{
- int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
+ int bufferCount = (count < copyBufferSize) ? count : copyBufferSize;
Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
EncryptBlock(localBuffer, 0, bufferCount);
@@ -747,144 +860,48 @@ public override void Finish()
long numEntries = entries.Count;
long sizeEntries = 0;
- foreach (ZipEntry entry in entries)
+ foreach (var entry in entries)
{
- WriteLeInt(ZipConstants.CentralHeaderSignature);
- WriteLeShort((entry.HostSystem << 8) | entry.VersionMadeBy);
- WriteLeShort(entry.Version);
- WriteLeShort(entry.Flags);
- WriteLeShort((short)entry.CompressionMethodForHeader);
- WriteLeInt((int)entry.DosTime);
- WriteLeInt((int)entry.Crc);
-
- if (entry.IsZip64Forced() ||
- (entry.CompressedSize >= uint.MaxValue))
- {
- WriteLeInt(-1);
- }
- else
- {
- WriteLeInt((int)entry.CompressedSize);
- }
-
- if (entry.IsZip64Forced() ||
- (entry.Size >= uint.MaxValue))
- {
- WriteLeInt(-1);
- }
- else
- {
- WriteLeInt((int)entry.Size);
- }
-
- byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
-
- if (name.Length > 0xffff)
- {
- throw new ZipException("Name too long.");
- }
-
- var ed = new ZipExtraData(entry.ExtraData);
-
- if (entry.CentralHeaderRequiresZip64)
- {
- ed.StartNewEntry();
- if (entry.IsZip64Forced() ||
- (entry.Size >= 0xffffffff))
- {
- ed.AddLeLong(entry.Size);
- }
+ sizeEntries += ZipFormat.WriteEndEntry(baseOutputStream_, entry, _stringCodec);
+ }
- if (entry.IsZip64Forced() ||
- (entry.CompressedSize >= 0xffffffff))
- {
- ed.AddLeLong(entry.CompressedSize);
- }
+ ZipFormat.WriteEndOfCentralDirectory(baseOutputStream_, numEntries, sizeEntries, offset, zipComment);
- if (entry.Offset >= 0xffffffff)
- {
- ed.AddLeLong(entry.Offset);
- }
-
- ed.AddNewEntry(1);
- }
- else
- {
- ed.Delete(1);
- }
+ entries = null;
+ }
- if (entry.AESKeySize > 0)
+ /// >
+ public override async Task FinishAsync(CancellationToken ct)
+ {
+ using (var ms = new MemoryStream())
+ {
+ if (entries == null)
{
- AddExtraDataAES(entry, ed);
+ return;
}
- byte[] extra = ed.GetEntryData();
-
- byte[] entryComment =
- (entry.Comment != null) ?
- ZipStrings.ConvertToArray(entry.Flags, entry.Comment) :
- new byte[0];
- if (entryComment.Length > 0xffff)
+ if (curEntry != null)
{
- throw new ZipException("Comment too long.");
+ await CloseEntryAsync(ct);
}
- WriteLeShort(name.Length);
- WriteLeShort(extra.Length);
- WriteLeShort(entryComment.Length);
- WriteLeShort(0); // disk number
- WriteLeShort(0); // internal file attributes
- // external file attributes
+ long numEntries = entries.Count;
+ long sizeEntries = 0;
- if (entry.ExternalFileAttributes != -1)
+ foreach (var entry in entries)
{
- WriteLeInt(entry.ExternalFileAttributes);
- }
- else
- {
- if (entry.IsDirectory)
- { // mark entry as directory (from nikolam.AT.perfectinfo.com)
- WriteLeInt(16);
- }
- else
+ await baseOutputStream_.WriteProcToStreamAsync(ms, s =>
{
- WriteLeInt(0);
- }
- }
-
- if (entry.Offset >= uint.MaxValue)
- {
- WriteLeInt(-1);
- }
- else
- {
- WriteLeInt((int)entry.Offset);
- }
-
- if (name.Length > 0)
- {
- baseOutputStream_.Write(name, 0, name.Length);
- }
-
- if (extra.Length > 0)
- {
- baseOutputStream_.Write(extra, 0, extra.Length);
- }
-
- if (entryComment.Length > 0)
- {
- baseOutputStream_.Write(entryComment, 0, entryComment.Length);
+ sizeEntries += ZipFormat.WriteEndEntry(s, entry, _stringCodec);
+ }, ct);
}
- sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
- }
+ await baseOutputStream_.WriteProcToStreamAsync(ms, s
+ => ZipFormat.WriteEndOfCentralDirectory(s, numEntries, sizeEntries, offset, zipComment),
+ ct);
- using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_))
- {
- zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
+ entries = null;
}
-
- entries = null;
}
///
@@ -920,6 +937,8 @@ public override void Flush()
///
private ZipEntry curEntry;
+ private bool entryIsPassthrough;
+
private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
private CompressionMethod curMethod = CompressionMethod.Deflated;
@@ -937,7 +956,7 @@ public override void Flush()
///
/// Comment for the entire archive recorded in central header.
///
- private byte[] zipComment = new byte[0];
+ private byte[] zipComment = Empty.Array();
///
/// Flag indicating that header patching is required for the current entry.
@@ -945,14 +964,9 @@ public override void Flush()
private bool patchEntryHeader;
///
- /// Position to patch crc
- ///
- private long crcPatchPos = -1;
-
- ///
- /// Position to patch size.
+ /// The values to patch in the entry local header
///
- private long sizePatchPos = -1;
+ private EntryPatchData patchData;
// Default is dynamic which is not backwards compatible and can cause problems
// with XP's built in compression which cant read Zip64 archives.
@@ -960,6 +974,20 @@ public override void Flush()
// NOTE: Setting the size for entries before they are added is the best solution!
private UseZip64 useZip64_ = UseZip64.Dynamic;
+ ///
+ /// The password to use when encrypting archive entries.
+ ///
+ private string password;
+
+ private readonly StringCodec _stringCodec = ZipStrings.GetStringCodec();
+
#endregion Instance Fields
+
+ #region Static Fields
+
+ // Static to help ensure that multiple files within a zip will get different random salt
+ private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create();
+
+ #endregion Static Fields
}
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs
index eff29a3ce..29fa98014 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs
@@ -1,193 +1,213 @@
using System;
using System.Text;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Zip
{
+ internal static class EncodingExtensions
+ {
+ public static bool IsZipUnicode(this Encoding e)
+ => e.Equals(StringCodec.UnicodeZipEncoding);
+ }
+
///
- /// This static class contains functions for encoding and decoding zip file strings
+ /// Deprecated way of setting zip encoding provided for backwards compability.
+ /// Use when possible.
///
+ ///
+ /// If any ZipStrings properties are being modified, it will enter a backwards compatibility mode, mimicking the
+ /// old behaviour where a single instance was shared between all Zip* instances.
+ ///
public static class ZipStrings
{
- static ZipStrings()
+ static readonly StringCodec CompatCodec = new StringCodec();
+
+ private static bool compatibilityMode;
+
+ ///
+ /// Returns a new instance or the shared backwards compatible instance.
+ ///
+ ///
+ public static StringCodec GetStringCodec()
+ => compatibilityMode ? CompatCodec : new StringCodec();
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static int CodePage
{
- try
+ get => CompatCodec.CodePage;
+ set
{
- var platformCodepage = Encoding.GetEncoding(0).CodePage;
- SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage;
+ CompatCodec.CodePage = value;
+ compatibilityMode = true;
}
- catch
+ }
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static int SystemDefaultCodePage => StringCodec.SystemDefaultCodePage;
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static bool UseUnicode
+ {
+ get => !CompatCodec.ForceZipLegacyEncoding;
+ set
{
- SystemDefaultCodePage = FallbackCodePage;
+ CompatCodec.ForceZipLegacyEncoding = !value;
+ compatibilityMode = true;
}
}
- /// Code page backing field
- ///
- /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states
- /// that file names should only be encoded with IBM Code Page 437 or UTF-8.
- /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows).
- /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/
- ///
- private static int codePage = AutomaticCodePage;
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ private static bool HasUnicodeFlag(int flags)
+ => ((GeneralBitFlags)flags).HasFlag(GeneralBitFlags.UnicodeText);
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static string ConvertToString(byte[] data, int count)
+ => CompatCodec.ZipOutputEncoding.GetString(data, 0, count);
- /// Automatically select codepage while opening archive
- /// see https://github.com/icsharpcode/SharpZipLib/pull/280#issuecomment-433608324
- ///
- private const int AutomaticCodePage = -1;
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static string ConvertToString(byte[] data)
+ => CompatCodec.ZipOutputEncoding.GetString(data);
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static string ConvertToStringExt(int flags, byte[] data, int count)
+ => CompatCodec.ZipEncoding(HasUnicodeFlag(flags)).GetString(data, 0, count);
- ///
- /// Encoding used for string conversion. Setting this to 65001 (UTF-8) will
- /// also set the Language encoding flag to indicate UTF-8 encoded file names.
- ///
- public static int CodePage
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static string ConvertToStringExt(int flags, byte[] data)
+ => CompatCodec.ZipEncoding(HasUnicodeFlag(flags)).GetString(data);
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static byte[] ConvertToArray(string str)
+ => ConvertToArray(0, str);
+
+ ///
+ [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")]
+ public static byte[] ConvertToArray(int flags, string str)
+ => (string.IsNullOrEmpty(str))
+ ? Empty.Array()
+ : CompatCodec.ZipEncoding(HasUnicodeFlag(flags)).GetBytes(str);
+ }
+
+ ///
+ /// Utility class for resolving the encoding used for reading and writing strings
+ ///
+ public class StringCodec
+ {
+ static StringCodec()
{
- get
+ try
{
- return codePage == AutomaticCodePage? Encoding.UTF8.CodePage:codePage;
+ var platformCodepage = Encoding.Default.CodePage;
+ SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage;
}
- set
+ catch
{
- if ((value < 0) || (value > 65535) ||
- (value == 1) || (value == 2) || (value == 3) || (value == 42))
- {
- throw new ArgumentOutOfRangeException(nameof(value));
- }
-
- codePage = value;
+ SystemDefaultCodePage = FallbackCodePage;
}
+
+ SystemDefaultEncoding = Encoding.GetEncoding(SystemDefaultCodePage);
}
- private const int FallbackCodePage = 437;
+ ///
+ /// If set, use the encoding set by for zip entries instead of the defaults
+ ///
+ public bool ForceZipLegacyEncoding { get; set; }
///
- /// Attempt to get the operating system default codepage, or failing that, to
- /// the fallback code page IBM 437.
+ /// The default encoding used for ZipCrypto passwords in zip files, set to
+ /// for greatest compability.
///
- public static int SystemDefaultCodePage { get; }
+ public static Encoding DefaultZipCryptoEncoding => SystemDefaultEncoding;
+
+ ///
+ /// Returns the encoding for an output .
+ /// Unless overriden by it returns .
+ ///
+ public Encoding ZipOutputEncoding => ZipEncoding(!ForceZipLegacyEncoding);
+
+ ///
+ /// Returns if is set, otherwise it returns the encoding indicated by
+ ///
+ public Encoding ZipEncoding(bool unicode) => unicode ? UnicodeZipEncoding : _legacyEncoding;
///
- /// Get wether the default codepage is set to UTF-8. Setting this property to false will
- /// set the to
+ /// Returns the appropriate encoding for an input according to .
+ /// If overridden by , it always returns the encoding indicated by .
///
+ ///
+ ///
+ public Encoding ZipInputEncoding(GeneralBitFlags flags) => ZipInputEncoding((int)flags);
+
+ ///
+ public Encoding ZipInputEncoding(int flags) => ZipEncoding(!ForceZipLegacyEncoding && (flags & (int)GeneralBitFlags.UnicodeText) != 0);
+
+ /// Code page encoding, used for non-unicode strings
///
- /// /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc.
- /// But sometimes it yields the special value of 1 which is nicknamed CodePageNoOEM in sources (might also mean CP_OEMCP, but Encoding puts it so).
- /// This was observed on Ukranian and Hindu systems.
- /// Given this value, throws an .
- /// So replace it with , (IBM 437 which is the default code page in a default Windows installation console.
+ /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states
+ /// that file names should only be encoded with IBM Code Page 437 or UTF-8.
+ /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows).
+ /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/
///
- public static bool UseUnicode
- {
- get
- {
- return codePage == Encoding.UTF8.CodePage;
- }
- set
- {
- if (value)
- {
- codePage = Encoding.UTF8.CodePage;
- }
- else
- {
- codePage = SystemDefaultCodePage;
- }
- }
- }
+ private Encoding _legacyEncoding = SystemDefaultEncoding;
+
+ private Encoding _zipArchiveCommentEncoding;
+ private Encoding _zipCryptoEncoding;
///
- /// Convert a portion of a byte array to a string using
+ /// Returns the UTF-8 code page (65001) used for zip entries with unicode flag set
///
- ///
- /// Data to convert to string
- ///
- ///
- /// Number of bytes to convert starting from index 0
- ///
- ///
- /// data[0]..data[count - 1] converted to a string
- ///
- public static string ConvertToString(byte[] data, int count)
- => data == null
- ? string.Empty
- : Encoding.GetEncoding(CodePage).GetString(data, 0, count);
+ public static readonly Encoding UnicodeZipEncoding = Encoding.UTF8;
///
- /// Convert a byte array to a string using
+ /// Code page used for non-unicode strings and legacy zip encoding (if is set).
+ /// Default value is
///
- ///
- /// Byte array to convert
- ///
- ///
- /// dataconverted to a string
- ///
- public static string ConvertToString(byte[] data)
- => ConvertToString(data, data.Length);
-
- private static Encoding EncodingFromFlag(int flags)
- => ((flags & (int)GeneralBitFlags.UnicodeText) != 0)
- ? Encoding.UTF8
- : Encoding.GetEncoding(
- // if CodePage wasn't set manually and no utf flag present
- // then we must use SystemDefault (old behavior)
- // otherwise, CodePage should be preferred over SystemDefault
- // see https://github.com/icsharpcode/SharpZipLib/issues/274
- codePage == AutomaticCodePage?
- SystemDefaultCodePage:
- codePage);
+ public int CodePage
+ {
+ get => _legacyEncoding.CodePage;
+ set => _legacyEncoding = (value < 4 || value > 65535 || value == 42)
+ ? throw new ArgumentOutOfRangeException(nameof(value))
+ : Encoding.GetEncoding(value);
+ }
+
+ private const int FallbackCodePage = 437;
///
- /// Convert a byte array to a string using
+ /// Operating system default codepage, or if it could not be retrieved, the fallback code page IBM 437.
///
- /// The applicable general purpose bits flags
- ///
- /// Byte array to convert
- ///
- /// The number of bytes to convert.
- ///
- /// dataconverted to a string
- ///
- public static string ConvertToStringExt(int flags, byte[] data, int count)
- => (data == null)
- ? string.Empty
- : EncodingFromFlag(flags).GetString(data, 0, count);
+ public static int SystemDefaultCodePage { get; }
///
- /// Convert a byte array to a string using
+ /// The system default encoding, based on
///
- ///
- /// Byte array to convert
- ///
- /// The applicable general purpose bits flags
- ///
- /// dataconverted to a string
- ///
- public static string ConvertToStringExt(int flags, byte[] data)
- => ConvertToStringExt(flags, data, data.Length);
+ public static Encoding SystemDefaultEncoding { get; }
///
- /// Convert a string to a byte array using
+ /// The encoding used for the zip archive comment. Defaults to the encoding for , since
+ /// no unicode flag can be set for it in the files.
///
- ///
- /// String to convert to an array
- ///
- /// Converted array
- public static byte[] ConvertToArray(string str)
- => str == null
- ? new byte[0]
- : Encoding.GetEncoding(CodePage).GetBytes(str);
+ public Encoding ZipArchiveCommentEncoding
+ {
+ get => _zipArchiveCommentEncoding ?? _legacyEncoding;
+ set => _zipArchiveCommentEncoding = value;
+ }
///
- /// Convert a string to a byte array using
+ /// The encoding used for the ZipCrypto passwords. Defaults to .
///
- /// The applicable general purpose bits flags
- ///
- /// String to convert to an array
- ///
- /// Converted array
- public static byte[] ConvertToArray(int flags, string str)
- => (string.IsNullOrEmpty(str))
- ? new byte[0]
- : EncodingFromFlag(flags).GetBytes(str);
+ public Encoding ZipCryptoEncoding
+ {
+ get => _zipCryptoEncoding ?? DefaultZipCryptoEncoding;
+ set => _zipCryptoEncoding = value;
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.TestBootstrapper/ICSharpCode.SharpZipLib.TestBootstrapper.csproj b/test/ICSharpCode.SharpZipLib.TestBootstrapper/ICSharpCode.SharpZipLib.TestBootstrapper.csproj
deleted file mode 100644
index 3f599186e..000000000
--- a/test/ICSharpCode.SharpZipLib.TestBootstrapper/ICSharpCode.SharpZipLib.TestBootstrapper.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- Exe
- netcoreapp2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/ICSharpCode.SharpZipLib.TestBootstrapper/NuGet.config b/test/ICSharpCode.SharpZipLib.TestBootstrapper/NuGet.config
deleted file mode 100644
index 7be9c71ec..000000000
--- a/test/ICSharpCode.SharpZipLib.TestBootstrapper/NuGet.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/test/ICSharpCode.SharpZipLib.TestBootstrapper/Program.cs b/test/ICSharpCode.SharpZipLib.TestBootstrapper/Program.cs
deleted file mode 100644
index 4a030de1f..000000000
--- a/test/ICSharpCode.SharpZipLib.TestBootstrapper/Program.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using NUnitLite;
-using System.Reflection;
-
-namespace ICSharpCode.SharpZipLib.TestBootstrapper
-{
- public class Program
- {
- private static void Main(string[] args)
- {
- new AutoRun(typeof(Tests.Base.InflaterDeflaterTestSuite).GetTypeInfo().Assembly)
- .Execute(args);
- }
- }
-}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/BZip2/Bzip2Tests.cs b/test/ICSharpCode.SharpZipLib.Tests/BZip2/Bzip2Tests.cs
index 056a2625b..62d5a7874 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/BZip2/Bzip2Tests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/BZip2/Bzip2Tests.cs
@@ -1,7 +1,6 @@
using ICSharpCode.SharpZipLib.BZip2;
using ICSharpCode.SharpZipLib.Tests.TestSupport;
using NUnit.Framework;
-using System;
using System.IO;
namespace ICSharpCode.SharpZipLib.Tests.BZip2
@@ -12,6 +11,9 @@ namespace ICSharpCode.SharpZipLib.Tests.BZip2
[TestFixture]
public class BZip2Suite
{
+ // Use the same random seed to guarantee all the code paths are followed
+ const int RandomSeed = 4;
+
///
/// Basic compress/decompress test BZip2
///
@@ -21,34 +23,30 @@ public void BasicRoundTrip()
{
var ms = new MemoryStream();
var outStream = new BZip2OutputStream(ms);
+
+ var buf = Utils.GetDummyBytes(size: 10000, RandomSeed);
- byte[] buf = new byte[10000];
- var rnd = new Random();
- rnd.NextBytes(buf);
-
- outStream.Write(buf, 0, buf.Length);
+ outStream.Write(buf, offset: 0, buf.Length);
outStream.Close();
ms = new MemoryStream(ms.GetBuffer());
- ms.Seek(0, SeekOrigin.Begin);
+ ms.Seek(offset: 0, SeekOrigin.Begin);
- using (BZip2InputStream inStream = new BZip2InputStream(ms))
+ using BZip2InputStream inStream = new BZip2InputStream(ms);
+ var buf2 = new byte[buf.Length];
+ var pos = 0;
+ while (true)
{
- byte[] buf2 = new byte[buf.Length];
- int pos = 0;
- while (true)
+ var numRead = inStream.Read(buf2, pos, count: 4096);
+ if (numRead <= 0)
{
- int numRead = inStream.Read(buf2, pos, 4096);
- if (numRead <= 0)
- {
- break;
- }
- pos += numRead;
+ break;
}
+ pos += numRead;
+ }
- for (int i = 0; i < buf.Length; ++i)
- {
- Assert.AreEqual(buf2[i], buf[i]);
- }
+ for (var i = 0; i < buf.Length; ++i)
+ {
+ Assert.AreEqual(buf2[i], buf[i]);
}
}
@@ -80,7 +78,7 @@ public void CreateEmptyArchive()
pos += numRead;
}
- Assert.AreEqual(pos, 0);
+ Assert.Zero(pos);
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Base/InflaterDeflaterTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Base/InflaterDeflaterTests.cs
index 1d736558e..e9ba0ad77 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Base/InflaterDeflaterTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Base/InflaterDeflaterTests.cs
@@ -6,6 +6,8 @@
using System.IO;
using System.Security;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tests.Base
{
@@ -15,19 +17,18 @@ namespace ICSharpCode.SharpZipLib.Tests.Base
[TestFixture]
public class InflaterDeflaterTestSuite
{
+ // Use the same random seed to guarantee all the code paths are followed
+ const int RandomSeed = 5;
+
private void Inflate(MemoryStream ms, byte[] original, int level, bool zlib)
{
- ms.Seek(0, SeekOrigin.Begin);
-
- var inflater = new Inflater(!zlib);
- var inStream = new InflaterInputStream(ms, inflater);
byte[] buf2 = new byte[original.Length];
- int currentIndex = 0;
- int count = buf2.Length;
-
- try
+ using (var inStream = GetInflaterInputStream(ms, zlib))
{
+ int currentIndex = 0;
+ int count = buf2.Length;
+
while (true)
{
int numRead = inStream.Read(buf2, currentIndex, count);
@@ -38,19 +39,95 @@ private void Inflate(MemoryStream ms, byte[] original, int level, bool zlib)
currentIndex += numRead;
count -= numRead;
}
+
+ Assert.That(currentIndex, Is.EqualTo(original.Length), "Decompressed data must have the same length as the original data");
}
- catch (Exception ex)
+
+ VerifyInflatedData(original, buf2, level, zlib);
+ }
+
+ private MemoryStream Deflate(byte[] data, int level, bool zlib)
+ {
+ var memoryStream = new MemoryStream();
+
+ var deflater = new Deflater(level, !zlib);
+ using (DeflaterOutputStream outStream = new DeflaterOutputStream(memoryStream, deflater))
{
- Console.WriteLine("Unexpected exception - '{0}'", ex.Message);
- throw;
+ outStream.IsStreamOwner = false;
+ outStream.Write(data, 0, data.Length);
+ outStream.Flush();
+ outStream.Finish();
}
+ return memoryStream;
+ }
- if (currentIndex != original.Length)
+ private void RandomDeflateInflate(int size, int level, bool zlib)
+ {
+ var buffer = Utils.GetDummyBytes(size, RandomSeed);
+ var ms = Deflate(buffer, level, zlib);
+ Inflate(ms, buffer, level, zlib);
+ }
+
+ private static InflaterInputStream GetInflaterInputStream(Stream compressedStream, bool zlib)
+ {
+ compressedStream.Seek(0, SeekOrigin.Begin);
+
+ var inflater = new Inflater(!zlib);
+ var inStream = new InflaterInputStream(compressedStream, inflater);
+
+ return inStream;
+ }
+
+ private async Task InflateAsync(MemoryStream ms, byte[] original, int level, bool zlib)
+ {
+ byte[] buf2 = new byte[original.Length];
+
+ using (var inStream = GetInflaterInputStream(ms, zlib))
{
- Console.WriteLine("Original {0}, new {1}", original.Length, currentIndex);
- Assert.Fail("Lengths different");
+ int currentIndex = 0;
+ int count = buf2.Length;
+
+ while (true)
+ {
+ int numRead = await inStream.ReadAsync(buf2, currentIndex, count);
+ if (numRead <= 0)
+ {
+ break;
+ }
+ currentIndex += numRead;
+ count -= numRead;
+ }
+
+ Assert.That(currentIndex, Is.EqualTo(original.Length), "Decompressed data must have the same length as the original data");
}
+ VerifyInflatedData(original, buf2, level, zlib);
+ }
+
+ private async Task DeflateAsync(byte[] data, int level, bool zlib)
+ {
+ var memoryStream = new MemoryStream();
+
+ var deflater = new Deflater(level, !zlib);
+ using (DeflaterOutputStream outStream = new DeflaterOutputStream(memoryStream, deflater))
+ {
+ outStream.IsStreamOwner = false;
+ await outStream.WriteAsync(data, 0, data.Length);
+ await outStream.FlushAsync();
+ await outStream.FinishAsync(CancellationToken.None);
+ }
+ return memoryStream;
+ }
+
+ private async Task RandomDeflateInflateAsync(int size, int level, bool zlib)
+ {
+ var buffer = Utils.GetDummyBytes(size, RandomSeed);
+ var ms = await DeflateAsync(buffer, level, zlib);
+ await InflateAsync(ms, buffer, level, zlib);
+ }
+
+ private void VerifyInflatedData(byte[] original, byte[] buf2, int level, bool zlib)
+ {
for (int i = 0; i < original.Length; ++i)
{
if (buf2[i] != original[i])
@@ -74,59 +151,39 @@ private void Inflate(MemoryStream ms, byte[] original, int level, bool zlib)
}
}
- private MemoryStream Deflate(byte[] data, int level, bool zlib)
- {
- var memoryStream = new MemoryStream();
-
- var deflater = new Deflater(level, !zlib);
- using (DeflaterOutputStream outStream = new DeflaterOutputStream(memoryStream, deflater))
- {
- outStream.IsStreamOwner = false;
- outStream.Write(data, 0, data.Length);
- outStream.Flush();
- outStream.Finish();
- }
- return memoryStream;
- }
-
- private void RandomDeflateInflate(int size, int level, bool zlib)
+ ///
+ /// Basic inflate/deflate test
+ ///
+ [Test]
+ [Category("Base")]
+ public void InflateDeflateZlib([Range(0, 9)] int level)
{
- byte[] buffer = new byte[size];
- var rnd = new Random();
- rnd.NextBytes(buffer);
-
- MemoryStream ms = Deflate(buffer, level, zlib);
- Inflate(ms, buffer, level, zlib);
+ RandomDeflateInflate(100000, level, true);
}
///
- /// Basic inflate/deflate test
+ /// Basic async inflate/deflate test
///
[Test]
[Category("Base")]
- public void InflateDeflateZlib()
+ [Category("Async")]
+ public async Task InflateDeflateZlibAsync([Range(0, 9)] int level)
{
- for (int level = 0; level < 10; ++level)
- {
- RandomDeflateInflate(100000, level, true);
- }
+ await RandomDeflateInflateAsync(size: 100000, level, zlib: true);
}
private delegate void RunCompress(byte[] buffer);
- private int runLevel;
- private bool runZlib;
- private long runCount;
- private Random runRandom = new Random(5);
+ private int _runLevel;
+ private bool _runZlib;
private void DeflateAndInflate(byte[] buffer)
{
- ++runCount;
- MemoryStream ms = Deflate(buffer, runLevel, runZlib);
- Inflate(ms, buffer, runLevel, runZlib);
+ var ms = Deflate(buffer, _runLevel, _runZlib);
+ Inflate(ms, buffer, _runLevel, _runZlib);
}
- private void TryVariants(RunCompress test, byte[] buffer, int index)
+ private void TryVariants(RunCompress test, byte[] buffer, Random random, int index)
{
int worker = 0;
while (worker <= 255)
@@ -134,47 +191,57 @@ private void TryVariants(RunCompress test, byte[] buffer, int index)
buffer[index] = (byte)worker;
if (index < buffer.Length - 1)
{
- TryVariants(test, buffer, index + 1);
+ TryVariants(test, buffer, random, index + 1);
}
else
{
test(buffer);
}
- worker += runRandom.Next(256);
+ worker += random.Next(maxValue: 256);
}
}
private void TryManyVariants(int level, bool zlib, RunCompress test, byte[] buffer)
{
- runLevel = level;
- runZlib = zlib;
- TryVariants(test, buffer, 0);
+ var random = new Random(RandomSeed);
+ _runLevel = level;
+ _runZlib = zlib;
+ TryVariants(test, buffer, random, 0);
}
// TODO: Fix this
- //[Test]
- //[Category("Base")]
- //public void SmallBlocks()
- //{
- // byte[] buffer = new byte[10];
- // Array.Clear(buffer, 0, buffer.Length);
- // TryManyVariants(0, false, new RunCompress(DeflateAndInflate), buffer);
- //}
+ [Test]
+ [Category("Base")]
+ [Explicit("Long-running")]
+ public void SmallBlocks()
+ {
+ var buffer = new byte[10];
+ TryManyVariants(level: 0, zlib: false, DeflateAndInflate, buffer);
+ }
///
/// Basic inflate/deflate test
///
[Test]
[Category("Base")]
- public void InflateDeflateNonZlib()
+ public void InflateDeflateNonZlib([Range(0, 9)] int level)
{
- for (int level = 0; level < 10; ++level)
- {
- RandomDeflateInflate(100000, level, false);
- }
+ RandomDeflateInflate(100000, level, false);
+ }
+
+ ///
+ /// Basic async inflate/deflate test
+ ///
+ [Test]
+ [Category("Base")]
+ [Category("Async")]
+ public async Task InflateDeflateNonZlibAsync([Range(0, 9)] int level)
+ {
+ await RandomDeflateInflateAsync(100000, level, false);
}
+
[Test]
[Category("Base")]
public void CloseDeflatorWithNestedUsing()
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Checksum/ChecksumTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Checksum/ChecksumTests.cs
index b8aefc52f..56c6bb836 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Checksum/ChecksumTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Checksum/ChecksumTests.cs
@@ -13,6 +13,14 @@ private readonly
// Represents ASCII string of "123456789"
byte[] check = { 49, 50, 51, 52, 53, 54, 55, 56, 57 };
+ // Represents string "123456789123456789123456789123456789"
+ private readonly byte[] longCheck = {
+ 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57
+ };
+
[Test]
public void Adler_32()
{
@@ -70,6 +78,32 @@ public void CRC_32_BZip2()
exceptionTesting(underTestBZip2Crc);
}
+ [Test]
+ public void CRC_32_BZip2_Long()
+ {
+ var underTestCrc32 = new BZip2Crc();
+ underTestCrc32.Update(longCheck);
+ Assert.AreEqual(0xEE53D2B2, underTestCrc32.Value);
+ }
+
+ [Test]
+ public void CRC_32_BZip2_Unaligned()
+ {
+ // Extract "456" and CRC
+ var underTestCrc32 = new BZip2Crc();
+ underTestCrc32.Update(new ArraySegment(check, 3, 3));
+ Assert.AreEqual(0x001D0511, underTestCrc32.Value);
+ }
+
+ [Test]
+ public void CRC_32_BZip2_Long_Unaligned()
+ {
+ // Extract "789123456789123456" and CRC
+ var underTestCrc32 = new BZip2Crc();
+ underTestCrc32.Update(new ArraySegment(longCheck, 15, 18));
+ Assert.AreEqual(0x025846E0, underTestCrc32.Value);
+ }
+
[Test]
public void CRC_32()
{
@@ -85,6 +119,32 @@ public void CRC_32()
exceptionTesting(underTestCrc32);
}
+ [Test]
+ public void CRC_32_Long()
+ {
+ var underTestCrc32 = new Crc32();
+ underTestCrc32.Update(longCheck);
+ Assert.AreEqual(0x3E29169C, underTestCrc32.Value);
+ }
+
+ [Test]
+ public void CRC_32_Unaligned()
+ {
+ // Extract "456" and CRC
+ var underTestCrc32 = new Crc32();
+ underTestCrc32.Update(new ArraySegment(check, 3, 3));
+ Assert.AreEqual(0xB1A8C371, underTestCrc32.Value);
+ }
+
+ [Test]
+ public void CRC_32_Long_Unaligned()
+ {
+ // Extract "789123456789123456" and CRC
+ var underTestCrc32 = new Crc32();
+ underTestCrc32.Update(new ArraySegment(longCheck, 15, 18));
+ Assert.AreEqual(0x31CA9A2E, underTestCrc32.Value);
+ }
+
private void exceptionTesting(IChecksum crcUnderTest)
{
bool exception = false;
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Core/ByteOrderUtilsTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Core/ByteOrderUtilsTests.cs
new file mode 100644
index 000000000..1a5d271ff
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Core/ByteOrderUtilsTests.cs
@@ -0,0 +1,137 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using BO = ICSharpCode.SharpZipLib.Core.ByteOrderStreamExtensions;
+using ICSharpCode.SharpZipLib.Core;
+
+// ReSharper disable InconsistentNaming
+
+namespace ICSharpCode.SharpZipLib.Tests.Core
+{
+ [TestFixture]
+ [Category("Core")]
+ public class ByteOrderUtilsTests
+ {
+ private const short native16 = 0x1234;
+ private static readonly byte[] swapped16 = { 0x34, 0x12 };
+
+ private const int native32 = 0x12345678;
+ private static readonly byte[] swapped32 = { 0x78, 0x56, 0x34, 0x12 };
+
+ private const long native64 = 0x123456789abcdef0;
+ private static readonly byte[] swapped64 = { 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12 };
+
+ [Test]
+ public void ToSwappedBytes()
+ {
+ Assert.AreEqual(swapped16, BO.SwappedBytes(native16));
+ Assert.AreEqual(swapped16, BO.SwappedBytes((ushort)native16));
+
+ Assert.AreEqual(swapped32, BO.SwappedBytes(native32));
+ Assert.AreEqual(swapped32, BO.SwappedBytes((uint)native32));
+
+ Assert.AreEqual(swapped64, BO.SwappedBytes(native64));
+ Assert.AreEqual(swapped64, BO.SwappedBytes((ulong)native64));
+ }
+
+ [Test]
+ public void FromSwappedBytes()
+ {
+ Assert.AreEqual(native16, BO.SwappedS16(swapped16));
+ Assert.AreEqual(native16, BO.SwappedU16(swapped16));
+
+ Assert.AreEqual(native32, BO.SwappedS32(swapped32));
+ Assert.AreEqual(native32, BO.SwappedU32(swapped32));
+
+ Assert.AreEqual(native64, BO.SwappedS64(swapped64));
+ Assert.AreEqual(native64, BO.SwappedU64(swapped64));
+ }
+
+ [Test]
+ public void ReadLESigned16()
+ => TestReadLE(native16, 2, BO.ReadLEShort);
+
+ [Test]
+ public void ReadLESigned32()
+ => TestReadLE(native32,4, BO.ReadLEInt);
+
+ [Test]
+ public void ReadLESigned64()
+ => TestReadLE(native64,8, BO.ReadLELong);
+
+ [Test]
+ public void WriteLESigned16()
+ => TestWriteLE(swapped16, s => s.WriteLEShort(native16));
+
+ [Test]
+ public void WriteLESigned32()
+ => TestWriteLE(swapped32, s => s.WriteLEInt(native32));
+
+ [Test]
+ public void WriteLESigned64()
+ => TestWriteLE(swapped64, s => s.WriteLELong(native64));
+
+ [Test]
+ public void WriteLEUnsigned16()
+ => TestWriteLE(swapped16, s => s.WriteLEUshort((ushort)native16));
+
+ [Test]
+ public void WriteLEUnsigned32()
+ => TestWriteLE(swapped32, s => s.WriteLEUint(native32));
+
+ [Test]
+ public void WriteLEUnsigned64()
+ => TestWriteLE(swapped64, s => s.WriteLEUlong(native64));
+
+ [Test]
+ public async Task WriteLEAsyncSigned16()
+ => await TestWriteLEAsync(swapped16, (int)native16, BO.WriteLEShortAsync);
+
+ [Test]
+ public async Task WriteLEAsyncUnsigned16()
+ => await TestWriteLEAsync(swapped16, (ushort)native16, BO.WriteLEUshortAsync);
+
+ [Test]
+ public async Task WriteLEAsyncSigned32()
+ => await TestWriteLEAsync(swapped32, native32, BO.WriteLEIntAsync);
+ [Test]
+ public async Task WriteLEAsyncUnsigned32()
+ => await TestWriteLEAsync(swapped32, (uint)native32, BO.WriteLEUintAsync);
+
+ [Test]
+ public async Task WriteLEAsyncSigned64()
+ => await TestWriteLEAsync(swapped64, native64, BO.WriteLELongAsync);
+ [Test]
+ public async Task WriteLEAsyncUnsigned64()
+ => await TestWriteLEAsync(swapped64, (ulong)native64, BO.WriteLEUlongAsync);
+
+
+ private static void TestReadLE(T expected, int bytes, Func read)
+ {
+ using (var ms = new MemoryStream(swapped64, 8 - bytes, bytes))
+ {
+ Assert.AreEqual(expected, read(ms));
+ }
+ }
+
+ private static void TestWriteLE(byte[] expected, Action write)
+ {
+ using (var ms = new MemoryStream())
+ {
+ write(ms);
+ Assert.AreEqual(expected, ms.ToArray());
+ }
+ }
+
+ private static async Task TestWriteLEAsync(byte[] expected, T input, Func write)
+ {
+ using (var ms = new MemoryStream())
+ {
+ await write(ms, input, CancellationToken.None);
+ Assert.AreEqual(expected, ms.ToArray());
+ }
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Core/CoreTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Core/CoreTests.cs
index 985718fb2..1b43c79ae 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Core/CoreTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Core/CoreTests.cs
@@ -1,3 +1,4 @@
+using System;
using ICSharpCode.SharpZipLib.Core;
using NUnit.Framework;
@@ -54,5 +55,51 @@ public void ValidFilter()
Assert.IsFalse(NameFilter.IsValidFilterExpression(@"\,)"));
Assert.IsFalse(NameFilter.IsValidFilterExpression(@"[]"));
}
+
+ // Use a shorter name wrapper to make tests more legible
+ private static string DropRoot(string s) => PathUtils.DropPathRoot(s);
+
+ [Test]
+ [Category("Core")]
+ [Platform("Win")]
+ public void DropPathRoot_Windows()
+ {
+ Assert.AreEqual("file.txt", DropRoot(@"\\server\share\file.txt"));
+ Assert.AreEqual("file.txt", DropRoot(@"c:\file.txt"));
+ Assert.AreEqual(@"subdir with spaces\file.txt", DropRoot(@"z:\subdir with spaces\file.txt"));
+ Assert.AreEqual("", DropRoot(@"\\server\share\"));
+ Assert.AreEqual(@"server\share\file.txt", DropRoot(@"\server\share\file.txt"));
+ Assert.AreEqual(@"path\file.txt", DropRoot(@"\\server\share\\path\file.txt"));
+ }
+
+ [Test]
+ [Category("Core")]
+ [Platform(Exclude="Win")]
+ public void DropPathRoot_Posix()
+ {
+ Assert.AreEqual("file.txt", DropRoot("/file.txt"));
+ Assert.AreEqual(@"tmp/file.txt", DropRoot(@"/tmp/file.txt"));
+ Assert.AreEqual(@"tmp\file.txt", DropRoot(@"\tmp\file.txt"));
+ Assert.AreEqual(@"tmp/file.txt", DropRoot(@"\tmp/file.txt"));
+ Assert.AreEqual(@"tmp\file.txt", DropRoot(@"/tmp\file.txt"));
+ Assert.AreEqual("", DropRoot("/"));
+
+ }
+
+ [Test]
+ [TestCase(@"c:\file:+/")]
+ [TestCase(@"c:\file*?")]
+ [TestCase("c:\\file|\"")]
+ [TestCase(@"c:\file<>")]
+ [TestCase(@"c:file")]
+ [TestCase(@"c::file")]
+ [TestCase(@"c:?file")]
+ [TestCase(@"c:+file")]
+ [TestCase(@"cc:file")]
+ [Category("Core")]
+ public void DropPathRoot_DoesNotThrowForInvalidPath(string path)
+ {
+ Assert.DoesNotThrow(() => Console.WriteLine(PathUtils.DropPathRoot(path)));
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Core/StringBuilderPoolTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Core/StringBuilderPoolTests.cs
new file mode 100644
index 000000000..85d8c65a9
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Core/StringBuilderPoolTests.cs
@@ -0,0 +1,77 @@
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Core;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.Core
+{
+ [TestFixture]
+ public class StringBuilderPoolTests
+ {
+ [Test]
+ [Category("Core")]
+ public void RoundTrip()
+ {
+ var pool = new StringBuilderPool();
+ var builder1 = pool.Rent();
+ pool.Return(builder1);
+ var builder2 = pool.Rent();
+ Assert.AreEqual(builder1, builder2);
+ }
+
+ [Test]
+ [Category("Core")]
+ public void ReturnsClears()
+ {
+ var pool = new StringBuilderPool();
+ var builder1 = pool.Rent();
+ builder1.Append("Hello");
+ pool.Return(builder1);
+ Assert.AreEqual(0, builder1.Length);
+ }
+
+ [Test]
+ [Category("Core")]
+ public async Task ThreadSafeAsync()
+ {
+ // use a lot of threads to increase the likelihood of errors
+ var concurrency = 100;
+
+ var pool = new StringBuilderPool();
+ var gate = new TaskCompletionSource();
+ var startedTasks = new Task[concurrency];
+ var completedTasks = new Task[concurrency];
+ for (int i = 0; i < concurrency; i++)
+ {
+ var started = new TaskCompletionSource();
+ startedTasks[i] = started.Task;
+ var captured = i;
+ completedTasks[i] = Task.Run(async () =>
+ {
+ started.SetResult(true);
+ await gate.Task;
+ var builder = pool.Rent();
+ builder.Append("Hello ");
+ builder.Append(captured);
+ var str = builder.ToString();
+ pool.Return(builder);
+ return str;
+ });
+ }
+
+ // make sure all the threads have started
+ await Task.WhenAll(startedTasks);
+
+ // let them all loose at the same time
+ gate.SetResult(true);
+
+ // make sure every thread produces the expected string and hence had its own StringBuilder
+ var results = await Task.WhenAll(completedTasks);
+ for (int i = 0; i < concurrency; i++)
+ {
+ var result = results[i];
+ Assert.AreEqual($"Hello {i}", result);
+ }
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs
new file mode 100644
index 000000000..209ae15d4
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipAsyncTests.cs
@@ -0,0 +1,144 @@
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.GZip;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.GZip
+{
+
+
+ [TestFixture]
+ public class GZipAsyncTests
+ {
+ [Test]
+ [Category("GZip")]
+ [Category("Async")]
+ public async Task SmallBufferDecompressionAsync([Values(0, 1, 3)] int seed)
+ {
+ var outputBufferSize = 100000;
+ var outputBuffer = new byte[outputBufferSize];
+ var inputBuffer = Utils.GetDummyBytes(outputBufferSize * 4, seed);
+
+#if NETCOREAPP3_1_OR_GREATER
+ await using var msGzip = new MemoryStream();
+ await using (var gzos = new GZipOutputStream(msGzip){IsStreamOwner = false})
+ {
+ await gzos.WriteAsync(inputBuffer, 0, inputBuffer.Length);
+ }
+
+ msGzip.Seek(0, SeekOrigin.Begin);
+
+ using (var gzis = new GZipInputStream(msGzip))
+ await using (var msRaw = new MemoryStream())
+ {
+ int readOut;
+ while ((readOut = gzis.Read(outputBuffer, 0, outputBuffer.Length)) > 0)
+ {
+ await msRaw.WriteAsync(outputBuffer, 0, readOut);
+ }
+
+ var resultBuffer = msRaw.ToArray();
+ for (var i = 0; i < resultBuffer.Length; i++)
+ {
+ Assert.AreEqual(inputBuffer[i], resultBuffer[i]);
+ }
+ }
+#else
+ using var msGzip = new MemoryStream();
+ using (var gzos = new GZipOutputStream(msGzip){IsStreamOwner = false})
+ {
+ await gzos.WriteAsync(inputBuffer, 0, inputBuffer.Length);
+ }
+
+ msGzip.Seek(0, SeekOrigin.Begin);
+
+ using (var gzis = new GZipInputStream(msGzip))
+ using (var msRaw = new MemoryStream())
+ {
+ int readOut;
+ while ((readOut = gzis.Read(outputBuffer, 0, outputBuffer.Length)) > 0)
+ {
+ await msRaw.WriteAsync(outputBuffer, 0, readOut);
+ }
+
+ var resultBuffer = msRaw.ToArray();
+ for (var i = 0; i < resultBuffer.Length; i++)
+ {
+ Assert.AreEqual(inputBuffer[i], resultBuffer[i]);
+ }
+ }
+#endif
+ }
+
+ ///
+ /// Basic compress/decompress test
+ ///
+ [Test]
+ [Category("GZip")]
+ [Category("Async")]
+ public async Task OriginalFilenameAsync()
+ {
+ var content = "FileContents";
+
+#if NETCOREAPP3_1_OR_GREATER
+ await using var ms = new MemoryStream();
+ await using (var outStream = new GZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ outStream.FileName = "/path/to/file.ext";
+ outStream.Write(Encoding.ASCII.GetBytes(content));
+ }
+#else
+ var ms = new MemoryStream();
+ var outStream = new GZipOutputStream(ms){ IsStreamOwner = false };
+ outStream.FileName = "/path/to/file.ext";
+ var bytes = Encoding.ASCII.GetBytes(content);
+ outStream.Write(bytes, 0, bytes.Length);
+ await outStream.FinishAsync(System.Threading.CancellationToken.None);
+ outStream.Dispose();
+
+#endif
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var inStream = new GZipInputStream(ms))
+ {
+ var readBuffer = new byte[content.Length];
+ inStream.Read(readBuffer, 0, readBuffer.Length);
+ Assert.AreEqual(content, Encoding.ASCII.GetString(readBuffer));
+ Assert.AreEqual("file.ext", inStream.GetFilename());
+ }
+ }
+
+ ///
+ /// Test creating an empty gzip stream using async
+ ///
+ [Test]
+ [Category("GZip")]
+ [Category("Async")]
+ public async Task EmptyGZipStreamAsync()
+ {
+#if NETCOREAPP3_1_OR_GREATER
+ await using var ms = new MemoryStream();
+ await using (var outStream = new GZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ // No content
+ }
+#else
+ var ms = new MemoryStream();
+ var outStream = new GZipOutputStream(ms){ IsStreamOwner = false };
+ await outStream.FinishAsync(System.Threading.CancellationToken.None);
+ outStream.Dispose();
+
+#endif
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var inStream = new GZipInputStream(ms))
+ using (var reader = new StreamReader(inStream))
+ {
+ var content = await reader.ReadToEndAsync();
+ Assert.IsEmpty(content);
+ }
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
index ef63c2997..3241fd134 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs
@@ -3,6 +3,9 @@
using NUnit.Framework;
using System;
using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tests.GZip
{
@@ -22,9 +25,7 @@ public void TestGZip()
var ms = new MemoryStream();
var outStream = new GZipOutputStream(ms);
- byte[] buf = new byte[100000];
- var rnd = new Random();
- rnd.NextBytes(buf);
+ var buf = Utils.GetDummyBytes(size: 100000);
outStream.Write(buf, 0, buf.Length);
outStream.Flush();
@@ -62,18 +63,47 @@ public void TestGZip()
[Test]
[Category("GZip")]
public void DelayedHeaderWriteNoData()
+ {
+ using var ms = new MemoryStream();
+ Assert.Zero(ms.Length);
+
+ using (new GZipOutputStream(ms))
+ {
+ Assert.Zero(ms.Length);
+ }
+
+ Assert.NotZero(ms.ToArray().Length);
+ }
+
+
+ ///
+ /// Variant of DelayedHeaderWriteNoData testing flushing for https://github.com/icsharpcode/SharpZipLib/issues/382
+ ///
+ [Test]
+ [Category("GZip")]
+ public void DelayedHeaderWriteFlushNoData()
{
var ms = new MemoryStream();
Assert.AreEqual(0, ms.Length);
- using (GZipOutputStream outStream = new GZipOutputStream(ms))
+ using (GZipOutputStream outStream = new GZipOutputStream(ms) { IsStreamOwner = false })
{
- Assert.AreEqual(0, ms.Length);
+ // #382 - test flushing the stream before writing to it.
+ outStream.Flush();
}
- byte[] data = ms.ToArray();
+ ms.Seek(0, SeekOrigin.Begin);
- Assert.IsTrue(data.Length > 0);
+ // Test that the gzip stream can be read
+ var readStream = new MemoryStream();
+ using (GZipInputStream inStream = new GZipInputStream(ms))
+ {
+ inStream.CopyTo(readStream);
+ }
+
+ byte[] data = readStream.ToArray();
+
+ Assert.That(data, Is.Empty, "Should not have any decompressed data");
}
///
@@ -99,6 +129,38 @@ public void DelayedHeaderWriteWithData()
Assert.IsTrue(data.Length > 0);
}
+ ///
+ /// variant of DelayedHeaderWriteWithData to test https://github.com/icsharpcode/SharpZipLib/issues/382
+ ///
+ [Test]
+ [Category("GZip")]
+ public void DelayedHeaderWriteFlushWithData()
+ {
+ var ms = new MemoryStream();
+ Assert.AreEqual(0, ms.Length);
+ using (GZipOutputStream outStream = new GZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ Assert.AreEqual(0, ms.Length);
+
+ // #382 - test flushing the stream before writing to it.
+ outStream.Flush();
+ outStream.WriteByte(45);
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ // Test that the gzip stream can be read
+ var readStream = new MemoryStream();
+ using (GZipInputStream inStream = new GZipInputStream(ms))
+ {
+ inStream.CopyTo(readStream);
+ }
+
+ // Check that the data was read
+ byte[] data = readStream.ToArray();
+ CollectionAssert.AreEqual(new byte[] { 45 }, data, "Decompressed data should match initial data");
+ }
+
[Test]
[Category("GZip")]
public void ZeroLengthInputStream()
@@ -196,7 +258,7 @@ public void DoubleClose()
s.Close();
memStream = new TrackedMemoryStream();
- using (GZipOutputStream no2 = new GZipOutputStream(memStream))
+ using (new GZipOutputStream(memStream))
{
s.Close();
}
@@ -209,14 +271,7 @@ public void WriteAfterFinish()
var s = new GZipOutputStream(memStream);
s.Finish();
- try
- {
- s.WriteByte(7);
- Assert.Fail("Write should fail");
- }
- catch
- {
- }
+ Assert.Throws(() => s.WriteByte(value: 7), "Write should fail");
}
[Test]
@@ -226,14 +281,7 @@ public void WriteAfterClose()
var s = new GZipOutputStream(memStream);
s.Close();
- try
- {
- s.WriteByte(7);
- Assert.Fail("Write should fail");
- }
- catch
- {
- }
+ Assert.Throws(() => s.WriteByte(value: 7), "Write should fail");
}
///
@@ -247,9 +295,7 @@ public void TrailingGarbage()
var outStream = new GZipOutputStream(ms);
// input buffer to be compressed
- byte[] buf = new byte[100000];
- var rnd = new Random();
- rnd.NextBytes(buf);
+ var buf = Utils.GetDummyBytes(size: 100000, seed: 3);
// compress input buffer
outStream.Write(buf, 0, buf.Length);
@@ -257,9 +303,7 @@ public void TrailingGarbage()
outStream.Finish();
// generate random trailing garbage and add to the compressed stream
- byte[] garbage = new byte[4096];
- rnd.NextBytes(garbage);
- ms.Write(garbage, 0, garbage.Length);
+ Utils.WriteDummyData(ms, size: 4096, seed: 4);
// rewind the concatenated stream
ms.Seek(0, SeekOrigin.Begin);
@@ -272,7 +316,7 @@ public void TrailingGarbage()
int count = buf2.Length;
while (true)
{
- int numRead = inStream.Read(buf2, currentIndex, count);
+ var numRead = inStream.Read(buf2, currentIndex, count);
if (numRead <= 0)
{
break;
@@ -282,7 +326,7 @@ public void TrailingGarbage()
}
/* ASSERT */
- Assert.AreEqual(0, count);
+ Assert.Zero(count);
for (int i = 0; i < buf.Length; ++i)
{
Assert.AreEqual(buf2[i], buf[i]);
@@ -301,9 +345,7 @@ public void FlushToUnderlyingStream()
var ms = new MemoryStream();
var outStream = new GZipOutputStream(ms);
- byte[] buf = new byte[100000];
- var rnd = new Random();
- rnd.NextBytes(buf);
+ byte[] buf = Utils.GetDummyBytes(size: 100000);
outStream.Write(buf, 0, buf.Length);
// Flush output stream but don't finish it yet
@@ -346,52 +388,35 @@ public void FlushToUnderlyingStream()
[Test]
[Category("GZip")]
- public void SmallBufferDecompression()
+ public void SmallBufferDecompression([Values(0, 1, 3)] int seed)
{
var outputBufferSize = 100000;
- var inputBufferSize = outputBufferSize * 4;
-
var outputBuffer = new byte[outputBufferSize];
- var inputBuffer = new byte[inputBufferSize];
-
- using (var msGzip = new MemoryStream())
+ var inputBuffer = Utils.GetDummyBytes(outputBufferSize * 4, seed);
+
+ using var msGzip = new MemoryStream();
+ using (var gzos = new GZipOutputStream(msGzip){IsStreamOwner = false})
{
- using (var gzos = new GZipOutputStream(msGzip))
+ gzos.Write(inputBuffer, 0, inputBuffer.Length);
+ }
+
+ msGzip.Seek(0, SeekOrigin.Begin);
+
+ using (var gzis = new GZipInputStream(msGzip))
+ using (var msRaw = new MemoryStream())
+ {
+ int readOut;
+ while ((readOut = gzis.Read(outputBuffer, 0, outputBuffer.Length)) > 0)
{
- gzos.IsStreamOwner = false;
-
- var rnd = new Random(0);
- rnd.NextBytes(inputBuffer);
- gzos.Write(inputBuffer, 0, inputBuffer.Length);
-
- gzos.Flush();
- gzos.Finish();
+ msRaw.Write(outputBuffer, 0, readOut);
}
- msGzip.Seek(0, SeekOrigin.Begin);
-
-
- using (var gzis = new GZipInputStream(msGzip))
- using (var msRaw = new MemoryStream())
+ var resultBuffer = msRaw.ToArray();
+ for (var i = 0; i < resultBuffer.Length; i++)
{
-
- int readOut;
- while ((readOut = gzis.Read(outputBuffer, 0, outputBuffer.Length)) > 0)
- {
- msRaw.Write(outputBuffer, 0, readOut);
- }
-
- var resultBuffer = msRaw.ToArray();
-
- for (var i = 0; i < resultBuffer.Length; i++)
- {
- Assert.AreEqual(inputBuffer[i], resultBuffer[i]);
- }
-
-
+ Assert.AreEqual(inputBuffer[i], resultBuffer[i]);
}
}
-
}
///
@@ -403,18 +428,13 @@ public void SmallBufferDecompression()
///
[Test]
[Category("Zip")]
- public void ShouldGracefullyHandleReadingANonReableStream()
+ public void ShouldGracefullyHandleReadingANonReadableStream()
{
MemoryStream ms = new SelfClosingStream();
using (var gzos = new GZipOutputStream(ms))
{
gzos.IsStreamOwner = false;
-
- byte[] buf = new byte[100000];
- var rnd = new Random();
- rnd.NextBytes(buf);
-
- gzos.Write(buf, 0, buf.Length);
+ Utils.WriteDummyData(gzos, size: 100000);
}
ms.Seek(0, SeekOrigin.Begin);
@@ -451,5 +471,37 @@ public void ReadWriteThroughput()
output: w => new GZipOutputStream(w)
);
}
+
+ ///
+ /// Basic compress/decompress test
+ ///
+ [Test]
+ [Category("GZip")]
+ public void OriginalFilename()
+ {
+ var content = "FileContents";
+
+
+ using var ms = new MemoryStream();
+ using (var outStream = new GZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ outStream.FileName = "/path/to/file.ext";
+
+ var writeBuffer = Encoding.ASCII.GetBytes(content);
+ outStream.Write(writeBuffer, 0, writeBuffer.Length);
+ outStream.Flush();
+ outStream.Finish();
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var inStream = new GZipInputStream(ms))
+ {
+ var readBuffer = new byte[content.Length];
+ inStream.Read(readBuffer, 0, readBuffer.Length);
+ Assert.AreEqual(content, Encoding.ASCII.GetString(readBuffer));
+ Assert.AreEqual("file.ext", inStream.GetFilename());
+ }
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.Tests.csproj b/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.Tests.csproj
index fa214385c..73ef2eb0d 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.Tests.csproj
+++ b/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.Tests.csproj
@@ -2,21 +2,20 @@
Library
- netcoreapp2.0;net46
+ net6.0;net462
+ true
+ ..\..\assets\ICSharpCode.SharpZipLib.snk
+ true
+ 8.0
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -29,4 +28,10 @@
+
+
+ ICSharpCode.SharpZipLib.snk
+
+
+
diff --git a/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.snk b/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.snk
deleted file mode 100644
index 58cf194df..000000000
Binary files a/test/ICSharpCode.SharpZipLib.Tests/ICSharpCode.SharpZipLib.snk and /dev/null differ
diff --git a/test/ICSharpCode.SharpZipLib.Tests/NuGet.config b/test/ICSharpCode.SharpZipLib.Tests/NuGet.config
deleted file mode 100644
index 7be9c71ec..000000000
--- a/test/ICSharpCode.SharpZipLib.Tests/NuGet.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Serialization/SerializationTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Serialization/SerializationTests.cs
index 6118022e4..cb3344ae4 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Serialization/SerializationTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Serialization/SerializationTests.cs
@@ -23,6 +23,7 @@ public class SerializationTests
[Category("Serialization")]
[TestCase(typeof(BZip2Exception))]
[TestCase(typeof(GZipException))]
+ [TestCase(typeof(InvalidHeaderException))]
[TestCase(typeof(InvalidNameException))]
[TestCase(typeof(LzwException))]
[TestCase(typeof(SharpZipBaseException))]
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarArchiveTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarArchiveTests.cs
new file mode 100644
index 000000000..d9e32194a
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarArchiveTests.cs
@@ -0,0 +1,91 @@
+using System.IO;
+using System.Text;
+using ICSharpCode.SharpZipLib.Core;
+using ICSharpCode.SharpZipLib.Tar;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using static ICSharpCode.SharpZipLib.Tests.TestSupport.Utils;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.Tar
+{
+ [TestFixture]
+ public class TarArchiveTests
+ {
+ [Test]
+ [Category("Tar")]
+ [Category("CreatesTempFile")]
+ [TestCase("output")]
+ [TestCase("output/")]
+ [TestCase(@"output\", IncludePlatform = "Win")]
+ public void ExtractingContentsWithNonTraversalPathSucceeds(string outputDir)
+ {
+ Assert.DoesNotThrow(() => ExtractTarOK(outputDir, "file", allowTraverse: false));
+ }
+
+ [Test]
+ [Category("Tar")]
+ [Category("CreatesTempFile")]
+ public void ExtractingContentsWithExplicitlyAllowedTraversalPathSucceeds()
+ {
+ Assert.DoesNotThrow(() => ExtractTarOK("output", "../file", allowTraverse: true));
+ }
+
+ [Test]
+ [Category("Tar")]
+ [Category("CreatesTempFile")]
+ [TestCase("output", "../file")]
+ [TestCase("output/", "../file")]
+ [TestCase("output", "../output.txt")]
+ public void ExtractingContentsWithDisallowedPathsFails(string outputDir, string fileName)
+ {
+ Assert.Throws(() => ExtractTarOK(outputDir, fileName, allowTraverse: false));
+ }
+
+ [Test]
+ [Category("Tar")]
+ [Category("CreatesTempFile")]
+ [Platform(Include = "Win", Reason = "Backslashes are only treated as path separators on windows")]
+ [TestCase(@"output\", @"..\file")]
+ [TestCase(@"output/", @"..\file")]
+ [TestCase("output", @"..\output.txt")]
+ [TestCase(@"output\", @"..\output.txt")]
+ public void ExtractingContentsOnWindowsWithDisallowedPathsFails(string outputDir, string fileName)
+ {
+ Assert.Throws(() => ExtractTarOK(outputDir, fileName, allowTraverse: false));
+ }
+
+ public void ExtractTarOK(string outputDir, string fileName, bool allowTraverse)
+ {
+ var fileContent = Encoding.UTF8.GetBytes("file content");
+ using var tempDir = GetTempDir();
+
+ var tempPath = tempDir.FullName;
+ var extractPath = Path.Combine(tempPath, outputDir);
+ var expectedOutputFile = Path.Combine(extractPath, fileName);
+
+ using var archiveStream = new MemoryStream();
+
+ Directory.CreateDirectory(extractPath);
+
+ using (var tos = new TarOutputStream(archiveStream, Encoding.UTF8){IsStreamOwner = false})
+ {
+ var entry = TarEntry.CreateTarEntry(fileName);
+ entry.Size = fileContent.Length;
+ tos.PutNextEntry(entry);
+ tos.Write(fileContent, 0, fileContent.Length);
+ tos.CloseEntry();
+ }
+
+ archiveStream.Position = 0;
+
+ using (var ta = TarArchive.CreateInputTarArchive(archiveStream, Encoding.UTF8))
+ {
+ ta.ProgressMessageEvent += (archive, entry, message)
+ => TestContext.WriteLine($"{entry.Name} {entry.Size} {message}");
+ ta.ExtractContents(extractPath, allowTraverse);
+ }
+
+ Assert.That(File.Exists(expectedOutputFile));
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarBufferTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarBufferTests.cs
new file mode 100644
index 000000000..3974ffb5b
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarBufferTests.cs
@@ -0,0 +1,125 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Tar;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.Tar
+{
+ [TestFixture]
+ public class TarBufferTests
+ {
+ [Test]
+ public void TestSimpleReadWrite()
+ {
+ var ms = new MemoryStream();
+ var reader = TarBuffer.CreateInputTarBuffer(ms, 1);
+ var writer = TarBuffer.CreateOutputTarBuffer(ms, 1);
+ writer.IsStreamOwner = false;
+
+ var block = new byte[TarBuffer.BlockSize];
+ var r = new Random();
+ r.NextBytes(block);
+
+ writer.WriteBlock(block);
+ writer.WriteBlock(block);
+ writer.WriteBlock(block);
+ writer.Close();
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ var block0 = reader.ReadBlock();
+ var block1 = reader.ReadBlock();
+ var block2 = reader.ReadBlock();
+ Assert.AreEqual(block, block0);
+ Assert.AreEqual(block, block1);
+ Assert.AreEqual(block, block2);
+ writer.Close();
+ }
+
+ [Test]
+ public void TestSkipBlock()
+ {
+ var ms = new MemoryStream();
+ var reader = TarBuffer.CreateInputTarBuffer(ms, 1);
+ var writer = TarBuffer.CreateOutputTarBuffer(ms, 1);
+ writer.IsStreamOwner = false;
+
+ var block0 = new byte[TarBuffer.BlockSize];
+ var block1 = new byte[TarBuffer.BlockSize];
+ var r = new Random();
+ r.NextBytes(block0);
+ r.NextBytes(block1);
+
+ writer.WriteBlock(block0);
+ writer.WriteBlock(block1);
+ writer.Close();
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ reader.SkipBlock();
+ var block = reader.ReadBlock();
+ Assert.AreEqual(block, block1);
+ writer.Close();
+ }
+
+ [Test]
+ public async Task TestSimpleReadWriteAsync()
+ {
+ var ms = new MemoryStream();
+ var reader = TarBuffer.CreateInputTarBuffer(ms, 1);
+ var writer = TarBuffer.CreateOutputTarBuffer(ms, 1);
+ writer.IsStreamOwner = false;
+
+ var block = new byte[TarBuffer.BlockSize];
+ var r = new Random();
+ r.NextBytes(block);
+
+ await writer.WriteBlockAsync(block, CancellationToken.None);
+ await writer.WriteBlockAsync(block, CancellationToken.None);
+ await writer.WriteBlockAsync(block, CancellationToken.None);
+ await writer.CloseAsync(CancellationToken.None);
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ var block0 = new byte[TarBuffer.BlockSize];
+ await reader.ReadBlockIntAsync(block0, CancellationToken.None, true);
+ var block1 = new byte[TarBuffer.BlockSize];
+ await reader.ReadBlockIntAsync(block1, CancellationToken.None, true);
+ var block2 = new byte[TarBuffer.BlockSize];
+ await reader.ReadBlockIntAsync(block2, CancellationToken.None, true);
+ Assert.AreEqual(block, block0);
+ Assert.AreEqual(block, block1);
+ Assert.AreEqual(block, block2);
+ await writer.CloseAsync(CancellationToken.None);
+ }
+
+ [Test]
+ public async Task TestSkipBlockAsync()
+ {
+ var ms = new MemoryStream();
+ var reader = TarBuffer.CreateInputTarBuffer(ms, 1);
+ var writer = TarBuffer.CreateOutputTarBuffer(ms, 1);
+ writer.IsStreamOwner = false;
+
+ var block0 = new byte[TarBuffer.BlockSize];
+ var block1 = new byte[TarBuffer.BlockSize];
+ var r = new Random();
+ r.NextBytes(block0);
+ r.NextBytes(block1);
+
+ await writer.WriteBlockAsync(block0, CancellationToken.None);
+ await writer.WriteBlockAsync(block1, CancellationToken.None);
+ await writer.CloseAsync(CancellationToken.None);
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ await reader.SkipBlockAsync(CancellationToken.None);
+ var block = new byte[TarBuffer.BlockSize];
+ await reader.ReadBlockIntAsync(block, CancellationToken.None, true);
+ Assert.AreEqual(block, block1);
+ await writer.CloseAsync(CancellationToken.None);
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarInputStreamTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarInputStreamTests.cs
new file mode 100644
index 000000000..83457834f
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarInputStreamTests.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Tar;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.Tar
+{
+ public class TarInputStreamTests
+ {
+ [Test]
+ public void TestRead()
+ {
+ var entryBytes = new byte[2000];
+ var r = new Random();
+ r.NextBytes(entryBytes);
+ using var ms = new MemoryStream();
+ using (var tos = new TarOutputStream(ms, Encoding.UTF8) { IsStreamOwner = false })
+ {
+ var e = TarEntry.CreateTarEntry("some entry");
+ e.Size = entryBytes.Length;
+ tos.PutNextEntry(e);
+ tos.Write(entryBytes, 0, entryBytes.Length);
+ tos.CloseEntry();
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using var tis = new TarInputStream(ms, Encoding.UTF8);
+ var entry = tis.GetNextEntry();
+ Assert.AreEqual("some entry", entry.Name);
+ var buffer = new byte[1000]; // smaller than 2 blocks
+ var read0 = tis.Read(buffer, 0, buffer.Length);
+ Assert.AreEqual(1000, read0);
+ Assert.AreEqual(entryBytes.AsSpan(0, 1000).ToArray(), buffer);
+
+ var read1 = tis.Read(buffer, 0, 5);
+ Assert.AreEqual(5, read1);
+ Assert.AreEqual(entryBytes.AsSpan(1000, 5).ToArray(), buffer.AsSpan().Slice(0, 5).ToArray());
+
+ var read2 = tis.Read(buffer, 0, 20);
+ Assert.AreEqual(20, read2);
+ Assert.AreEqual(entryBytes.AsSpan(1005, 20).ToArray(), buffer.AsSpan().Slice(0, 20).ToArray());
+
+ var read3 = tis.Read(buffer, 0, 975);
+ Assert.AreEqual(975, read3);
+ Assert.AreEqual(entryBytes.AsSpan(1025, 975).ToArray(), buffer.AsSpan().Slice(0, 975).ToArray());
+ }
+
+ [Test]
+ public async Task TestReadAsync()
+ {
+ var entryBytes = new byte[2000];
+ var r = new Random();
+ r.NextBytes(entryBytes);
+ using var ms = new MemoryStream();
+ using (var tos = new TarOutputStream(ms, Encoding.UTF8) { IsStreamOwner = false })
+ {
+ var e = TarEntry.CreateTarEntry("some entry");
+ e.Size = entryBytes.Length;
+ await tos.PutNextEntryAsync(e, CancellationToken.None);
+ await tos.WriteAsync(entryBytes, 0, entryBytes.Length);
+ await tos.CloseEntryAsync(CancellationToken.None);
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using var tis = new TarInputStream(ms, Encoding.UTF8);
+ var entry = await tis.GetNextEntryAsync(CancellationToken.None);
+ Assert.AreEqual("some entry", entry.Name);
+ var buffer = new byte[1000]; // smaller than 2 blocks
+ var read0 = await tis.ReadAsync(buffer, 0, buffer.Length);
+ Assert.AreEqual(1000, read0);
+ Assert.AreEqual(entryBytes.AsSpan(0, 1000).ToArray(), buffer);
+
+ var read1 = await tis.ReadAsync(buffer, 0, 5);
+ Assert.AreEqual(5, read1);
+ Assert.AreEqual(entryBytes.AsSpan(1000, 5).ToArray(), buffer.AsSpan().Slice(0, 5).ToArray());
+
+ var read2 = await tis.ReadAsync(buffer, 0, 20);
+ Assert.AreEqual(20, read2);
+ Assert.AreEqual(entryBytes.AsSpan(1005, 20).ToArray(), buffer.AsSpan().Slice(0, 20).ToArray());
+
+ var read3 = await tis.ReadAsync(buffer, 0, 975);
+ Assert.AreEqual(975, read3);
+ Assert.AreEqual(entryBytes.AsSpan(1025, 975).ToArray(), buffer.AsSpan().Slice(0, 975).ToArray());
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs
index c7945f142..c6a35ff08 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs
@@ -4,6 +4,10 @@
using NUnit.Framework;
using System;
using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework.Internal;
namespace ICSharpCode.SharpZipLib.Tests.Tar
{
@@ -20,6 +24,12 @@ private void EntryCounter(TarArchive archive, TarEntry entry, string message)
entryCount++;
}
+ [SetUp]
+ public void Setup()
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ }
+
///
/// Test that an empty archive can be created and when read has 0 entries in it
///
@@ -28,25 +38,25 @@ private void EntryCounter(TarArchive archive, TarEntry entry, string message)
public void EmptyTar()
{
var ms = new MemoryStream();
- int recordSize = 0;
- using (TarArchive tarOut = TarArchive.CreateOutputTarArchive(ms))
+ int recordSize;
+ using (var tarOut = TarArchive.CreateOutputTarArchive(ms))
{
recordSize = tarOut.RecordSize;
}
Assert.IsTrue(ms.GetBuffer().Length > 0, "Archive size must be > zero");
- Assert.AreEqual(ms.GetBuffer().Length % recordSize, 0, "Archive size must be a multiple of record size");
+ Assert.Zero(ms.GetBuffer().Length % recordSize, "Archive size must be a multiple of record size");
var ms2 = new MemoryStream();
ms2.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
ms2.Seek(0, SeekOrigin.Begin);
- using (TarArchive tarIn = TarArchive.CreateInputTarArchive(ms2))
+ using (var tarIn = TarArchive.CreateInputTarArchive(ms2, nameEncoding: null))
{
entryCount = 0;
tarIn.ProgressMessageEvent += EntryCounter;
tarIn.ListContents();
- Assert.AreEqual(0, entryCount, "Expected 0 tar entries");
+ Assert.Zero(entryCount, "Expected 0 tar entries");
}
}
@@ -57,27 +67,24 @@ public void EmptyTar()
[Category("Tar")]
public void BlockFactorHandling()
{
- const int MinimumBlockFactor = 1;
- const int MaximumBlockFactor = 64;
- const int FillFactor = 2;
+ const int minimumBlockFactor = 1;
+ const int maximumBlockFactor = 64;
+ const int fillFactor = 2;
- for (int factor = MinimumBlockFactor; factor < MaximumBlockFactor; ++factor)
+ for (var factor = minimumBlockFactor; factor < maximumBlockFactor; ++factor)
{
var ms = new MemoryStream();
- using (TarOutputStream tarOut = new TarOutputStream(ms, factor))
+ using (var tarOut = new TarOutputStream(ms, factor, nameEncoding: null))
{
- TarEntry entry = TarEntry.CreateTarEntry("TestEntry");
- entry.Size = (TarBuffer.BlockSize * factor * FillFactor);
+ var entry = TarEntry.CreateTarEntry("TestEntry");
+ entry.Size = TarBuffer.BlockSize * factor * fillFactor;
tarOut.PutNextEntry(entry);
- byte[] buffer = new byte[TarBuffer.BlockSize];
-
- var r = new Random();
- r.NextBytes(buffer);
+ var buffer = Utils.GetDummyBytes(TarBuffer.BlockSize);
// Last block is a partial one
- for (int i = 0; i < factor * FillFactor; ++i)
+ for (var i = 0; i < factor * fillFactor; ++i)
{
tarOut.Write(buffer, 0, buffer.Length);
}
@@ -87,7 +94,7 @@ public void BlockFactorHandling()
Assert.IsNotNull(tarData, "Data written is null");
// Blocks = Header + Data Blocks + Zero block + Record trailer
- int usedBlocks = 1 + (factor * FillFactor) + 2;
+ int usedBlocks = 1 + (factor * fillFactor) + 2;
int totalBlocks = usedBlocks + (factor - 1);
totalBlocks /= factor;
totalBlocks *= factor;
@@ -95,24 +102,22 @@ public void BlockFactorHandling()
Assert.AreEqual(TarBuffer.BlockSize * totalBlocks, tarData.Length, "Tar file should contain {0} blocks in length",
totalBlocks);
- if (usedBlocks < totalBlocks)
+ if (usedBlocks >= totalBlocks) continue;
+
+ // Start at first byte after header.
+ var byteIndex = TarBuffer.BlockSize * ((factor * fillFactor) + 1);
+ while (byteIndex < tarData.Length)
{
- // Start at first byte after header.
- int byteIndex = TarBuffer.BlockSize * ((factor * FillFactor) + 1);
- while (byteIndex < tarData.Length)
- {
- int blockNumber = byteIndex / TarBuffer.BlockSize;
- int offset = blockNumber % TarBuffer.BlockSize;
- Assert.AreEqual(0, tarData[byteIndex],
- string.Format("Trailing block data should be null iteration {0} block {1} offset {2} index {3}",
- factor,
- blockNumber, offset, byteIndex));
- byteIndex += 1;
- }
+ var blockNumber = byteIndex / TarBuffer.BlockSize;
+ var offset = blockNumber % TarBuffer.BlockSize;
+ Assert.AreEqual(0, tarData[byteIndex],
+ "Trailing block data should be null iteration {0} block {1} offset {2} index {3}",
+ factor, blockNumber, offset, byteIndex);
+ byteIndex += 1;
}
}
}
-
+
///
/// Check that the tar trailer only contains nulls.
///
@@ -120,13 +125,13 @@ public void BlockFactorHandling()
[Category("Tar")]
public void TrailerContainsNulls()
{
- const int TestBlockFactor = 3;
+ const int testBlockFactor = 3;
- for (int iteration = 0; iteration < TestBlockFactor * 2; ++iteration)
+ for (int iteration = 0; iteration < testBlockFactor * 2; ++iteration)
{
var ms = new MemoryStream();
- using (TarOutputStream tarOut = new TarOutputStream(ms, TestBlockFactor))
+ using (TarOutputStream tarOut = new TarOutputStream(ms, testBlockFactor, null))
{
TarEntry entry = TarEntry.CreateTarEntry("TestEntry");
if (iteration > 0)
@@ -160,9 +165,9 @@ public void TrailerContainsNulls()
// Blocks = Header + Data Blocks + Zero block + Record trailer
int usedBlocks = 1 + iteration + 2;
- int totalBlocks = usedBlocks + (TestBlockFactor - 1);
- totalBlocks /= TestBlockFactor;
- totalBlocks *= TestBlockFactor;
+ int totalBlocks = usedBlocks + (testBlockFactor - 1);
+ totalBlocks /= testBlockFactor;
+ totalBlocks *= testBlockFactor;
Assert.AreEqual(TarBuffer.BlockSize * totalBlocks, tarData.Length,
string.Format("Tar file should be {0} blocks in length", totalBlocks));
@@ -188,7 +193,7 @@ public void TrailerContainsNulls()
private void TryLongName(string name)
{
var ms = new MemoryStream();
- using (TarOutputStream tarOut = new TarOutputStream(ms))
+ using (TarOutputStream tarOut = new TarOutputStream(ms, nameEncoding: null))
{
DateTime modTime = DateTime.Now;
@@ -200,7 +205,7 @@ private void TryLongName(string name)
ms2.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
ms2.Seek(0, SeekOrigin.Begin);
- using (TarInputStream tarIn = new TarInputStream(ms2))
+ using (TarInputStream tarIn = new TarInputStream(ms2, nameEncoding: null))
{
TarEntry nextEntry = tarIn.GetNextEntry();
@@ -283,20 +288,15 @@ public void ExtendedHeaderLongName()
var buffer = new byte[2560];
var truncated = Convert.FromBase64String(input64);
Array.Copy(truncated, buffer, truncated.Length);
- truncated = null;
-
- using (var ms = new MemoryStream(buffer))
- using (var tis = new TarInputStream(ms))
- {
- var entry = tis.GetNextEntry();
- Assert.IsNotNull(entry, "Entry is null");
-
- Assert.IsNotNull(entry.Name, "Entry name is null");
- Assert.AreEqual(expectedName.Length, entry.Name.Length, $"Entry name is truncated to {entry.Name.Length} bytes.");
-
- Assert.AreEqual(expectedName, entry.Name, "Entry name does not match expected value");
- }
+ using var ms = new MemoryStream(buffer);
+ using var tis = new TarInputStream(ms, nameEncoding: null);
+ var entry = tis.GetNextEntry();
+
+ Assert.IsNotNull(entry, "Entry is null");
+ Assert.IsNotNull(entry.Name, "Entry name is null");
+ Assert.AreEqual(expectedName.Length, entry.Name.Length, $"Entry name is truncated to {entry.Name.Length} bytes.");
+ Assert.AreEqual(expectedName, entry.Name, "Entry name does not match expected value");
}
///
@@ -387,11 +387,9 @@ public void HeaderEquality()
public void Checksum()
{
var ms = new MemoryStream();
- using (TarOutputStream tarOut = new TarOutputStream(ms))
+ using (var tarOut = new TarOutputStream(ms, nameEncoding: null))
{
- DateTime modTime = DateTime.Now;
-
- TarEntry entry = TarEntry.CreateTarEntry("TestEntry");
+ var entry = TarEntry.CreateTarEntry("TestEntry");
entry.TarHeader.Mode = 12345;
tarOut.PutNextEntry(entry);
@@ -402,7 +400,7 @@ public void Checksum()
ms2.Seek(0, SeekOrigin.Begin);
TarEntry nextEntry;
- using (TarInputStream tarIn = new TarInputStream(ms2))
+ using (var tarIn = new TarInputStream(ms2, nameEncoding: null))
{
nextEntry = tarIn.GetNextEntry();
Assert.IsTrue(nextEntry.TarHeader.IsChecksumValid, "Checksum should be valid");
@@ -414,20 +412,9 @@ public void Checksum()
ms3.Write(new byte[] { 34 }, 0, 1);
ms3.Seek(0, SeekOrigin.Begin);
- using (TarInputStream tarIn = new TarInputStream(ms3))
+ using (var tarIn = new TarInputStream(ms3, nameEncoding: null))
{
- bool trapped = false;
-
- try
- {
- nextEntry = tarIn.GetNextEntry();
- }
- catch (TarException)
- {
- trapped = true;
- }
-
- Assert.IsTrue(trapped, "Checksum should be invalid");
+ Assert.Throws(() => tarIn.GetNextEntry(), "Checksum should be invalid");
}
}
@@ -442,7 +429,7 @@ public void ValuesPreserved()
TarEntry entry;
DateTime modTime = DateTime.Now;
- using (TarOutputStream tarOut = new TarOutputStream(ms))
+ using (TarOutputStream tarOut = new TarOutputStream(ms, null))
{
entry = TarEntry.CreateTarEntry("TestEntry");
entry.GroupId = 12;
@@ -459,7 +446,7 @@ public void ValuesPreserved()
ms2.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
ms2.Seek(0, SeekOrigin.Begin);
- using (TarInputStream tarIn = new TarInputStream(ms2))
+ using (TarInputStream tarIn = new TarInputStream(ms2, null))
{
TarEntry nextEntry = tarIn.GetNextEntry();
Assert.AreEqual(entry.TarHeader.Checksum, nextEntry.TarHeader.Checksum, "Checksum");
@@ -637,7 +624,7 @@ public void CloningAndUniqueness()
public void OutputStreamOwnership()
{
var memStream = new TrackedMemoryStream();
- var s = new TarOutputStream(memStream);
+ var s = new TarOutputStream(memStream, null);
Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially");
Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially");
@@ -648,7 +635,7 @@ public void OutputStreamOwnership()
Assert.IsTrue(memStream.IsDisposed, "Should be disposed after parent owner close");
memStream = new TrackedMemoryStream();
- s = new TarOutputStream(memStream);
+ s = new TarOutputStream(memStream, null);
Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially");
Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially");
@@ -665,7 +652,7 @@ public void OutputStreamOwnership()
public void InputStreamOwnership()
{
var memStream = new TrackedMemoryStream();
- var s = new TarInputStream(memStream);
+ var s = new TarInputStream(memStream, null);
Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially");
Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially");
@@ -676,7 +663,7 @@ public void InputStreamOwnership()
Assert.IsTrue(memStream.IsDisposed, "Should be disposed after parent owner close");
memStream = new TrackedMemoryStream();
- s = new TarInputStream(memStream);
+ s = new TarInputStream(memStream, null);
Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially");
Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially");
@@ -696,37 +683,35 @@ public void EndBlockHandling()
long outCount, inCount;
- using (var ms = new MemoryStream())
+ using var ms = new MemoryStream();
+ using (var tarOut = TarArchive.CreateOutputTarArchive(ms))
+ using (var dummyFile = Utils.GetDummyFile(dummySize))
{
- using (var tarOut = TarArchive.CreateOutputTarArchive(ms))
- using (var dummyFile = Utils.GetDummyFile(dummySize))
- {
- tarOut.IsStreamOwner = false;
- tarOut.WriteEntry(TarEntry.CreateEntryFromFile(dummyFile.Filename), false);
- }
+ tarOut.IsStreamOwner = false;
+ tarOut.WriteEntry(TarEntry.CreateEntryFromFile(dummyFile), recurse: false);
+ }
- outCount = ms.Position;
- ms.Seek(0, SeekOrigin.Begin);
+ outCount = ms.Position;
+ ms.Seek(0, SeekOrigin.Begin);
- using (var tarIn = TarArchive.CreateInputTarArchive(ms))
- using (var tempDir = new Utils.TempDir())
- {
- tarIn.IsStreamOwner = false;
- tarIn.ExtractContents(tempDir.Fullpath);
+ using (var tarIn = TarArchive.CreateInputTarArchive(ms, nameEncoding: null))
+ using (var tempDir = Utils.GetTempDir())
+ {
+ tarIn.IsStreamOwner = false;
+ tarIn.ExtractContents(tempDir);
- foreach (var file in Directory.GetFiles(tempDir.Fullpath, "*", SearchOption.AllDirectories))
- {
- Console.WriteLine($"Extracted \"{file}\"");
- }
+ foreach (var file in Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories))
+ {
+ Console.WriteLine($"Extracted \"{file}\"");
}
+ }
- inCount = ms.Position;
+ inCount = ms.Position;
- Console.WriteLine($"Output count: {outCount}");
- Console.WriteLine($"Input count: {inCount}");
+ Console.WriteLine($"Output count: {outCount}");
+ Console.WriteLine($"Input count: {inCount}");
- Assert.AreEqual(inCount, outCount, "Bytes read and bytes written should be equal");
- }
+ Assert.AreEqual(inCount, outCount, "Bytes read and bytes written should be equal");
}
[Test]
@@ -735,14 +720,14 @@ public void EndBlockHandling()
[Explicit("Long Running")]
public void WriteThroughput()
{
- const string EntryName = "LargeTarEntry";
+ const string entryName = "LargeTarEntry";
PerformanceTesting.TestWrite(TestDataSize.Large, bs =>
{
- var tos = new TarOutputStream(bs);
+ var tos = new TarOutputStream(bs, nameEncoding: null);
tos.PutNextEntry(new TarEntry(new TarHeader()
{
- Name = EntryName,
+ Name = entryName,
Size = (int)TestDataSize.Large,
}));
return tos;
@@ -759,25 +744,25 @@ public void WriteThroughput()
[Explicit("Long Running")]
public void SingleLargeEntry()
{
- const string EntryName = "LargeTarEntry";
+ const string entryName = "LargeTarEntry";
const TestDataSize dataSize = TestDataSize.Large;
PerformanceTesting.TestReadWrite(
size: dataSize,
input: bs =>
{
- var tis = new TarInputStream(bs);
+ var tis = new TarInputStream(bs, null);
var entry = tis.GetNextEntry();
- Assert.AreEqual(entry.Name, EntryName);
+ Assert.AreEqual(entryName, entry.Name);
return tis;
},
output: bs =>
{
- var tos = new TarOutputStream(bs);
+ var tos = new TarOutputStream(bs, null);
tos.PutNextEntry(new TarEntry(new TarHeader()
{
- Name = EntryName,
+ Name = entryName,
Size = (int)dataSize,
}));
return tos;
@@ -794,44 +779,141 @@ public void SingleLargeEntry()
[Category("Tar")]
public void ExtractingCorruptTarShouldntLeakFiles()
{
+ using var memoryStream = new MemoryStream();
+ //Create a tar.gz in the output stream
+ using (var gzipStream = new GZipOutputStream(memoryStream))
+ {
+ gzipStream.IsStreamOwner = false;
+
+ using (var tarOut = TarArchive.CreateOutputTarArchive(gzipStream))
+ using (var dummyFile = Utils.GetDummyFile(size: 32000))
+ {
+ tarOut.IsStreamOwner = false;
+ tarOut.WriteEntry(TarEntry.CreateEntryFromFile(dummyFile), recurse: false);
+ }
+ }
+
+ // corrupt archive - make sure the file still has more than one block
+ memoryStream.SetLength(16000);
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ // try to extract
+ using (var gzipStream = new GZipInputStream(memoryStream))
+ {
+ gzipStream.IsStreamOwner = false;
+
+ using var tempDir = Utils.GetTempDir();
+ using (var tarIn = TarArchive.CreateInputTarArchive(gzipStream, nameEncoding: null))
+ {
+ tarIn.IsStreamOwner = false;
+ Assert.Throws(() => tarIn.ExtractContents(tempDir));
+ }
+
+ // Try to remove the output directory to check if any file handles are still being held
+ Assert.DoesNotThrow(() => tempDir.Delete());
+
+ Assert.That(tempDir.Exists, Is.False, "Temporary folder should have been removed");
+ }
+ }
+ [TestCase(10, "utf-8")]
+ [TestCase(10, "shift-jis")]
+ [Category("Tar")]
+ public void ParseHeaderWithEncoding(int length, string encodingName)
+ {
+ // U+3042 is Japanese Hiragana
+ // https://unicode.org/charts/PDF/U3040.pdf
+ var name = new string((char)0x3042, length);
+ var header = new TarHeader();
+ var enc = Encoding.GetEncoding(encodingName);
+ byte[] headerbytes = new byte[1024];
+ var encodedName = enc.GetBytes(name);
+ header.Name = name;
+ header.WriteHeader(headerbytes, enc);
+ var reparseHeader = new TarHeader();
+ reparseHeader.ParseBuffer(headerbytes, enc);
+ Assert.AreEqual(name, reparseHeader.Name);
+ // top 100 bytes are name field in tar header
+ for (int i = 0; i < encodedName.Length; i++)
+ {
+ Assert.AreEqual(encodedName[i], headerbytes[i]);
+ }
+ }
+ [TestCase(1, "utf-8")]
+ [TestCase(100, "utf-8")]
+ [TestCase(128, "utf-8")]
+ [TestCase(1, "shift-jis")]
+ [TestCase(100, "shift-jis")]
+ [TestCase(128, "shift-jis")]
+ [Category("Tar")]
+ public async Task StreamWithJapaneseNameAsync(int length, string encodingName)
+ {
+ // U+3042 is Japanese Hiragana
+ // https://unicode.org/charts/PDF/U3040.pdf
+ var entryName = new string((char)0x3042, length);
+ var data = new byte[32];
+ var encoding = Encoding.GetEncoding(encodingName);
using (var memoryStream = new MemoryStream())
{
- //Create a tar.gz in the output stream
- using (var gzipStream = new GZipOutputStream(memoryStream))
+ using (var tarOutput = new TarOutputStream(memoryStream, encoding))
+ {
+ var entry = TarEntry.CreateTarEntry(entryName);
+ entry.Size = 32;
+ tarOutput.PutNextEntry(entry);
+ tarOutput.Write(data, 0, data.Length);
+ }
+
+ using(var memInput = new MemoryStream(memoryStream.ToArray()))
+ using(var inputStream = new TarInputStream(memInput, encoding))
{
- gzipStream.IsStreamOwner = false;
+ var buf = new byte[64];
+ var entry = await inputStream.GetNextEntryAsync(CancellationToken.None);
+ Assert.AreEqual(entryName, entry.Name);
+ var bytesread = await inputStream.ReadAsync(buf, 0, buf.Length, CancellationToken.None);
+ Assert.AreEqual(data.Length, bytesread);
+ }
+ File.WriteAllBytes(Path.Combine(Path.GetTempPath(), $"jpnametest_{length}_{encodingName}.tar"), memoryStream.ToArray());
+ }
+ }
+ ///
+ /// This test could be considered integration test. it creates a tar archive with the root directory specified
+ /// Then extracts it and compares the two folders. This used to fail on unix due to issues with root folder handling
+ /// in the tar archive.
+ ///
+ [Test]
+ [Category("Tar")]
+ public void RootPathIsRespected()
+ {
+ using (var extractDirectory = new TempDir())
+ using (var tarFileName = new TempFile())
+ using (var tempDirectory = new TempDir())
+ {
+ tempDirectory.CreateDummyFile();
- using (var tarOut = TarArchive.CreateOutputTarArchive(gzipStream))
- using (var dummyFile = Utils.GetDummyFile(32000))
+ using (var tarFile = File.Open(tarFileName.FullName, FileMode.Create))
+ {
+ using (var tarOutputStream = TarArchive.CreateOutputTarArchive(tarFile))
{
- tarOut.IsStreamOwner = false;
- tarOut.WriteEntry(TarEntry.CreateEntryFromFile(dummyFile.Filename), false);
+ tarOutputStream.RootPath = tempDirectory.FullName;
+ var entry = TarEntry.CreateEntryFromFile(tempDirectory.FullName);
+ tarOutputStream.WriteEntry(entry, true);
}
}
- // corrupt archive - make sure the file still has more than one block
- memoryStream.SetLength(16000);
- memoryStream.Seek(0, SeekOrigin.Begin);
-
- // try to extract
- using (var gzipStream = new GZipInputStream(memoryStream))
+ using (var file = File.OpenRead(tarFileName.FullName))
{
- string tempDirName;
- gzipStream.IsStreamOwner = false;
-
- using (var tempDir = new Utils.TempDir())
+ using (var archive = TarArchive.CreateInputTarArchive(file, Encoding.UTF8))
{
- tempDirName = tempDir.Fullpath;
-
- using (var tarIn = TarArchive.CreateInputTarArchive(gzipStream))
- {
- tarIn.IsStreamOwner = false;
- Assert.Throws(() => tarIn.ExtractContents(tempDir.Fullpath));
- }
+ archive.ExtractContents(extractDirectory.FullName);
}
+ }
- Assert.That(Directory.Exists(tempDirName), Is.False, "Temporary folder should have been removed");
- }
+ var expectationDirectory = new DirectoryInfo(tempDirectory.FullName);
+ foreach (var checkFile in expectationDirectory.GetFiles("", SearchOption.AllDirectories))
+ {
+ var relativePath = checkFile.FullName.Substring(expectationDirectory.FullName.Length + 1);
+ FileAssert.Exists(Path.Combine(extractDirectory.FullName, relativePath));
+ FileAssert.AreEqual(checkFile.FullName, Path.Combine(extractDirectory.FullName, relativePath));
+ }
}
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/SevenZip.cs b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/SevenZip.cs
new file mode 100644
index 000000000..e9887172a
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/SevenZip.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.TestSupport
+{
+ // Helper class for verifying zips with 7-zip
+ internal static class SevenZipHelper
+ {
+ private static readonly string[] possible7zPaths = new[] {
+ // Check in PATH
+ "7z", "7za",
+
+ // Check in default install location
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "7-Zip", "7z.exe"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "7-Zip", "7z.exe"),
+ };
+
+ private static bool TryGet7zBinPath(out string path7z)
+ {
+ var runTimeLimit = TimeSpan.FromSeconds(3);
+
+ foreach (var testPath in possible7zPaths)
+ {
+ try
+ {
+ var p = Process.Start(new ProcessStartInfo(testPath, "i")
+ {
+ RedirectStandardOutput = true,
+ UseShellExecute = false
+ });
+ while (!p.StandardOutput.EndOfStream && (DateTime.Now - p.StartTime) < runTimeLimit)
+ {
+ p.StandardOutput.DiscardBufferedData();
+ }
+ if (!p.HasExited)
+ {
+ p.Close();
+ Assert.Warn($"Timed out checking for 7z binary in \"{testPath}\"!");
+ continue;
+ }
+
+ if (p.ExitCode == 0)
+ {
+ path7z = testPath;
+ return true;
+ }
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ path7z = null;
+ return false;
+ }
+
+ ///
+ /// Helper function to verify the provided zip stream with 7Zip.
+ ///
+ /// A stream containing the zip archive to test.
+ /// The password for the archive.
+ internal static void VerifyZipWith7Zip(Stream zipStream, string password)
+ {
+ if (TryGet7zBinPath(out string path7z))
+ {
+ Console.WriteLine($"Using 7z path: \"{path7z}\"");
+
+ var fileName = Path.GetTempFileName();
+
+ try
+ {
+ using (var fs = File.OpenWrite(fileName))
+ {
+ zipStream.Seek(0, SeekOrigin.Begin);
+ zipStream.CopyTo(fs);
+ }
+
+ var p = Process.Start(new ProcessStartInfo(path7z, $"t -p{password} \"{fileName}\"")
+ {
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ });
+
+ if (p == null)
+ {
+ Assert.Inconclusive("Failed to start 7z process. Skipping!");
+ }
+ if (!p.WaitForExit(2000))
+ {
+ Assert.Warn("Timed out verifying zip file!");
+ }
+
+ TestContext.Out.Write(p.StandardOutput.ReadToEnd());
+ var errors = p.StandardError.ReadToEnd();
+ Assert.IsEmpty(errors, "7z reported errors");
+ Assert.AreEqual(0, p.ExitCode, "Archive verification failed");
+ }
+ finally
+ {
+ File.Delete(fileName);
+ }
+ }
+ else
+ {
+ Assert.Warn("Skipping file verification since 7za is not in path");
+ }
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Streams.cs b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Streams.cs
index 3f5ae552a..f6b0fff3e 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Streams.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Streams.cs
@@ -177,13 +177,15 @@ public class MemoryStreamWithoutSeek : TrackedMemoryStream
///
///
/// true if the stream is open.
- public override bool CanSeek
+ public override bool CanSeek => false;
+
+ ///
+ public override long Position
{
- get
- {
- return false;
- }
+ get => throw new NotSupportedException("Getting position is not supported");
+ set => throw new NotSupportedException("Setting position is not supported");
}
+
}
///
diff --git a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/StringTesting.cs b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/StringTesting.cs
index 3d67a9c70..e1d7a1fb0 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/StringTesting.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/StringTesting.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
namespace ICSharpCode.SharpZipLib.Tests.TestSupport
{
@@ -6,36 +7,20 @@ public static class StringTesting
{
static StringTesting()
{
- AddLanguage("Chinese", "測試.txt", "big5");
- AddLanguage("Greek", "Ϗΰ.txt", "windows-1253");
- AddLanguage("Nordic", "Åæ.txt", "windows-1252");
- AddLanguage("Arabic", "ڀڅ.txt", "windows-1256");
- AddLanguage("Russian", "Прйвёт.txt", "windows-1251");
- }
-
- private static void AddLanguage(string language, string filename, string encoding)
- {
- languages.Add(language);
- filenames.Add(filename);
- encodings.Add(encoding);
- entries++;
+ TestSamples = new []
+ {
+ ("Chinese", "測試.txt", "big5"),
+ ("Greek", "Ϗΰ.txt", "windows-1253"),
+ ("Nordic", "Åæ.txt", "windows-1252"),
+ ("Arabic", "ڀڅ.txt", "windows-1256"),
+ ("Russian", "Прйвёт.txt", "windows-1251"),
+ };
}
- private static int entries = 0;
- private static List languages = new List();
- private static List filenames = new List();
- private static List encodings = new List();
+ public static (string language, string filename, string encoding)[] TestSamples { get; }
- public static IEnumerable Languages => filenames.AsReadOnly();
- public static IEnumerable Filenames => filenames.AsReadOnly();
- public static IEnumerable Encodings => filenames.AsReadOnly();
-
- public static IEnumerable<(string language, string filename, string encoding)> GetTestSamples()
- {
- for (int i = 0; i < entries; i++)
- {
- yield return (languages[i], filenames[i], encodings[i]);
- }
- }
+ public static IEnumerable Languages => TestSamples.Select(s => s.language);
+ public static IEnumerable Filenames => TestSamples.Select(s => s.filename);
+ public static IEnumerable Encodings => TestSamples.Select(s => s.encoding);
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs
index 9c582daa6..f610660ee 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs
@@ -1,7 +1,11 @@
using NUnit.Framework;
using System;
+using System.Diagnostics;
using System.IO;
using System.Text;
+using ICSharpCode.SharpZipLib.Tests.Zip;
+using System.Linq;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tests.TestSupport
{
@@ -12,10 +16,18 @@ public static class Utils
{
public static int DummyContentLength = 16;
- private static Random random = new Random();
+ internal const int DefaultSeed = 5;
+ private static Random random = new Random(DefaultSeed);
+
+ ///
+ /// Returns the system root for the current platform (usually c:\ for windows and / for others)
+ ///
+ public static string SystemRoot { get; } =
+ Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData));
private static void Compare(byte[] a, byte[] b)
{
+
if (a == null)
{
throw new ArgumentNullException(nameof(a));
@@ -33,125 +45,233 @@ private static void Compare(byte[] a, byte[] b)
}
}
- public static void WriteDummyData(string fileName, int size = -1)
+ ///
+ /// Write pseudo-random data to ,
+ /// creating it if it does not exist or truncating it otherwise
+ ///
+ ///
+ ///
+ ///
+ public static void WriteDummyData(string fileName, int size, int seed = DefaultSeed)
{
- using(var fs = File.OpenWrite(fileName))
- {
- WriteDummyData(fs, size);
- }
+ using var fs = File.Create(fileName);
+ WriteDummyData(fs, size, seed);
}
- public static void WriteDummyData(Stream stream, int size = -1)
+ ///
+ /// Write pseudo-random data to
+ ///
+ ///
+ ///
+ ///
+ public static void WriteDummyData(Stream stream, int size, int seed = DefaultSeed)
{
- var bytes = (size < 0)
- ? Encoding.ASCII.GetBytes(DateTime.UtcNow.Ticks.ToString("x16"))
- : new byte[size];
-
- if(size > 0)
- {
- random.NextBytes(bytes);
- }
-
- stream.Write(bytes, 0, bytes.Length);
+ var bytes = GetDummyBytes(size, seed);
+ stream.Write(bytes, offset: 0, bytes.Length);
+ }
+
+ ///
+ /// Creates a buffer of pseudo-random bytes
+ ///
+ ///
+ ///
+ ///
+ public static byte[] GetDummyBytes(int size, int seed = DefaultSeed)
+ {
+ var random = new Random(seed);
+ var bytes = new byte[size];
+ random.NextBytes(bytes);
+ return bytes;
+ }
+
+ public static async Task WriteDummyDataAsync(Stream stream, int size = -1)
+ {
+ var bytes = GetDummyBytes(size);
+ await stream.WriteAsync(bytes, 0, bytes.Length);
}
- public static TempFile GetDummyFile(int size = -1)
+ ///
+ /// Returns a file reference with bytes of dummy data written to it
+ ///
+ ///
+ ///
+ public static TempFile GetDummyFile(int size = 16)
{
var tempFile = new TempFile();
- WriteDummyData(tempFile.Filename, size);
+ using var fs = tempFile.Create();
+ WriteDummyData(fs, size);
return tempFile;
}
+ ///
+ /// Returns a randomized file/directory name (without any path) using a generated GUID
+ ///
+ ///
public static string GetDummyFileName()
- => $"{random.Next():x8}{random.Next():x8}{random.Next():x8}";
+ => string.Concat(Guid.NewGuid().ToByteArray().Select(b => $"{b:x2}"));
- public class TempFile : IDisposable
- {
- public string Filename { get; internal set; }
+ ///
+ /// Returns a reference to a temporary directory that deletes it's contents when disposed
+ ///
+ ///
+ public static TempDir GetTempDir() => new TempDir();
+
+ ///
+ /// Returns a reference to a temporary file that deletes it's referred file when disposed
+ ///
+ ///
+ public static TempFile GetTempFile() => new TempFile();
- public TempFile()
+ public static void PatchFirstEntrySize(Stream stream, int newSize)
+ {
+ using(stream)
{
- Filename = Path.GetTempFileName();
+ var sizeBytes = BitConverter.GetBytes(newSize);
+
+ stream.Seek(18, SeekOrigin.Begin);
+ stream.Write(sizeBytes, 0, 4);
+ stream.Write(sizeBytes, 0, 4);
}
+ }
+ }
+
+ public class TestTraceListener : TraceListener
+ {
+ private readonly TextWriter _writer;
+ public TestTraceListener(TextWriter writer)
+ {
+ _writer = writer;
+ }
- #region IDisposable Support
+ public override void WriteLine(string message) => _writer.WriteLine(message);
+ public override void Write(string message) => _writer.Write(message);
+ }
+
+ public class TempFile : FileSystemInfo, IDisposable
+ {
+ private FileInfo _fileInfo;
- private bool disposed = false; // To detect redundant calls
+ public override string Name => _fileInfo.Name;
+ public override bool Exists => _fileInfo.Exists;
+ public string DirectoryName => _fileInfo.DirectoryName;
- protected virtual void Dispose(bool disposing)
- {
- if (!disposed)
- {
- if (disposing && File.Exists(Filename))
- {
- try
- {
- File.Delete(Filename);
- }
- catch { }
- }
-
- disposed = true;
- }
- }
+ public override string FullName => _fileInfo.FullName;
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ public byte[] ReadAllBytes() => File.ReadAllBytes(_fileInfo.FullName);
- #endregion IDisposable Support
- }
+ public static implicit operator string(TempFile tf) => tf._fileInfo.FullName;
+
+ public override void Delete()
+ {
+ if(!Exists) return;
+ _fileInfo.Delete();
+ }
- public class TempDir : IDisposable
- {
- public string Fullpath { get; internal set; }
+ public FileStream Open(FileMode mode, FileAccess access) => _fileInfo.Open(mode, access);
+ public FileStream Open(FileMode mode) => _fileInfo.Open(mode);
+ public FileStream Create() => _fileInfo.Create();
- public TempDir()
- {
- Fullpath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(Fullpath);
- }
+ public static TempFile WithDummyData(int size, string dirPath = null, string filename = null, int seed = Utils.DefaultSeed)
+ {
+ var tempFile = new TempFile(dirPath, filename);
+ Utils.WriteDummyData(tempFile.FullName, size, seed);
+ return tempFile;
+ }
- #region IDisposable Support
+ internal TempFile(string dirPath = null, string filename = null)
+ {
+ dirPath ??= Path.GetTempPath();
+ filename ??= Utils.GetDummyFileName();
+ _fileInfo = new FileInfo(Path.Combine(dirPath, filename));
+ }
- private bool disposed = false; // To detect redundant calls
+ #region IDisposable Support
- protected virtual void Dispose(bool disposing)
- {
- if (!disposed)
- {
- if (disposing && Directory.Exists(Fullpath))
- {
- try
- {
- Directory.Delete(Fullpath, true);
- }
- catch { }
- }
-
- disposed = true;
- }
- }
+ private bool _disposed; // To detect redundant calls
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed) return;
+ if (disposing)
+ {
+ try
+ {
+ Delete();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
- internal string CreateDummyFile(int size = -1)
- => CreateDummyFile(GetDummyFileName(), size);
+ _disposed = true;
+ }
- internal string CreateDummyFile(string name, int size = -1)
- {
- var fileName = Path.Combine(Fullpath, name);
- WriteDummyData(fileName, size);
- return fileName;
- }
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
- #endregion IDisposable Support
- }
+ #endregion IDisposable Support
}
+
+
+
+ public class TempDir : FileSystemInfo, IDisposable
+ {
+ public override string Name => Path.GetFileName(FullName);
+ public override bool Exists => Directory.Exists(FullName);
+
+ public static implicit operator string(TempDir td) => td.FullName;
+
+ public override void Delete()
+ {
+ if(!Exists) return;
+ Directory.Delete(FullPath, recursive: true);
+ }
+
+ public TempDir()
+ {
+ FullPath = Path.Combine(Path.GetTempPath(), Utils.GetDummyFileName());
+ Directory.CreateDirectory(FullPath);
+ }
+
+ public TempFile CreateDummyFile(int size = 16, int seed = Utils.DefaultSeed)
+ => CreateDummyFile(null, size);
+
+ public TempFile CreateDummyFile(string name, int size = 16, int seed = Utils.DefaultSeed)
+ => TempFile.WithDummyData(size, FullPath, name, seed);
+
+ public TempFile GetFile(string fileName) => new TempFile(FullPath, fileName);
+
+ #region IDisposable Support
+
+ private bool _disposed; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed) return;
+ if (disposing)
+ {
+ try
+ {
+ Delete();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion IDisposable Support
+ }
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/ZipTesting.cs b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/ZipTesting.cs
index 688b91dc3..7311da7a2 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/TestSupport/ZipTesting.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/TestSupport/ZipTesting.cs
@@ -1,5 +1,9 @@
using ICSharpCode.SharpZipLib.Zip;
+using NUnit.Framework.Constraints;
+using NUnit.Framework;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
namespace ICSharpCode.SharpZipLib.Tests.TestSupport
{
@@ -8,30 +12,112 @@ namespace ICSharpCode.SharpZipLib.Tests.TestSupport
///
internal static class ZipTesting
{
- ///
- /// Tests the archive.
- ///
- /// The data.
- ///
- public static bool TestArchive(byte[] data)
+ public static void AssertValidZip(Stream stream, string password = null, bool usesAes = true)
{
- return TestArchive(data, null);
+ using var zipFile = new ZipFile(stream)
+ {
+ IsStreamOwner = false,
+ Password = password,
+ };
+
+ Assert.That(zipFile, Does.PassTestArchive());
+
+ if (!string.IsNullOrEmpty(password) && usesAes)
+ {
+ Assert.Ignore("ZipInputStream does not support AES");
+ }
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ Assert.DoesNotThrow(() =>
+ {
+ using var zis = new ZipInputStream(stream){Password = password};
+ while (zis.GetNextEntry() != null)
+ {
+ new StreamReader(zis).ReadToEnd();
+ }
+ }, "Archive could not be read by ZipInputStream");
}
+ }
- ///
- /// Tests the archive.
- ///
- /// The data.
- /// The password.
- /// true if archive tests ok; false otherwise.
- public static bool TestArchive(byte[] data, string password)
+ public class TestArchiveReport
+ {
+ internal const string PassingArchive = "Passing Archive";
+
+ readonly List _messages = new List();
+ public void HandleTestResults(TestStatus status, string message)
{
- using (MemoryStream ms = new MemoryStream(data))
- using (ZipFile zipFile = new ZipFile(ms))
+ if (string.IsNullOrWhiteSpace(message)) return;
+ _messages.Add(message);
+ }
+
+ public override string ToString() => _messages.Any() ? string.Join(", ", _messages) : PassingArchive;
+ }
+
+ public class PassesTestArchiveConstraint : Constraint
+ {
+ private readonly string _password;
+ private readonly bool _testData;
+
+ public PassesTestArchiveConstraint(string password = null, bool testData = true)
+ {
+ _password = password;
+ _testData = testData;
+ }
+
+ public override string Description => TestArchiveReport.PassingArchive;
+
+ public override ConstraintResult ApplyTo(TActual actual)
+ {
+ MemoryStream ms = null;
+ try
{
- zipFile.Password = password;
- return zipFile.TestArchive(true);
+ if (!(actual is ZipFile zipFile))
+ {
+ if (!(actual is byte[] rawArchive))
+ {
+ return new ConstraintResult(this, actual, ConstraintStatus.Failure);
+ }
+
+ ms = new MemoryStream(rawArchive);
+ zipFile = new ZipFile(ms){Password = _password};
+ }
+
+ var report = new TestArchiveReport();
+
+ return new ConstraintResult(
+ this, report, zipFile.TestArchive(
+ _testData,
+ TestStrategy.FindAllErrors,
+ report.HandleTestResults
+ )
+ ? ConstraintStatus.Success
+ : ConstraintStatus.Failure);
}
+ finally
+ {
+ ms?.Dispose();
+ }
+ }
+ }
+
+ public static class ZipTestingConstraintExtensions
+ {
+ public static IResolveConstraint PassTestArchive(this ConstraintExpression expression, string password = null, bool testData = true)
+ {
+ var constraint = new PassesTestArchiveConstraint(password, testData);
+ expression.Append(constraint);
+ return constraint;
}
}
+
+ ///
+ public class Does: NUnit.Framework.Does
+ {
+ public static IResolveConstraint PassTestArchive(string password = null, bool testData = true)
+ => new PassesTestArchiveConstraint(password, testData);
+
+ public static IResolveConstraint PassTestArchive(bool testData)
+ => new PassesTestArchiveConstraint(password: null, testData);
+ }
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs
index ebb44df36..3858f38f8 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs
@@ -6,6 +6,8 @@
using System.IO;
using System.Linq;
using System.Text;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
+using TimeSetting = ICSharpCode.SharpZipLib.Zip.ZipEntryFactory.TimeSetting;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -39,7 +41,7 @@ public void Basics()
ZipEntry entry = zf[0];
Assert.AreEqual(tempName1, entry.Name);
Assert.AreEqual(1, entry.Size);
- Assert.IsTrue(zf.TestArchive(true));
+ Assert.That(zf, Does.PassTestArchive());
zf.Close();
}
@@ -93,37 +95,79 @@ public void ExtractEmptyDirectories()
Assert.IsTrue(Directory.Exists(targetDir), "Empty directory should be created");
}
- [Test]
+ ///
+ /// Test that FastZip can create empty directory entries in archives.
+ ///
+ [TestCase(null)]
+ [TestCase("password")]
[Category("Zip")]
[Category("CreatesTempFile")]
- public void ContentEqualAfterAfterArchived([Values(0, 1, 64)]int contentSize)
+ public void CreateEmptyDirectories(string password)
{
- using(var sourceDir = new Utils.TempDir())
- using(var targetDir = new Utils.TempDir())
- using(var zipFile = Utils.GetDummyFile(0))
+ using (var tempFilePath = Utils.GetTempDir())
{
- var sourceFile = sourceDir.CreateDummyFile(contentSize);
- var sourceContent = File.ReadAllBytes(sourceFile);
- new FastZip().CreateZip(zipFile.Filename, sourceDir.Fullpath, true, null);
+ string name = Path.Combine(tempFilePath.FullName, "x.zip");
- Assert.DoesNotThrow(() =>
+ // Create empty test folders (The folder that we'll zip, and the test sub folder).
+ string archiveRootDir = Path.Combine(tempFilePath.FullName, ZipTempDir);
+ string targetDir = Path.Combine(archiveRootDir, "floyd");
+ Directory.CreateDirectory(targetDir);
+
+ // Create the archive with FastZip
+ var fastZip = new FastZip
{
- new FastZip().ExtractZip(zipFile.Filename, targetDir.Fullpath, null);
- }, "Exception during extraction of test archive");
-
- var targetFile = Path.Combine(targetDir.Fullpath, Path.GetFileName(sourceFile));
- var targetContent = File.ReadAllBytes(targetFile);
+ CreateEmptyDirectories = true,
+ Password = password,
+ };
+ fastZip.CreateZip(name, archiveRootDir, recurse: true, fileFilter: null);
+
+ // Test that the archive contains the empty folder entry
+ using (var zipFile = new ZipFile(name))
+ {
+ Assert.That(zipFile.Count, Is.EqualTo(1), "Should only be one entry in the file");
- Assert.AreEqual(sourceContent.Length, targetContent.Length, "Extracted file size does not match source file size");
- Assert.AreEqual(sourceContent, targetContent, "Extracted content does not match source content");
+ var folderEntry = zipFile.GetEntry("floyd/");
+ Assert.That(folderEntry.IsDirectory, Is.True, "The entry must be a folder");
+
+ Assert.That(zipFile, Does.PassTestArchive());
+ }
}
}
[Test]
[Category("Zip")]
- public void Encryption()
+ [Category("CreatesTempFile")]
+ public void ContentEqualAfterAfterArchived([Values(0, 1, 64)]int contentSize)
+ {
+ using var sourceDir = Utils.GetTempDir();
+ using var targetDir = Utils.GetTempDir();
+ using var zipFile = Utils.GetTempFile();
+
+ var sourceFile = sourceDir.CreateDummyFile(contentSize);
+ var sourceContent = sourceFile.ReadAllBytes();
+ new FastZip().CreateZip(zipFile.FullName, sourceDir.FullName, recurse: true, fileFilter: null);
+
+ Assert.DoesNotThrow(() =>
+ {
+ new FastZip().ExtractZip(zipFile, targetDir, fileFilter: null);
+ }, "Exception during extraction of test archive");
+
+ var targetFile = Path.Combine(targetDir, Path.GetFileName(sourceFile));
+ var targetContent = File.ReadAllBytes(targetFile);
+
+ Assert.AreEqual(sourceContent.Length, targetContent.Length, "Extracted file size does not match source file size");
+ Assert.AreEqual(sourceContent, targetContent, "Extracted content does not match source content");
+ }
+
+ [Test]
+ [TestCase(ZipEncryptionMethod.ZipCrypto)]
+ [TestCase(ZipEncryptionMethod.AES128)]
+ [TestCase(ZipEncryptionMethod.AES256)]
+ [Category("Zip")]
+ public void Encryption(ZipEncryptionMethod encryptionMethod)
{
const string tempName1 = "a.dat";
+ const int tempSize = 1;
var target = new MemoryStream();
@@ -131,12 +175,15 @@ public void Encryption()
Assert.IsNotNull(tempFilePath, "No permission to execute this test?");
string addFile = Path.Combine(tempFilePath, tempName1);
- MakeTempFile(addFile, 1);
+ MakeTempFile(addFile, tempSize);
try
{
- var fastZip = new FastZip();
- fastZip.Password = "Ahoy";
+ var fastZip = new FastZip
+ {
+ Password = "Ahoy",
+ EntryEncryptionMethod = encryptionMethod
+ };
fastZip.CreateZip(target, tempFilePath, false, @"a\.dat", null);
@@ -144,12 +191,28 @@ public void Encryption()
using (ZipFile zf = new ZipFile(archive))
{
zf.Password = "Ahoy";
- Assert.AreEqual(1, zf.Count);
- ZipEntry entry = zf[0];
- Assert.AreEqual(tempName1, entry.Name);
- Assert.AreEqual(1, entry.Size);
- Assert.IsTrue(zf.TestArchive(true));
- Assert.IsTrue(entry.IsCrypted);
+ Assert.That(zf.Count, Is.EqualTo(1));
+ var entry = zf[0];
+ Assert.That(entry.Name, Is.EqualTo(tempName1));
+ Assert.That(entry.Size, Is.EqualTo(tempSize));
+ Assert.That(entry.IsCrypted);
+
+ Assert.That(zf, Does.PassTestArchive());
+
+ switch (encryptionMethod)
+ {
+ case ZipEncryptionMethod.ZipCrypto:
+ Assert.That(entry.AESKeySize, Is.Zero, "AES key size should be 0 for ZipCrypto encrypted entries");
+ break;
+
+ case ZipEncryptionMethod.AES128:
+ Assert.That(entry.AESKeySize, Is.EqualTo(128), "AES key size should be 128 for AES128 encrypted entries");
+ break;
+
+ case ZipEncryptionMethod.AES256:
+ Assert.That(entry.AESKeySize, Is.EqualTo(256), "AES key size should be 256 for AES256 encrypted entries");
+ break;
+ }
}
}
finally
@@ -162,72 +225,62 @@ public void Encryption()
[Category("Zip")]
public void CreateExceptions()
{
- var fastZip = new FastZip();
- string tempFilePath = GetTempFilePath();
- Assert.IsNotNull(tempFilePath, "No permission to execute this test?");
-
Assert.Throws(() =>
{
- string addFile = Path.Combine(tempFilePath, "test.zip");
- try
- {
- fastZip.CreateZip(addFile, @"z:\doesnt exist", false, null);
- }
- finally
- {
- File.Delete(addFile);
- }
+ using var tempDir = Utils.GetTempDir();
+ var fastZip = new FastZip();
+ var badPath = Path.Combine(Path.GetTempPath(), Utils.GetDummyFileName());
+ var addFile = tempDir.GetFile("test.zip");
+ fastZip.CreateZip(addFile, badPath, recurse: false, fileFilter: null);
});
}
#region String testing helper
- private void TestFileNames(params string[] names)
- => TestFileNames((IEnumerable)names);
-
- private void TestFileNames(IEnumerable names)
+ private void TestFileNames(int codePage, IReadOnlyList names)
{
var zippy = new FastZip();
+ if (codePage > 0)
+ {
+ zippy.UseUnicode = false;
+ zippy.LegacyCodePage = codePage;
+ }
- using (var tempDir = new Utils.TempDir())
- using (var tempZip = new Utils.TempFile())
+ using var tempDir = Utils.GetTempDir();
+ using var tempZip = Utils.GetTempFile();
+ int nameCount = 0;
+ foreach (var name in names)
{
- int nameCount = 0;
- foreach (var name in names)
- {
- tempDir.CreateDummyFile(name);
- nameCount++;
- }
+ tempDir.CreateDummyFile(name);
+ nameCount++;
+ }
- zippy.CreateZip(tempZip.Filename, tempDir.Fullpath, true, null, null);
+ zippy.CreateZip(tempZip, tempDir, recurse: true, fileFilter: null);
- using (ZipFile z = new ZipFile(tempZip.Filename))
- {
- Assert.AreEqual(nameCount, z.Count);
- foreach (var name in names)
- {
- var index = z.FindEntry(name, true);
+ using var zf = new ZipFile(tempZip, zippy.StringCodec);
+ Assert.AreEqual(nameCount, zf.Count);
+ foreach (var name in names)
+ {
+ var index = zf.FindEntry(name, ignoreCase: true);
- Assert.AreNotEqual(index, -1, "Zip entry \"{0}\" not found", name);
+ Assert.AreNotEqual(expected: -1, index, "Zip entry \"{0}\" not found", name);
- var entry = z[index];
+ var entry = zf[index];
- if (ZipStrings.UseUnicode)
- {
- Assert.IsTrue(entry.IsUnicodeText, "Zip entry #{0} not marked as unicode", index);
- }
- else
- {
- Assert.IsFalse(entry.IsUnicodeText, "Zip entry #{0} marked as unicode", index);
- }
+ if (zippy.UseUnicode)
+ {
+ Assert.IsTrue(entry.IsUnicodeText, "Zip entry #{0} not marked as unicode", index);
+ }
+ else
+ {
+ Assert.IsFalse(entry.IsUnicodeText, "Zip entry #{0} marked as unicode", index);
+ }
- Assert.AreEqual(name, entry.Name);
+ Assert.AreEqual(name, entry.Name);
- var nameBytes = string.Join(" ", Encoding.BigEndianUnicode.GetBytes(entry.Name).Select(b => b.ToString("x2")));
+ var nameBytes = string.Join(" ", Encoding.BigEndianUnicode.GetBytes(entry.Name).Select(b => b.ToString("x2")));
- Console.WriteLine($" - Zip entry: {entry.Name} ({nameBytes})");
- }
- }
+ Console.WriteLine($" - Zip entry: {entry.Name} ({nameBytes})");
}
}
@@ -238,15 +291,7 @@ private void TestFileNames(IEnumerable names)
[Category("Unicode")]
public void UnicodeText()
{
- var preCp = ZipStrings.CodePage;
- try
- {
- TestFileNames(StringTesting.Filenames);
- }
- finally
- {
- ZipStrings.CodePage = preCp;
- }
+ TestFileNames(0, StringTesting.Filenames.ToArray());
}
[Test]
@@ -254,35 +299,26 @@ public void UnicodeText()
[Category("Unicode")]
public void NonUnicodeText()
{
- var preCp = ZipStrings.CodePage;
- try
- {
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- foreach ((string language, string filename, string encoding) in StringTesting.GetTestSamples())
+ foreach (var (language, filename, encoding) in StringTesting.TestSamples)
+ {
+ Console.WriteLine($"{language} filename \"{filename}\" using \"{encoding}\":");
+
+ // TODO: samples of this test must be reversible
+ // Some samples can't be restored back with their encoding.
+ // test wasn't failing only because SystemDefaultCodepage is 65001 on Net.Core and
+ // old behaviour actually was using Unicode instead of user's passed codepage
+ var encoder = Encoding.GetEncoding(encoding);
+ var bytes = encoder.GetBytes(filename);
+ var restoredString = encoder.GetString(bytes);
+ if(string.CompareOrdinal(filename, restoredString) != 0)
{
- Console.WriteLine($"{language} filename \"{filename}\" using \"{encoding}\":");
-
- // TODO: samples of this test must be reversible
- // Some samples can't be restored back with their encoding.
- // test wasn't failing only because SystemDefaultCodepage is 65001 on Net.Core and
- // old behaviour actually was using Unicode instead of user's passed codepage
- var encoder = Encoding.GetEncoding(encoding);
- var bytes = encoder.GetBytes(filename);
- var restoredString = encoder.GetString(bytes);
- if(string.CompareOrdinal(filename, restoredString) != 0)
- {
- Console.WriteLine($"Sample for language {language} with value of {filename} is skipped, because it's irreversable");
- continue;
- }
-
- ZipStrings.CodePage = Encoding.GetEncoding(encoding).CodePage;
- TestFileNames(filename);
+ Console.WriteLine($"Sample for language {language} with value of {filename} is skipped, because it's irreversable");
+ continue;
}
- }
- finally
- {
- ZipStrings.CodePage = preCp;
+
+ TestFileNames(Encoding.GetEncoding(encoding).CodePage, new [] { filename });
}
}
@@ -325,6 +361,7 @@ public void ExtractExceptions()
public void ReadingOfLockedDataFiles()
{
const string tempName1 = "a.dat";
+ const int tempSize = 1;
var target = new MemoryStream();
@@ -332,7 +369,7 @@ public void ReadingOfLockedDataFiles()
Assert.IsNotNull(tempFilePath, "No permission to execute this test?");
string addFile = Path.Combine(tempFilePath, tempName1);
- MakeTempFile(addFile, 1);
+ MakeTempFile(addFile, tempSize);
try
{
@@ -345,11 +382,11 @@ public void ReadingOfLockedDataFiles()
var archive = new MemoryStream(target.ToArray());
using (ZipFile zf = new ZipFile(archive))
{
- Assert.AreEqual(1, zf.Count);
- ZipEntry entry = zf[0];
- Assert.AreEqual(tempName1, entry.Name);
- Assert.AreEqual(1, entry.Size);
- Assert.IsTrue(zf.TestArchive(true));
+ Assert.That(zf.Count, Is.EqualTo(1));
+ var entry = zf[0];
+ Assert.That(entry.Name, Is.EqualTo(tempName1));
+ Assert.That(entry.Size, Is.EqualTo(tempSize));
+ Assert.That(zf, Does.PassTestArchive());
zf.Close();
}
@@ -366,6 +403,7 @@ public void ReadingOfLockedDataFiles()
public void NonAsciiPasswords()
{
const string tempName1 = "a.dat";
+ const int tempSize = 1;
var target = new MemoryStream();
@@ -373,7 +411,7 @@ public void NonAsciiPasswords()
Assert.IsNotNull(tempFilePath, "No permission to execute this test?");
string addFile = Path.Combine(tempFilePath, tempName1);
- MakeTempFile(addFile, 1);
+ MakeTempFile(addFile, tempSize);
string password = "abc\u0066\u0393";
try
@@ -387,12 +425,12 @@ public void NonAsciiPasswords()
using (ZipFile zf = new ZipFile(archive))
{
zf.Password = password;
- Assert.AreEqual(1, zf.Count);
- ZipEntry entry = zf[0];
- Assert.AreEqual(tempName1, entry.Name);
- Assert.AreEqual(1, entry.Size);
- Assert.IsTrue(zf.TestArchive(true));
- Assert.IsTrue(entry.IsCrypted);
+ Assert.That(zf.Count, Is.EqualTo(1));
+ var entry = zf[0];
+ Assert.That(entry.Name, Is.EqualTo(tempName1));
+ Assert.That(entry.Size, Is.EqualTo(tempSize));
+ Assert.That(zf, Does.PassTestArchive());
+ Assert.That(entry.IsCrypted);
}
}
finally
@@ -414,7 +452,7 @@ public void LimitExtractPath()
tempPath = Path.Combine(tempPath, uniqueName);
var extractPath = Path.Combine(tempPath, "output");
- const string contentFile = "content.txt";
+ const string contentFile = "output.txt";
var contentFilePathBad = Path.Combine("..", contentFile);
var extractFilePathBad = Path.Combine(tempPath, contentFile);
@@ -529,5 +567,269 @@ public void StreamClosedOnError()
// test folder should not have been created on error
Assert.That(Directory.Exists(tempFolderPath), Is.False, "Temp folder path should still not exist");
}
+
+ ///
+ /// #426 - set the modified date for created directory entries if the RestoreDateTimeOnExtract option is enabled
+ ///
+ [Test]
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ public void SetDirectoryModifiedDate()
+ {
+ string tempFilePath = GetTempFilePath();
+ Assert.IsNotNull(tempFilePath, "No permission to execute this test?");
+
+ string zipName = Path.Combine(tempFilePath, $"{nameof(SetDirectoryModifiedDate)}.zip");
+
+ EnsureTestDirectoryIsEmpty(tempFilePath);
+
+ var modifiedTime = new DateTime(2001, 1, 2);
+ string targetDir = Path.Combine(tempFilePath, ZipTempDir, nameof(SetDirectoryModifiedDate));
+ using (FileStream fs = File.Create(zipName))
+ {
+ using (ZipOutputStream zOut = new ZipOutputStream(fs))
+ {
+ // Add an empty directory entry, with a specified time field
+ var entry = new ZipEntry("emptyFolder/")
+ {
+ DateTime = modifiedTime
+ };
+ zOut.PutNextEntry(entry);
+ }
+ }
+
+ try
+ {
+ // extract the zip
+ var fastZip = new FastZip
+ {
+ CreateEmptyDirectories = true,
+ RestoreDateTimeOnExtract = true
+ };
+ fastZip.ExtractZip(zipName, targetDir, "zz");
+
+ File.Delete(zipName);
+
+ // Check that the empty sub folder exists and has the expected modlfied date
+ string emptyTargetDir = Path.Combine(targetDir, "emptyFolder");
+
+ Assert.That(Directory.Exists(emptyTargetDir), Is.True, "Empty directory should be created");
+
+ var extractedFolderTime = Directory.GetLastWriteTime(emptyTargetDir);
+ Assert.That(extractedFolderTime, Is.EqualTo(modifiedTime));
+ }
+ finally
+ {
+ // Tidy up
+ Directory.Delete(targetDir, true);
+ }
+ }
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/78
+ ///
+ /// if true, the stream given to CreateZip should be left open, if false it should be disposed.
+ [TestCase(true)]
+ [TestCase(false)]
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ public void CreateZipShouldLeaveOutputStreamOpenIfRequested(bool leaveOpen)
+ {
+ const string tempFileName = "a(2).dat";
+ const int tempSize = 16;
+
+ using var tempFolder = Utils.GetTempDir();
+ // Create test input file
+ tempFolder.CreateDummyFile(tempFileName, tempSize);
+
+ // Create the zip with fast zip
+ var target = new TrackedMemoryStream();
+ var fastZip = new FastZip();
+
+ fastZip.CreateZip(target, tempFolder, recurse: false, @"a\(2\)\.dat", directoryFilter: null, leaveOpen);
+
+ // Check that the output stream was disposed (or not) as expected
+ Assert.That(target.IsDisposed, Is.Not.EqualTo(leaveOpen), "IsDisposed should be the opposite of leaveOpen");
+
+ // Check that the file contents are correct in both cases
+ var archive = new MemoryStream(target.ToArray());
+ using var zf = new ZipFile(archive);
+ Assert.That(zf.Count, Is.EqualTo(1));
+ var entry = zf[0];
+ Assert.That(entry.Name, Is.EqualTo(tempFileName));
+ Assert.That(entry.Size, Is.EqualTo(tempSize));
+ Assert.That(zf, Does.PassTestArchive());
+ }
+
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [Test]
+ public void CreateZipShouldSetTimeOnEntriesFromConstructorDateTime()
+ {
+ var targetTime = TestTargetTime(TimeSetting.Fixed);
+ var fastZip = new FastZip(targetTime);
+ var target = CreateFastZipTestArchiveWithAnEntry(fastZip);
+ var archive = new MemoryStream(target.ToArray());
+ using (var zf = new ZipFile(archive))
+ {
+ Assert.AreEqual(targetTime, zf[0].DateTime);
+ }
+ }
+
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
+ [TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
+ public void CreateZipShouldSetTimeOnEntriesFromConstructorTimeSetting(TimeSetting timeSetting)
+ {
+ var targetTime = TestTargetTime(timeSetting);
+ var fastZip = new FastZip(timeSetting);
+
+ var alterTime = (Action) null;
+ switch(timeSetting)
+ {
+ case TimeSetting.LastWriteTime: alterTime = fi => fi.LastWriteTime = targetTime; break;
+ case TimeSetting.LastWriteTimeUtc: alterTime = fi => fi.LastWriteTimeUtc = targetTime; break;
+ case TimeSetting.CreateTime: alterTime = fi => fi.CreationTime = targetTime; break;
+ case TimeSetting.CreateTimeUtc: alterTime = fi => fi.CreationTimeUtc = targetTime; break;
+ }
+
+ var target = CreateFastZipTestArchiveWithAnEntry(fastZip, alterTime);
+ // Check that the file contents are correct in both cases
+ var archive = new MemoryStream(target.ToArray());
+ using (var zf = new ZipFile(archive))
+ {
+ var expectedTime = TestTargetTime(timeSetting);
+ var actualTime = zf[0].DateTime;
+ // Assert that the time is within +/- 2s of the target time to allow for timing/rounding discrepancies
+ Assert.LessOrEqual(Math.Abs((expectedTime - actualTime).TotalSeconds), 2);
+ }
+ }
+
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(TimeSetting.CreateTimeUtc), TestCase(TimeSetting.LastWriteTimeUtc), TestCase(TimeSetting.LastAccessTimeUtc)]
+ [TestCase(TimeSetting.CreateTime), TestCase(TimeSetting.LastWriteTime), TestCase(TimeSetting.LastAccessTime)]
+ [TestCase(TimeSetting.Fixed)]
+ public void ExtractZipShouldSetTimeOnFilesFromConstructorTimeSetting(TimeSetting timeSetting)
+ {
+ var targetTime = ExpectedFixedTime();
+ var archiveStream = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));
+
+ if (timeSetting == TimeSetting.Fixed)
+ {
+ Assert.Ignore("Fixed time without specifying a time is undefined");
+ }
+
+ var fastZip = new FastZip(timeSetting);
+ using var extractDir = Utils.GetTempDir();
+ fastZip.ExtractZip(archiveStream, extractDir.FullName, FastZip.Overwrite.Always,
+ _ => true, "", "", restoreDateTime: true, isStreamOwner: true, allowParentTraversal: false);
+ var fi = new FileInfo(Path.Combine(extractDir.FullName, SingleEntryFileName));
+ var actualTime = FileTimeFromTimeSetting(fi, timeSetting);
+ // Assert that the time is within +/- 2s of the target time to allow for timing/rounding discrepancies
+ Assert.LessOrEqual(Math.Abs((targetTime - actualTime).TotalSeconds), 2);
+ }
+
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
+ public void ExtractZipShouldSetTimeOnFilesFromConstructorDateTime(DateTimeKind dtk)
+ {
+ // Create the archive with a fixed "bad" datetime
+ var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(UnexpectedFixedTime(dtk)));
+
+ // Extract the archive with a fixed time override
+ var targetTime = ExpectedFixedTime(dtk);
+ var fastZip = new FastZip(targetTime);
+ using var extractDir = Utils.GetTempDir();
+ fastZip.ExtractZip(target, extractDir.FullName, FastZip.Overwrite.Always,
+ _ => true, "", "", restoreDateTime: true, isStreamOwner: true, allowParentTraversal: false);
+ var fi = new FileInfo(Path.Combine(extractDir.FullName, SingleEntryFileName));
+ var fileTime = FileTimeFromTimeSetting(fi, TimeSetting.Fixed);
+ if (fileTime.Kind != dtk) fileTime = fileTime.ToUniversalTime();
+ Assert.AreEqual(targetTime, fileTime);
+ }
+
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(DateTimeKind.Local), TestCase(DateTimeKind.Utc)]
+ public void ExtractZipShouldSetTimeOnFilesWithEmptyConstructor(DateTimeKind dtk)
+ {
+ // Create the archive with a fixed datetime
+ var targetTime = ExpectedFixedTime(dtk);
+ var target = CreateFastZipTestArchiveWithAnEntry(new FastZip(targetTime));
+
+ // Extract the archive with an empty constructor
+ var fastZip = new FastZip();
+ using var extractDir = Utils.GetTempDir();
+ fastZip.ExtractZip(target, extractDir.FullName, FastZip.Overwrite.Always,
+ _ => true, "", "", restoreDateTime: true, isStreamOwner: true, allowParentTraversal: false);
+ var fi = new FileInfo(Path.Combine(extractDir.FullName, SingleEntryFileName));
+ Assert.AreEqual(targetTime, FileTimeFromTimeSetting(fi, TimeSetting.Fixed));
+ }
+
+ private static bool IsLastAccessTime(TimeSetting ts)
+ => ts == TimeSetting.LastAccessTime || ts == TimeSetting.LastAccessTimeUtc;
+
+ private static DateTime FileTimeFromTimeSetting(FileInfo fi, TimeSetting timeSetting)
+ {
+ switch (timeSetting)
+ {
+ case TimeSetting.LastWriteTime: return fi.LastWriteTime;
+ case TimeSetting.LastWriteTimeUtc: return fi.LastWriteTimeUtc;
+ case TimeSetting.CreateTime: return fi.CreationTime;
+ case TimeSetting.CreateTimeUtc: return fi.CreationTimeUtc;
+ case TimeSetting.LastAccessTime: return fi.LastAccessTime;
+ case TimeSetting.LastAccessTimeUtc: return fi.LastAccessTimeUtc;
+ case TimeSetting.Fixed: return fi.LastWriteTime;
+ }
+
+ throw new ArgumentException("Invalid TimeSetting", nameof(timeSetting));
+ }
+
+ private static DateTime TestTargetTime(TimeSetting ts)
+ {
+ var dtk = ts == TimeSetting.CreateTimeUtc
+ || ts == TimeSetting.LastWriteTimeUtc
+ || ts == TimeSetting.LastAccessTimeUtc
+ ? DateTimeKind.Utc
+ : DateTimeKind.Local;
+
+ return IsLastAccessTime(ts)
+ // AccessTime will be altered by reading/writing the file entry
+ ? CurrentTime(dtk)
+ : ExpectedFixedTime(dtk);
+ }
+
+ private static DateTime CurrentTime(DateTimeKind kind)
+ {
+ var now = kind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now;
+ return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, (now.Second / 2) * 2, kind);
+ }
+
+ private static DateTime ExpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
+ => new DateTime(2010, 5, 30, 16, 22, 50, dtk);
+ private static DateTime UnexpectedFixedTime(DateTimeKind dtk = DateTimeKind.Unspecified)
+ => new DateTime(1980, 10, 11, 22, 39, 30, dtk);
+
+ private const string SingleEntryFileName = "testEntry.dat";
+
+ private static TrackedMemoryStream CreateFastZipTestArchiveWithAnEntry(FastZip fastZip, Action alterFile = null)
+ {
+ var target = new TrackedMemoryStream();
+
+ using var tempFolder = Utils.GetTempDir();
+ // Create test input file
+ var addFile = Path.Combine(tempFolder.FullName, SingleEntryFileName);
+ MakeTempFile(addFile, 16);
+ var fi = new FileInfo(addFile);
+ alterFile?.Invoke(fi);
+
+ fastZip.CreateZip(target, tempFolder.FullName, recurse: false,
+ SingleEntryFileName, directoryFilter: null, leaveOpen: true);
+
+ return target;
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs
index b0d22c9bc..d45eedf3c 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs
@@ -1,5 +1,7 @@
-using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using ICSharpCode.SharpZipLib.Checksum;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
using ICSharpCode.SharpZipLib.Zip;
+using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using NUnit.Framework;
using System;
using System.IO;
@@ -7,6 +9,7 @@
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Text;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -133,23 +136,13 @@ private string DescribeAttributes(FieldAttributes attributes)
return att;
}
- [Test]
- [Category("Zip")]
- //[ExpectedException(typeof(NotSupportedException))]
- public void UnsupportedCompressionMethod()
- {
- var ze = new ZipEntry("HumblePie");
- //ze.CompressionMethod = CompressionMethod.BZip2;
-
- Assert.That(() => ze.CompressionMethod = CompressionMethod.BZip2,
- Throws.TypeOf());
- }
-
///
/// Invalid passwords should be detected early if possible, seekable stream
+ /// Note: Have a 1/255 chance of failing due to CRC collision (hence retried once)
///
[Test]
[Category("Zip")]
+ [Retry(2)]
public void InvalidPasswordSeekable()
{
byte[] originalData = null;
@@ -216,9 +209,11 @@ public void ExerciseGetNextEntry()
///
/// Invalid passwords should be detected early if possible, non seekable stream
+ /// Note: Have a 1/255 chance of failing due to CRC collision (hence retried once)
///
[Test]
[Category("Zip")]
+ [Retry(2)]
public void InvalidPasswordNonSeekable()
{
byte[] originalData = null;
@@ -389,7 +384,7 @@ public void StoredNonSeekableKnownSizeNoCrc()
index += count;
}
}
- Assert.IsTrue(ZipTesting.TestArchive(ms.ToArray()));
+ Assert.That(ms.ToArray(), Does.PassTestArchive());
}
[Test]
@@ -397,20 +392,20 @@ public void StoredNonSeekableKnownSizeNoCrc()
public void StoredNonSeekableKnownSizeNoCrcEncrypted()
{
// This cant be stored directly as the crc is not known
- const int TargetSize = 24692;
- const string Password = "Mabutu";
+ const int targetSize = 24692;
+ const string password = "Mabutu";
MemoryStream ms = new MemoryStreamWithoutSeek();
using (ZipOutputStream outStream = new ZipOutputStream(ms))
{
- outStream.Password = Password;
+ outStream.Password = password;
outStream.IsStreamOwner = false;
var entry = new ZipEntry("dummyfile.tst");
entry.CompressionMethod = CompressionMethod.Stored;
// The bit thats in question is setting the size before its added to the archive.
- entry.Size = TargetSize;
+ entry.Size = targetSize;
outStream.PutNextEntry(entry);
@@ -419,7 +414,7 @@ public void StoredNonSeekableKnownSizeNoCrcEncrypted()
var rnd = new Random();
- int size = TargetSize;
+ int size = targetSize;
byte[] original = new byte[size];
rnd.NextBytes(original);
@@ -435,7 +430,7 @@ public void StoredNonSeekableKnownSizeNoCrcEncrypted()
index += count;
}
}
- Assert.IsTrue(ZipTesting.TestArchive(ms.ToArray(), Password));
+ Assert.That(ms.ToArray(), Does.PassTestArchive(password));
}
///
@@ -508,10 +503,10 @@ public void MixedEncryptedAndPlain()
int extractCount = 0;
int extractIndex = 0;
- ZipEntry entry;
+
byte[] decompressedData = new byte[100];
- while ((entry = inStream.GetNextEntry()) != null)
+ while (inStream.GetNextEntry() != null)
{
extractCount = decompressedData.Length;
extractIndex = 0;
@@ -537,7 +532,7 @@ public void MixedEncryptedAndPlain()
[Category("Zip")]
public void BasicStoredEncrypted()
{
- ExerciseZip(CompressionMethod.Stored, 0, 50000, "Rosebud", true);
+ ExerciseZip(CompressionMethod.Stored, compressionLevel: 0, size: 50000, "Rosebud", canSeek: true);
}
///
@@ -548,7 +543,7 @@ public void BasicStoredEncrypted()
[Category("Zip")]
public void BasicStoredEncryptedNonSeekable()
{
- ExerciseZip(CompressionMethod.Stored, 0, 50000, "Rosebud", false);
+ ExerciseZip(CompressionMethod.Stored, compressionLevel: 0, size: 50000, "Rosebud", canSeek: false);
}
///
@@ -864,20 +859,17 @@ private object UnZipZeroLength(byte[] zipped)
return result;
}
- private void CheckNameConversion(string toCheck)
- {
- byte[] intermediate = ZipStrings.ConvertToArray(toCheck);
- string final = ZipStrings.ConvertToString(intermediate);
-
- Assert.AreEqual(toCheck, final, "Expected identical result");
- }
-
[Test]
[Category("Zip")]
- public void NameConversion()
+ [TestCase("Hello")]
+ [TestCase("a/b/c/d/e/f/g/h/SomethingLikeAnArchiveName.txt")]
+ public void LegacyNameConversion(string name)
{
- CheckNameConversion("Hello");
- CheckNameConversion("a/b/c/d/e/f/g/h/SomethingLikeAnArchiveName.txt");
+ var encoding = new StringCodec().ZipEncoding(false);
+ byte[] intermediate = encoding.GetBytes(name);
+ string final = encoding.GetString(intermediate);
+
+ Assert.AreEqual(name, final, "Expected identical result");
}
[Test]
@@ -886,22 +878,22 @@ public void UnicodeNameConversion()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- ZipStrings.CodePage = 850;
+ var codec = new StringCodec() {CodePage = 850};
string sample = "Hello world";
byte[] rawData = Encoding.ASCII.GetBytes(sample);
- string converted = ZipStrings.ConvertToStringExt(0, rawData);
+ var converted = codec.ZipInputEncoding(0).GetString(rawData);
Assert.AreEqual(sample, converted);
- converted = ZipStrings.ConvertToStringExt((int)GeneralBitFlags.UnicodeText, rawData);
+ converted = codec.ZipInputEncoding((int)GeneralBitFlags.UnicodeText).GetString(rawData);
Assert.AreEqual(sample, converted);
// This time use some greek characters
sample = "\u03A5\u03d5\u03a3";
rawData = Encoding.UTF8.GetBytes(sample);
- converted = ZipStrings.ConvertToStringExt((int)GeneralBitFlags.UnicodeText, rawData);
+ converted = codec.ZipInputEncoding((int)GeneralBitFlags.UnicodeText).GetString(rawData);
Assert.AreEqual(sample, converted);
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/PassthroughTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/PassthroughTests.cs
new file mode 100644
index 000000000..954e339b1
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/PassthroughTests.cs
@@ -0,0 +1,141 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+using ICSharpCode.SharpZipLib.Checksum;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using ICSharpCode.SharpZipLib.Zip;
+using NUnit.Framework;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
+
+namespace ICSharpCode.SharpZipLib.Tests.Zip
+{
+ [TestFixture]
+ public class PassthroughTests
+ {
+ [Test]
+ [Category("Zip")]
+ public void AddingValidPrecompressedEntryToZipOutputStream()
+ {
+ using var ms = new MemoryStream();
+
+ using (var outStream = new ZipOutputStream(ms){IsStreamOwner = false})
+ {
+ var (compressedData, crc, size) = CreateDeflatedData();
+ var entry = new ZipEntry("dummyfile.tst")
+ {
+ CompressionMethod = CompressionMethod.Deflated,
+ Size = size,
+ Crc = (uint)crc.Value,
+ CompressedSize = compressedData.Length,
+ };
+
+ outStream.PutNextPassthroughEntry(entry);
+
+ compressedData.CopyTo(outStream);
+ }
+
+ Assert.That(ms.ToArray(), Does.PassTestArchive());
+ }
+
+ private static (MemoryStream, Crc32, int) CreateDeflatedData()
+ {
+ var data = Encoding.UTF8.GetBytes("Hello, world");
+
+ var crc = new Crc32();
+ crc.Update(data);
+
+ var compressedData = new MemoryStream();
+ using(var gz = new DeflateStream(compressedData, CompressionMode.Compress, leaveOpen: true))
+ {
+ gz.Write(data, 0, data.Length);
+ }
+ compressedData.Position = 0;
+
+ return (compressedData, crc, data.Length);
+ }
+
+ [Test]
+ [Category("Zip")]
+ public void AddingPrecompressedEntryToZipOutputStreamWithInvalidSize()
+ {
+ using var outStream = new ZipOutputStream(new MemoryStream());
+ var (compressedData, crc, size) = CreateDeflatedData();
+ outStream.Password = "mockpassword";
+ var entry = new ZipEntry("dummyfile.tst")
+ {
+ CompressionMethod = CompressionMethod.Stored,
+ Crc = (uint)crc.Value,
+ CompressedSize = compressedData.Length,
+ };
+
+ Assert.Throws(() =>
+ {
+ outStream.PutNextPassthroughEntry(entry);
+ });
+ }
+
+
+ [Test]
+ [Category("Zip")]
+ public void AddingPrecompressedEntryToZipOutputStreamWithInvalidCompressedSize()
+ {
+ using var outStream = new ZipOutputStream(new MemoryStream());
+ var (compressedData, crc, size) = CreateDeflatedData();
+ outStream.Password = "mockpassword";
+ var entry = new ZipEntry("dummyfile.tst")
+ {
+ CompressionMethod = CompressionMethod.Stored,
+ Size = size,
+ Crc = (uint)crc.Value,
+ };
+
+ Assert.Throws(() =>
+ {
+ outStream.PutNextPassthroughEntry(entry);
+ });
+ }
+
+ [Test]
+ [Category("Zip")]
+ public void AddingPrecompressedEntryToZipOutputStreamWithNonSupportedMethod()
+ {
+ using var outStream = new ZipOutputStream(new MemoryStream());
+ var (compressedData, crc, size) = CreateDeflatedData();
+ outStream.Password = "mockpassword";
+ var entry = new ZipEntry("dummyfile.tst")
+ {
+ CompressionMethod = CompressionMethod.LZMA,
+ Size = size,
+ Crc = (uint)crc.Value,
+ CompressedSize = compressedData.Length,
+ };
+
+ Assert.Throws(() =>
+ {
+ outStream.PutNextPassthroughEntry(entry);
+ });
+ }
+
+ [Test]
+ [Category("Zip")]
+ public void AddingPrecompressedEntryToZipOutputStreamWithEncryption()
+ {
+ using var outStream = new ZipOutputStream(new MemoryStream());
+ var (compressedData, crc, size) = CreateDeflatedData();
+ outStream.Password = "mockpassword";
+ var entry = new ZipEntry("dummyfile.tst")
+ {
+ CompressionMethod = CompressionMethod.Deflated,
+ Size = size,
+ Crc = (uint)crc.Value,
+ CompressedSize = compressedData.Length,
+ };
+
+ Assert.Throws(() =>
+ {
+ outStream.PutNextPassthroughEntry(entry);
+ });
+ }
+ }
+}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs
index 21c3eacbd..3e8ab732c 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs
@@ -2,7 +2,12 @@
using ICSharpCode.SharpZipLib.Tests.TestSupport;
using ICSharpCode.SharpZipLib.Zip;
using NUnit.Framework;
+using System;
+using System.Diagnostics;
using System.IO;
+using System.Linq;
+using System.Text;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -12,6 +17,12 @@ namespace ICSharpCode.SharpZipLib.Tests.Zip
[TestFixture]
public class StreamHandling : ZipBase
{
+ private TestTraceListener Listener;
+ [SetUp]
+ public void Init() => Trace.Listeners.Add(Listener = new TestTraceListener(TestContext.Out));
+ [TearDown]
+ public void Deinit() => Trace.Listeners.Remove(Listener);
+
private void MustFailRead(Stream s, byte[] buffer, int offset, int count)
{
bool exception = false;
@@ -43,9 +54,9 @@ public void ParameterHandling()
ms.Seek(0, SeekOrigin.Begin);
var inStream = new ZipInputStream(ms);
- ZipEntry e = inStream.GetNextEntry();
+ inStream.GetNextEntry();
- MustFailRead(inStream, null, 0, 0);
+ MustFailRead(inStream, buffer: null, 0, 0);
MustFailRead(inStream, buffer, -1, 1);
MustFailRead(inStream, buffer, 0, 11);
MustFailRead(inStream, buffer, 7, 5);
@@ -76,7 +87,7 @@ public void Zip64Descriptor()
outStream.WriteByte(89);
outStream.Close();
- Assert.IsTrue(ZipTesting.TestArchive(msw.ToArray()));
+ Assert.That(msw.ToArray(), Does.PassTestArchive());
msw = new MemoryStreamWithoutSeek();
outStream = new ZipOutputStream(msw);
@@ -87,7 +98,7 @@ public void Zip64Descriptor()
outStream.WriteByte(89);
outStream.Close();
- Assert.IsTrue(ZipTesting.TestArchive(msw.ToArray()));
+ Assert.That(msw.ToArray(), Does.PassTestArchive());
}
[Test]
@@ -109,18 +120,16 @@ public void ReadAndWriteZip64NonSeekable()
outStream.Close();
}
- Assert.IsTrue(ZipTesting.TestArchive(msw.ToArray()));
-
- msw.Position = 0;
+ var msBytes = msw.ToArray();
+ Assert.That(msBytes, Does.PassTestArchive());
- using (ZipInputStream zis = new ZipInputStream(msw))
+ using (var zis = new ZipInputStream(new MemoryStream(msBytes)))
{
while (zis.GetNextEntry() != null)
{
- int len = 0;
- int bufferSize = 1024;
- byte[] buffer = new byte[bufferSize];
- while ((len = zis.Read(buffer, 0, bufferSize)) > 0)
+ const int bufferSize = 1024;
+ var buffer = new byte[bufferSize];
+ while (zis.Read(buffer, 0, bufferSize) > 0)
{
// Reading the data is enough
}
@@ -147,7 +156,7 @@ public void EntryWithNoDataAndZip64()
outStream.Finish();
outStream.Close();
- Assert.IsTrue(ZipTesting.TestArchive(msw.ToArray()));
+ Assert.That(msw.ToArray(), Does.PassTestArchive());
}
///
@@ -188,7 +197,50 @@ public void EmptyZipEntries()
}
}
inStream.Close();
- Assert.AreEqual(extractCount, 0, "No data should be read from empty entries");
+ Assert.Zero(extractCount, "No data should be read from empty entries");
+ }
+
+ ///
+ /// Test that calling Write with 0 bytes behaves.
+ /// See issue @ https://github.com/icsharpcode/SharpZipLib/issues/123.
+ ///
+ [Test]
+ [Category("Zip")]
+ public void TestZeroByteWrite()
+ {
+ using (var ms = new MemoryStreamWithoutSeek())
+ {
+ using (var outStream = new ZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ var ze = new ZipEntry("Striped Marlin");
+ outStream.PutNextEntry(ze);
+
+ var buffer = Array.Empty();
+ outStream.Write(buffer, 0, 0);
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var inStream = new ZipInputStream(ms) { IsStreamOwner = false })
+ {
+ int extractCount = 0;
+ byte[] decompressedData = new byte[100];
+
+ while (inStream.GetNextEntry() != null)
+ {
+ while (true)
+ {
+ int numRead = inStream.Read(decompressedData, extractCount, decompressedData.Length);
+ if (numRead <= 0)
+ {
+ break;
+ }
+ extractCount += numRead;
+ }
+ }
+ Assert.Zero(extractCount, "No data should be read from empty entries");
+ }
+ }
}
[Test]
@@ -197,44 +249,66 @@ public void WriteZipStreamWithNoCompression([Values(0, 1, 256)] int contentLengt
{
var buffer = new byte[255];
- using (var dummyZip = Utils.GetDummyFile(0))
- using (var inputFile = Utils.GetDummyFile(contentLength))
+ using var dummyZip = Utils.GetTempFile();
+ using var inputFile = Utils.GetDummyFile(contentLength);
+ // Filename is manually cleaned here to prevent this test from failing while ZipEntry doesn't automatically clean it
+ var inputFileName = ZipEntry.CleanName(inputFile);
+
+ using (var zipFileStream = File.OpenWrite(dummyZip))
+ using (var zipOutputStream = new ZipOutputStream(zipFileStream))
+ using (var inputFileStream = File.OpenRead(inputFile))
{
- using (var zipFileStream = File.OpenWrite(dummyZip.Filename))
- using (var zipOutputStream = new ZipOutputStream(zipFileStream))
- using (var inputFileStream = File.OpenRead(inputFile.Filename))
+ zipOutputStream.PutNextEntry(new ZipEntry(inputFileName)
{
- zipOutputStream.PutNextEntry(new ZipEntry(inputFile.Filename)
- {
- CompressionMethod = CompressionMethod.Stored,
- });
+ CompressionMethod = CompressionMethod.Stored,
+ });
- StreamUtils.Copy(inputFileStream, zipOutputStream, buffer);
- }
+ StreamUtils.Copy(inputFileStream, zipOutputStream, buffer);
+ }
+
+ using (var zf = new ZipFile(dummyZip))
+ {
+ var inputBytes = File.ReadAllBytes(inputFile);
+
+ var entry = zf.GetEntry(inputFileName);
+ Assert.IsNotNull(entry, "No entry matching source file \"{0}\" found in archive, found \"{1}\"", inputFileName, zf[0].Name);
- using (var zf = new ZipFile(dummyZip.Filename))
+ Assert.DoesNotThrow(() =>
{
- var inputBytes = File.ReadAllBytes(inputFile.Filename);
+ using var entryStream = zf.GetInputStream(entry);
+ var outputBytes = new byte[entryStream.Length];
+ entryStream.Read(outputBytes, 0, outputBytes.Length);
- var inputFileName = ZipEntry.CleanName(inputFile.Filename);
- var entry = zf.GetEntry(inputFileName);
- Assert.IsNotNull(entry, "No entry matching source file \"{0}\" found in archive, found \"{1}\"", inputFileName, zf[0].Name);
+ Assert.AreEqual(inputBytes, outputBytes, "Archive content does not match the source content");
+ }, "Failed to locate entry stream in archive");
- Assert.DoesNotThrow(() =>
- {
- using (var entryStream = zf.GetInputStream(entry))
- {
- var outputBytes = new byte[entryStream.Length];
- entryStream.Read(outputBytes, 0, outputBytes.Length);
+ Assert.That(zf, Does.PassTestArchive());
+ }
+ }
- Assert.AreEqual(inputBytes, outputBytes, "Archive content does not match the source content");
- }
- }, "Failed to locate entry stream in archive");
+ [Test]
+ [Category("Zip")]
+ public void ZipEntryFileNameAutoClean()
+ {
+ using var dummyZip = Utils.GetDummyFile(0);
+ using var inputFile = Utils.GetDummyFile();
+ using (var zipFileStream = File.OpenWrite(dummyZip))
+ using (var zipOutputStream = new ZipOutputStream(zipFileStream))
+ using (var inputFileStream = File.OpenRead(inputFile))
+ {
+ // New ZipEntry created with a full file name path as it's name
+ zipOutputStream.PutNextEntry(new ZipEntry(inputFile)
+ {
+ CompressionMethod = CompressionMethod.Stored,
+ });
- Assert.IsTrue(zf.TestArchive(testData: true), "Archive did not pass TestArchive");
- }
+ inputFileStream.CopyTo(zipOutputStream);
+ }
-
+ using (var zf = new ZipFile(dummyZip))
+ {
+ // The ZipEntry name should have been automatically cleaned
+ Assert.AreEqual(ZipEntry.CleanName(inputFile), zf[0].Name);
}
}
@@ -351,7 +425,7 @@ public void WriteThroughput()
[Explicit("Long Running")]
public void SingleLargeEntry()
{
- const string EntryName = "CantSeek";
+ const string entryName = "CantSeek";
PerformanceTesting.TestReadWrite(
size: TestDataSize.Large,
@@ -360,14 +434,14 @@ public void SingleLargeEntry()
var zis = new ZipInputStream(bs);
var entry = zis.GetNextEntry();
- Assert.AreEqual(entry.Name, EntryName);
+ Assert.AreEqual(entryName, entry.Name);
Assert.IsTrue((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0);
return zis;
},
output: bs =>
{
var zos = new ZipOutputStream(bs);
- zos.PutNextEntry(new ZipEntry(EntryName));
+ zos.PutNextEntry(new ZipEntry(entryName));
return zos;
}
);
@@ -385,20 +459,18 @@ public void SingleLargeEntry()
[Category("Zip")]
public void ShouldReadBZip2EntryButNotDecompress()
{
- var fileBytes = System.Convert.FromBase64String(BZip2CompressedZip);
+ var fileBytes = Convert.FromBase64String(BZip2CompressedZip);
- using (var input = new MemoryStream(fileBytes, false))
- {
- var zis = new ZipInputStream(input);
- var entry = zis.GetNextEntry();
+ using var input = new MemoryStream(fileBytes, writable: false);
+ var zis = new ZipInputStream(input);
+ var entry = zis.GetNextEntry();
- Assert.That(entry.Name, Is.EqualTo("a.dat"), "Should be able to get entry name");
- Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Entry should be BZip2 compressed");
- Assert.That(zis.CanDecompressEntry, Is.False, "Should not be able to decompress BZip2 entry");
+ Assert.That(entry.Name, Is.EqualTo("a.dat"), "Should be able to get entry name");
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Entry should be BZip2 compressed");
+ Assert.That(zis.CanDecompressEntry, Is.False, "Should not be able to decompress BZip2 entry");
- var buffer = new byte[1];
- Assert.Throws(() => zis.Read(buffer, 0, 1), "Trying to read the stream should throw");
- }
+ var buffer = new byte[1];
+ Assert.Throws(() => zis.Read(buffer, 0, 1), "Trying to read the stream should throw");
}
///
@@ -429,5 +501,90 @@ public void ShouldBeAbleToReadEntriesWithInvalidFileNames()
}
}
}
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/507
+ ///
+ [Test]
+ [Category("Zip")]
+ public void AddingAnAESEntryWithNoPasswordShouldThrow()
+ {
+ using var memoryStream = new MemoryStream();
+ using var outStream = new ZipOutputStream(memoryStream);
+ var newEntry = new ZipEntry("test") { AESKeySize = 256 };
+
+ Assert.Throws(() => outStream.PutNextEntry(newEntry));
+ }
+
+ [Test]
+ [Category("Zip")]
+ public void ShouldThrowDescriptiveExceptionOnUncompressedDescriptorEntry()
+ {
+ using var ms = new MemoryStreamWithoutSeek();
+ using (var zos = new ZipOutputStream(ms))
+ {
+ zos.IsStreamOwner = false;
+ var entry = new ZipEntry("testentry");
+ entry.CompressionMethod = CompressionMethod.Stored;
+ entry.Flags |= (int)GeneralBitFlags.Descriptor;
+ zos.PutNextEntry(entry);
+ zos.Write(new byte[1], 0, 1);
+ zos.CloseEntry();
+ }
+
+ // Patch the Compression Method, since ZipOutputStream automatically changes it to Deflate when descriptors are used
+ ms.Seek(8, SeekOrigin.Begin);
+ ms.WriteByte((byte)CompressionMethod.Stored);
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var zis = new ZipInputStream(ms))
+ {
+ zis.IsStreamOwner = false;
+ var buf = new byte[32];
+ zis.GetNextEntry();
+
+ Assert.Throws(typeof(StreamUnsupportedException), () =>
+ {
+ zis.Read(buf, 0, buf.Length);
+ });
+ }
+ }
+
+ [Test]
+ [Category("Zip")]
+ public void IteratingOverEntriesInDirectUpdatedArchive([Values(0x0, 0x80)] byte padding)
+ {
+ using (var tempFile = new TempFile())
+ {
+ using (var zf = ZipFile.Create(tempFile))
+ {
+ zf.BeginUpdate();
+ // Add a "large" file, where the bottom 1023 bytes will become padding
+ var contentsAndPadding = Enumerable.Repeat(padding, count: 1024).ToArray();
+ zf.Add(new MemoryDataSource(contentsAndPadding), "FirstFile", CompressionMethod.Stored);
+ // Add a second file after the first one
+ zf.Add(new StringMemoryDataSource("fileContents"), "SecondFile", CompressionMethod.Stored);
+ zf.CommitUpdate();
+ }
+
+ // Since ZipFile doesn't support UpdateCommand.Modify yet we'll have to simulate it by patching the header
+ Utils.PatchFirstEntrySize(tempFile.Open(FileMode.Open), 1);
+
+ // Iterate updated entries
+ using (var fs = File.OpenRead(tempFile))
+ using (var zis = new ZipInputStream(fs))
+ {
+ var firstEntry = zis.GetNextEntry();
+ Assert.NotNull(firstEntry);
+ Assert.AreEqual(1, firstEntry.CompressedSize);
+ Assert.AreEqual(1, firstEntry.Size);
+
+ var secondEntry = zis.GetNextEntry();
+ Assert.NotNull(secondEntry, "Zip entry following padding not found");
+ var contents = new StreamReader(zis, Encoding.UTF8, false, 128, true).ReadToEnd();
+ Assert.AreEqual("fileContents", contents);
+ }
+ }
+ }
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/WindowsNameTransformHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/WindowsNameTransformHandling.cs
index 7e7d440be..8e6941251 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/WindowsNameTransformHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/WindowsNameTransformHandling.cs
@@ -2,12 +2,20 @@
using NUnit.Framework;
using System;
using System.IO;
+using System.Runtime.InteropServices;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
[TestFixture]
public class WindowsNameTransformHandling : TransformBase
{
+ [OneTimeSetUp]
+ public void TestInit() {
+ if (Path.DirectorySeparatorChar != '\\') {
+ Assert.Inconclusive("WindowsNameTransform will not work on platforms not using '\\' directory separators");
+ }
+ }
+
[Test]
public void BasicFiles()
{
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs
index fdacb69e2..0988089ee 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEncryptionHandling.cs
@@ -1,17 +1,38 @@
-using ICSharpCode.SharpZipLib.Core;
-using ICSharpCode.SharpZipLib.Zip;
+using ICSharpCode.SharpZipLib.Zip;
using NUnit.Framework;
using System;
-using System.Diagnostics;
using System.IO;
using System.Text;
using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Core;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
[TestFixture]
public class ZipEncryptionHandling
{
+ static int BufferSize = 4096;
+ // Random data that is smaller than the size of the read buffer
+ private static readonly byte[] SmallDummyData = new byte[BufferSize / 2 - 41];
+
+ // Random data that is smaller than the size of the read buffer
+ private static readonly byte[] LargeDummyData = new byte[BufferSize * 3 + 245];
+
+ static ZipEncryptionHandling()
+ {
+ var sb = new StringBuilder();
+ for (int i = 0; i < 200; i++)
+ {
+ sb.AppendLine(Guid.NewGuid().ToString());
+ }
+
+ var random = new Random();
+ random.NextBytes(SmallDummyData);
+ random.NextBytes(LargeDummyData);
+ }
+
[Test]
[Category("Encryption")]
[Category("Zip")]
@@ -42,6 +63,42 @@ public void ZipCryptoEncryption(CompressionMethod compressionMethod)
CreateZipWithEncryptedEntries("foo", 0, compressionMethod);
}
+ ///
+ /// Test Known zero length encrypted entries with ZipOutputStream.
+ /// These are entries where the entry size is set to 0 ahead of time, so that PutNextEntry will fill in the header and there will be no patching.
+ /// Test with Zip64 on and off, as the logic is different for the two.
+ ///
+ [Test]
+ public void ZipOutputStreamEncryptEmptyEntries(
+ [Values] UseZip64 useZip64,
+ [Values(0, 128, 256)] int keySize,
+ [Values(CompressionMethod.Stored, CompressionMethod.Deflated)] CompressionMethod compressionMethod)
+ {
+ using (var ms = new MemoryStream())
+ {
+ using (var zipOutputStream = new ZipOutputStream(ms))
+ {
+ zipOutputStream.IsStreamOwner = false;
+ zipOutputStream.Password = "password";
+ zipOutputStream.UseZip64 = useZip64;
+
+ ZipEntry zipEntry = new ZipEntry("emptyEntry")
+ {
+ AESKeySize = keySize,
+ CompressionMethod = compressionMethod,
+ CompressedSize = 0,
+ Crc = 0,
+ Size = 0,
+ };
+
+ zipOutputStream.PutNextEntry(zipEntry);
+ zipOutputStream.CloseEntry();
+ }
+
+ SevenZipHelper.VerifyZipWith7Zip(ms, "password");
+ }
+ }
+
[Test]
[Category("Encryption")]
[Category("Zip")]
@@ -62,16 +119,144 @@ public void ZipFileAesDecryption()
{
if (!entry.IsFile) continue;
- using (var zis = zipFile.GetInputStream(entry))
- using (var sr = new StreamReader(zis, Encoding.UTF8))
+ using var zis = zipFile.GetInputStream(entry);
+ var content = ReadAllBytes(zis);
+ Assert.AreEqual(LargeDummyData, content, "Decompressed content does not match input data");
+ }
+
+ Assert.That(zipFile, Does.PassTestArchive(testData: false), "Encrypted archive should pass validation.");
+ }
+ }
+
+ ///
+ /// Tests for reading encrypted entries using ZipInputStream.
+ ///
+ [Test]
+ [Category("Encryption")]
+ [Category("Zip")]
+ public void ZipInputStreamDecryption(
+ [Values(0, 128, 256)] int aesKeySize,
+ [Values(CompressionMethod.Stored, CompressionMethod.Deflated)]
+ CompressionMethod compressionMethod,
+ [Values] bool forceDataDescriptor)
+ {
+ var password = "password";
+
+ using (var ms = forceDataDescriptor ? new MemoryStreamWithoutSeek() : new MemoryStream())
+ {
+ WriteEncryptedZipToStream(ms, 3, 3, password, aesKeySize, compressionMethod);
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var zis = new ZipInputStream(ms))
+ {
+ zis.IsStreamOwner = false;
+ zis.Password = password;
+
+ for (int i = 0; i < 6; i++)
{
- var content = sr.ReadToEnd();
- Assert.AreEqual(DummyDataString, content, "Decompressed content does not match input data");
+ var entry = zis.GetNextEntry();
+ int fileNumber = int.Parse(entry.Name[5].ToString());
+
+ var content = ReadAllBytes(zis);
+ var largeDataExpected = fileNumber < 3;
+
+ Assert.AreEqual(largeDataExpected ? LargeDummyData : SmallDummyData, content,
+ "Decompressed content does not match input data"); }
+ }
+ }
+ }
+
+ ///
+ /// Tests for reading encrypted entries using ZipInputStream.
+ /// Verify that it is possible to skip reading of entries.
+ ///
+ [Test]
+ [Category("Encryption")]
+ [Category("Zip")]
+ public void ZipInputStreamDecryptionSupportsSkippingEntries(
+ [Values(0, 128, 256)] int aesKeySize,
+ [Values(CompressionMethod.Stored, CompressionMethod.Deflated)]
+ CompressionMethod compressionMethod,
+ [Values] bool forceDataDescriptor)
+ {
+ var password = "password";
+
+ using (var ms = forceDataDescriptor ? new MemoryStreamWithoutSeek() : new MemoryStream())
+ {
+ WriteEncryptedZipToStream(ms, 3, 3, password, aesKeySize, compressionMethod);
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var zis = new ZipInputStream(ms))
+ {
+ zis.IsStreamOwner = false;
+ zis.Password = password;
+
+ for (int i = 0; i < 6; i++)
+ {
+ var entry = zis.GetNextEntry();
+ int fileNumber = int.Parse(entry.Name[5].ToString());
+
+ if (fileNumber % 2 == 1)
+ {
+ continue;
+ }
+
+ var content = ReadAllBytes(zis);
+ var largeDataExpected = fileNumber < 3;
+
+ Assert.AreEqual(largeDataExpected ? LargeDummyData : SmallDummyData, content,
+ "Decompressed content does not match input data");
}
}
}
}
+ ///
+ /// Tests for reading encrypted entries using ZipInputStream.
+ /// Verify that it is possible to read entries only partially.
+ ///
+ [Test]
+ [Category("Encryption")]
+ [Category("Zip")]
+ public void ZipInputStreamDecryptionSupportsPartiallyReadingOfEntries(
+ [Values(0, 128, 256)] int aesKeySize,
+ [Values(CompressionMethod.Stored, CompressionMethod.Deflated)]
+ CompressionMethod compressionMethod,
+ [Values] bool forceDataDescriptor)
+ {
+ var password = "password";
+
+ using (var ms = forceDataDescriptor ? new MemoryStreamWithoutSeek() : new MemoryStream())
+ {
+ WriteEncryptedZipToStream(ms, 3, 3, password, aesKeySize, compressionMethod);
+ ms.Seek(0, SeekOrigin.Begin);
+
+ using (var zis = new ZipInputStream(ms))
+ {
+ zis.IsStreamOwner = false;
+ zis.Password = password;
+
+ for (int i = 0; i < 6; i++)
+ {
+ var entry = zis.GetNextEntry();
+ int fileNumber = int.Parse(entry.Name[5].ToString());
+
+ if (fileNumber % 2 == 1)
+ {
+ zis.ReadByte();
+ continue;
+ }
+
+ var content = ReadAllBytes(zis);
+ var largeDataExpected = fileNumber < 3;
+
+ Assert.AreEqual(largeDataExpected ? LargeDummyData : SmallDummyData, content,
+ "Decompressed content does not match input data");
+ }
+ }
+ }
+ }
+
[Test]
[Category("Encryption")]
[Category("Zip")]
@@ -93,12 +278,10 @@ public void ZipFileAesRead()
{
if (!entry.IsFile) continue;
- using (var zis = zipFile.GetInputStream(entry))
- using (var sr = new StreamReader(zis, Encoding.UTF8))
- {
- var content = sr.ReadToEnd();
- Assert.AreEqual(DummyDataString, content, "Decompressed content does not match input data");
- }
+ using var zis = zipFile.GetInputStream(entry);
+ var content = ReadAllBytes(zis);
+
+ Assert.AreEqual(LargeDummyData, content, "Decompressed content does not match input data");
}
}
}
@@ -113,14 +296,9 @@ public void ZipFileStoreAes()
{
string password = "password";
- using (var memoryStream = new MemoryStream())
+ // Make an encrypted zip file
+ using (var memoryStream = MakeAESEncryptedZipStream(password))
{
- // Try to create a zip stream
- WriteEncryptedZipToStream(memoryStream, password, 256, CompressionMethod.Stored);
-
- // reset
- memoryStream.Seek(0, SeekOrigin.Begin);
-
// try to read it
var zipFile = new ZipFile(memoryStream, leaveOpen: true)
{
@@ -134,23 +312,70 @@ public void ZipFileStoreAes()
// Should be stored rather than deflated
Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.Stored), "Entry should be stored");
+ using var zis = zipFile.GetInputStream(entry);
+ var content = ReadAllBytes(zis);
+
+ Assert.AreEqual(LargeDummyData, content, "Decompressed content does not match input data");
+ }
+ }
+ }
+
+ ///
+ /// As , but with Async reads
+ ///
+ [Test]
+ [Category("Encryption")]
+ [Category("Zip")]
+ public async Task ZipFileStoreAesAsync()
+ {
+ string password = "password";
+
+ // Make an encrypted zip file
+ using (var memoryStream = MakeAESEncryptedZipStream(password))
+ {
+ // try to read it
+ var zipFile = new ZipFile(memoryStream, leaveOpen: true)
+ {
+ Password = password
+ };
+
+ foreach (ZipEntry entry in zipFile)
+ {
+ // Should be stored rather than deflated
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.Stored), "Entry should be stored");
+
using (var zis = zipFile.GetInputStream(entry))
- using (var sr = new StreamReader(zis, Encoding.UTF8))
{
- var content = sr.ReadToEnd();
- Assert.That(content, Is.EqualTo(DummyDataString), "Decompressed content does not match input data");
+ using var inputStream = zipFile.GetInputStream(entry);
+ var content = ReadAllBytes(inputStream);
+
+ Assert.That(content, Is.EqualTo(LargeDummyData), "Decompressed content does not match input data");
}
}
}
}
+ // Shared helper for the ZipFileStoreAes tests
+ private static Stream MakeAESEncryptedZipStream(string password)
+ {
+ var memoryStream = new MemoryStream();
+
+ // Try to create a zip stream
+ WriteEncryptedZipToStream(memoryStream, password, 256, CompressionMethod.Stored);
+
+ // reset
+ memoryStream.Seek(0, SeekOrigin.Begin);
+
+ return memoryStream;
+ }
+
///
/// Test using AES encryption on a file whose contents are Stored rather than deflated
///
[Test]
[Category("Encryption")]
[Category("Zip")]
- public void ZipFileStoreAesPartialRead()
+ public void ZipFileStoreAesPartialRead([Values(1, 7, 17)] int readSize)
{
string password = "password";
@@ -179,26 +404,24 @@ public void ZipFileStoreAesPartialRead()
{
using (var zis = zipFile.GetInputStream(entry))
{
- byte[] buffer = new byte[1];
+ byte[] buffer = new byte[readSize];
while (true)
{
- int b = zis.ReadByte();
+ int read = zis.Read(buffer, 0, readSize);
- if (b == -1)
+ if (read == 0)
break;
- ms.WriteByte((byte)b);
+ ms.Write(buffer, 0, read);
}
}
ms.Seek(0, SeekOrigin.Begin);
- using (var sr = new StreamReader(ms, Encoding.UTF8))
- {
- var content = sr.ReadToEnd();
- Assert.That(content, Is.EqualTo(DummyDataString), "Decompressed content does not match input data");
- }
+ var content = ms.ToArray();
+
+ Assert.That(content, Is.EqualTo(LargeDummyData), "Decompressed content does not match input data");
}
}
}
@@ -252,12 +475,10 @@ public void ZipFileAesAdd()
Assert.That(originalEntry.AESKeySize, Is.EqualTo(keySize));
- using (var zis = zipFile.GetInputStream(originalEntry))
- using (var sr = new StreamReader(zis, Encoding.UTF8))
- {
- var content = sr.ReadToEnd();
- Assert.That(content, Is.EqualTo(DummyDataString), "Decompressed content does not match input data");
- }
+ using var zis = zipFile.GetInputStream(originalEntry);
+ var content = ReadAllBytes(zis);
+
+ Assert.That(content, Is.EqualTo(LargeDummyData), "Decompressed content does not match input data");
}
// Check the additional entry
@@ -277,7 +498,7 @@ public void ZipFileAesAdd()
}
// As an extra test, verify the file with 7-zip
- VerifyZipWith7Zip(memoryStream, password);
+ SevenZipHelper.VerifyZipWith7Zip(memoryStream, password);
}
}
@@ -295,7 +516,7 @@ public void ZipFileAesDelete()
using (var memoryStream = new MemoryStream())
{
// Try to create a zip stream
- WriteEncryptedZipToStream(memoryStream, 3, password, keySize, CompressionMethod.Deflated);
+ WriteEncryptedZipToStream(memoryStream, 3, 0, password, keySize, CompressionMethod.Deflated);
// reset
memoryStream.Seek(0, SeekOrigin.Begin);
@@ -338,8 +559,9 @@ public void ZipFileAesDelete()
using (var zis = zipFile.GetInputStream(originalEntry))
using (var sr = new StreamReader(zis, Encoding.UTF8))
{
- var content = sr.ReadToEnd();
- Assert.That(content, Is.EqualTo(DummyDataString), "Decompressed content does not match input data");
+ var content = ReadAllBytes(zis);
+
+ Assert.That(content, Is.EqualTo(LargeDummyData), "Decompressed content does not match input data");
}
}
@@ -353,66 +575,125 @@ public void ZipFileAesDelete()
using (var zis = zipFile.GetInputStream(originalEntry))
using (var sr = new StreamReader(zis, Encoding.UTF8))
{
- var content = sr.ReadToEnd();
- Assert.That(content, Is.EqualTo(DummyDataString), "Decompressed content does not match input data");
+ var content = ReadAllBytes(zis);
+
+ Assert.That(content, Is.EqualTo(LargeDummyData), "Decompressed content does not match input data");
}
}
}
}
// As an extra test, verify the file with 7-zip
- VerifyZipWith7Zip(memoryStream, password);
+ SevenZipHelper.VerifyZipWith7Zip(memoryStream, password);
}
}
- private static readonly string[] possible7zPaths = new[] {
- // Check in PATH
- "7z", "7za",
+ // This is a zip file with one AES encrypted entry, whose password in an empty string.
+ const string TestFileWithEmptyPassword = @"UEsDBDMACQBjACaj0FAyKbop//////////8EAB8AdGVzdAEAEAA4AAAA
+ AAAAAFIAAAAAAAAAAZkHAAIAQUUDCABADvo3YqmCtIE+lhw26kjbqkGsLEOk6bVA+FnSpVD4yGP4Mr66Hs14aTtsPUaANX2
+ Z6qZczEmwoaNQpNBnKl7p9YOG8GSHDfTCUU/AZvT4yGFhUEsHCDIpuilSAAAAAAAAADgAAAAAAAAAUEsBAjMAMwAJAGMAJq
+ PQUDIpuin//////////wQAHwAAAAAAAAAAAAAAAAAAAHRlc3QBABAAOAAAAAAAAABSAAAAAAAAAAGZBwACAEFFAwgAUEsFBgAAAAABAAEAUQAAAKsAAAAAAA==";
+
+ ///
+ /// Test reading an AES encrypted entry whose password is an empty string.
+ ///
+ ///
+ /// Test added for https://github.com/icsharpcode/SharpZipLib/issues/471.
+ ///
+ [Test]
+ [Category("Zip")]
+ public void ZipFileAESReadWithEmptyPassword()
+ {
+ var fileBytes = Convert.FromBase64String(TestFileWithEmptyPassword);
+
+ using (var ms = new MemoryStream(fileBytes))
+ using (var zipFile = new ZipFile(ms, leaveOpen: true))
+ {
+ zipFile.Password = string.Empty;
+
+ var entry = zipFile.FindEntry("test", true);
- // Check in default install location
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "7-Zip", "7z.exe"),
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "7-Zip", "7z.exe"),
- };
+ using (var inputStream = zipFile.GetInputStream(entry))
+ using (var sr = new StreamReader(inputStream, Encoding.UTF8))
+ {
+ var content = sr.ReadToEnd();
+ Assert.That(content, Is.EqualTo("Lorem ipsum dolor sit amet, consectetur adipiscing elit."), "Decompressed content does not match expected data");
+ }
+ }
+ }
+
+ // This is a zip file with three AES encrypted entry, whose password is password.
+ const string TestFileWithThreeEntries = @"UEsDBDMAAQBjAI9jbFIAAAAAIQAAAAUAAAAJAAsARmlsZTEudHh0AZkHAAIAQUUDAAAoyz4gbB4/SnNvoPSBMVS9Zhp5sKKD
+ GnLy8zwsuV0Jh/RQSwMEMwABAGMAnWNsUgAAAAAhAAAABQAAAAkACwBGaWxlMi50eHQBmQcAAgBBRQMAANoCDbQUG7iCJgGC2/5OrmUQUk/+fACL804W0bboF8YMM1BLAwQzAA
+ EAYwCjY2xSAAAAACEAAAAFAAAACQALAEZpbGUzLnR4dAGZBwACAEFFAwAAqmBqgBkl3tP6ND0uCD50mhwfOtbmwV1IKyUVK5wGVQUiUEsBAj8AMwABAGMAj2NsUgAAAAAhAAAA
+ BQAAAAkALwAAAAAAAAAgAAAAAAAAAEZpbGUxLnR4dAoAIAAAAAAAAQAYAApFb9MyF9cB7fEh1jIX1wHNrjnNMhfXAQGZBwACAEFFAwAAUEsBAj8AMwABAGMAnWNsUgAAAAAhAA
+ AABQAAAAkALwAAAAAAAAAgAAAAUwAAAEZpbGUyLnR4dAoAIAAAAAAAAQAYAK5pWOQyF9cBdTCL5TIX1wGab3HVMhfXAQGZBwACAEFFAwAAUEsBAj8AMwABAGMAo2NsUgAAAAAh
+ AAAABQAAAAkALwAAAAAAAAAgAAAApgAAAEZpbGUzLnR4dAoAIAAAAAAAAQAYANB1M+kyF9cB0gxl6jIX1wGqVSHWMhfXAQGZBwACAEFFAwAAUEsFBgAAAAADAAMAMgEAAPkAAAAAAA==";
- public static bool TryGet7zBinPath(out string path7z)
+ ///
+ /// Test reading an AES encrypted file and skipping some entries by not reading form the stream
+ ///
+ [Test]
+ [Category("Zip")]
+ public void ZipInputStreamAESReadSkippingEntriesIsPossible()
{
- var runTimeLimit = TimeSpan.FromSeconds(3);
+ var fileBytes = Convert.FromBase64String(TestFileWithThreeEntries);
- foreach (var testPath in possible7zPaths)
+ using (var ms = new MemoryStream(fileBytes))
+ using (var zis = new ZipInputStream(ms) { IsStreamOwner = false})
{
- try
+ zis.Password = "password";
+
+ for (int i = 0; i < 3; i++)
{
- var p = Process.Start(new ProcessStartInfo(testPath, "i")
+ var entry = zis.GetNextEntry();
+ if (i == 1)
{
- RedirectStandardOutput = true
- });
- while (!p.StandardOutput.EndOfStream && (DateTime.Now - p.StartTime) < runTimeLimit)
+ continue;
+ }
+
+ using (var sr = new StreamReader(zis, Encoding.UTF8, leaveOpen: true, detectEncodingFromByteOrderMarks: true, bufferSize: 1024))
{
- p.StandardOutput.DiscardBufferedData();
+ var content = sr.ReadToEnd();
+ Assert.AreEqual(Path.GetFileNameWithoutExtension(entry.Name), content, "Decompressed content does not match input data");
}
- if (!p.HasExited)
+ }
+ }
+ }
+
+ ///
+ /// Test reading an AES encrypted file and reading some entries only partially be not reading to the end of stream.
+ ///
+ [Test]
+ [Category("Zip")]
+ public void ZipInputStreamAESReadEntriesCanBeReadPartially()
+ {
+ var fileBytes = Convert.FromBase64String(TestFileWithThreeEntries);
+
+ using (var ms = new MemoryStream(fileBytes))
+ using (var zis = new ZipInputStream(ms) { IsStreamOwner = false})
+ {
+ zis.Password = "password";
+
+ for (int i = 0; i < 3; i++)
+ {
+ var entry = zis.GetNextEntry();
+ if (i == 1)
{
- p.Close();
- Assert.Warn($"Timed out checking for 7z binary in \"{testPath}\"!");
+ zis.ReadByte();
continue;
}
- if (p.ExitCode == 0)
+ using (var sr = new StreamReader(zis, Encoding.UTF8, leaveOpen: true, detectEncodingFromByteOrderMarks: true, bufferSize: 1024))
{
- path7z = testPath;
- return true;
+ var content = sr.ReadToEnd();
+ Assert.AreEqual(Path.GetFileNameWithoutExtension(entry.Name), content, "Decompressed content does not match input data");
}
}
- catch (Exception)
- {
- continue;
- }
}
- path7z = null;
- return false;
- }
+ }
- public void WriteEncryptedZipToStream(Stream stream, string password, int keySize, CompressionMethod compressionMethod = CompressionMethod.Deflated)
+ public static void WriteEncryptedZipToStream(Stream stream, string password, int keySize, CompressionMethod compressionMethod = CompressionMethod.Deflated)
{
using (var zs = new ZipOutputStream(stream))
{
@@ -420,11 +701,11 @@ public void WriteEncryptedZipToStream(Stream stream, string password, int keySiz
zs.SetLevel(9); // 0-9, 9 being the highest level of compression
zs.Password = password; // optional. Null is the same as not setting. Required if using AES.
- AddEncrypedEntryToStream(zs, $"test", keySize, compressionMethod);
+ AddEncrypedEntryToStream(zs, $"test", keySize, compressionMethod, LargeDummyData);
}
}
- public void WriteEncryptedZipToStream(Stream stream, int entryCount, string password, int keySize, CompressionMethod compressionMethod)
+ public static void WriteEncryptedZipToStream(Stream stream, int entryCount, int shortEntryCount, string password, int keySize, CompressionMethod compressionMethod = CompressionMethod.Deflated)
{
using (var zs = new ZipOutputStream(stream))
{
@@ -434,12 +715,17 @@ public void WriteEncryptedZipToStream(Stream stream, int entryCount, string pass
for (int i = 0; i < entryCount; i++)
{
- AddEncrypedEntryToStream(zs, $"test-{i}", keySize, compressionMethod);
+ AddEncrypedEntryToStream(zs, $"test-{i}", keySize, compressionMethod, LargeDummyData);
}
+
+ for (int i = 0; i < shortEntryCount; i++)
+ {
+ AddEncrypedEntryToStream(zs, $"test-{i + entryCount}", keySize, compressionMethod, SmallDummyData);
+ }
}
}
- private void AddEncrypedEntryToStream(ZipOutputStream zipOutputStream, string entryName, int keySize, CompressionMethod compressionMethod)
+ private static void AddEncrypedEntryToStream(ZipOutputStream zipOutputStream, string entryName, int keySize, CompressionMethod compressionMethod, byte[] content)
{
ZipEntry zipEntry = new ZipEntry(entryName)
{
@@ -450,9 +736,7 @@ private void AddEncrypedEntryToStream(ZipOutputStream zipOutputStream, string en
zipOutputStream.PutNextEntry(zipEntry);
- byte[] dummyData = Encoding.UTF8.GetBytes(DummyDataString);
-
- using (var dummyStream = new MemoryStream(dummyData))
+ using (var dummyStream = new MemoryStream(content))
{
dummyStream.CopyTo(zipOutputStream);
}
@@ -465,54 +749,15 @@ public void CreateZipWithEncryptedEntries(string password, int keySize, Compress
using (var ms = new MemoryStream())
{
WriteEncryptedZipToStream(ms, password, keySize, compressionMethod);
- VerifyZipWith7Zip(ms, password);
+ SevenZipHelper.VerifyZipWith7Zip(ms, password);
}
}
-
- ///
- /// Helper function to verify the provided zip stream with 7Zip.
- ///
- /// A stream containing the zip archive to test.
- /// The password for the archive.
- private void VerifyZipWith7Zip(Stream zipStream, string password)
+
+ private static byte[] ReadAllBytes(Stream input)
{
- if (TryGet7zBinPath(out string path7z))
- {
- Console.WriteLine($"Using 7z path: \"{path7z}\"");
-
- var fileName = Path.GetTempFileName();
-
- try
- {
- using (var fs = File.OpenWrite(fileName))
- {
- zipStream.Seek(0, SeekOrigin.Begin);
- zipStream.CopyTo(fs);
- }
-
- var p = Process.Start(path7z, $"t -p{password} \"{fileName}\"");
- if (!p.WaitForExit(2000))
- {
- Assert.Warn("Timed out verifying zip file!");
- }
-
- Assert.AreEqual(0, p.ExitCode, "Archive verification failed");
- }
- finally
- {
- File.Delete(fileName);
- }
- }
- else
- {
- Assert.Warn("Skipping file verification since 7za is not in path");
- }
+ using MemoryStream ms = new MemoryStream();
+ input.CopyTo(ms);
+ return ms.ToArray();
}
-
-
- private const string DummyDataString = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-Fusce bibendum diam ac nunc rutrum ornare. Maecenas blandit elit ligula, eget suscipit lectus rutrum eu.
-Maecenas aliquam, purus mattis pulvinar pharetra, nunc orci maximus justo, sed facilisis massa dui sed lorem.
-Vestibulum id iaculis leo. Duis porta ante lorem. Duis condimentum enim nec lorem tristique interdum. Fusce in faucibus libero.";
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryFactoryHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryFactoryHandling.cs
index ab2ae3744..5a892abde 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryFactoryHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryFactoryHandling.cs
@@ -87,98 +87,181 @@ public void CreateInMemoryValues()
[Test]
[Category("Zip")]
[Category("CreatesTempFile")]
- public void CreatedValues()
+ [Platform("Win32NT")]
+ public void CreatedFileEntriesUsesExpectedAttributes()
{
string tempDir = GetTempFilePath();
- Assert.IsNotNull(tempDir, "No permission to execute this test?");
+ if (tempDir == null) Assert.Inconclusive("No permission to execute this test?");
tempDir = Path.Combine(tempDir, "SharpZipTest");
+ Directory.CreateDirectory(tempDir);
- if (tempDir != null)
+ try
{
- Directory.CreateDirectory(tempDir);
+ string tempFile = Path.Combine(tempDir, "SharpZipTest.Zip");
+
+ using (FileStream f = File.Create(tempFile, 1024))
+ {
+ f.WriteByte(0);
+ }
+
+ FileAttributes attributes = FileAttributes.Hidden;
+
+ File.SetAttributes(tempFile, attributes);
+ ZipEntryFactory factory = null;
+ ZipEntry entry;
+ int combinedAttributes = 0;
try
{
- // Note the seconds returned will be even!
- var createTime = new DateTime(2100, 2, 27, 11, 07, 56);
- var lastWriteTime = new DateTime(2050, 11, 3, 7, 23, 32);
- var lastAccessTime = new DateTime(2050, 11, 3, 0, 42, 12);
-
- string tempFile = Path.Combine(tempDir, "SharpZipTest.Zip");
- using (FileStream f = File.Create(tempFile, 1024))
- {
- f.WriteByte(0);
- }
-
- File.SetCreationTime(tempFile, createTime);
- File.SetLastWriteTime(tempFile, lastWriteTime);
- File.SetLastAccessTime(tempFile, lastAccessTime);
-
- FileAttributes attributes = FileAttributes.Hidden;
-
- File.SetAttributes(tempFile, attributes);
- ZipEntryFactory factory = null;
- ZipEntry entry;
- int combinedAttributes = 0;
-
- try
- {
- factory = new ZipEntryFactory();
-
- factory.Setting = ZipEntryFactory.TimeSetting.CreateTime;
- factory.GetAttributes = ~((int)FileAttributes.ReadOnly);
- factory.SetAttributes = (int)FileAttributes.ReadOnly;
- combinedAttributes = (int)(FileAttributes.ReadOnly | FileAttributes.Hidden);
-
- entry = factory.MakeFileEntry(tempFile);
- Assert.AreEqual(createTime, entry.DateTime, "Create time failure");
- Assert.AreEqual(entry.ExternalFileAttributes, combinedAttributes);
- Assert.AreEqual(1, entry.Size);
-
- factory.Setting = ZipEntryFactory.TimeSetting.LastAccessTime;
- entry = factory.MakeFileEntry(tempFile);
- Assert.AreEqual(lastAccessTime, entry.DateTime, "Access time failure");
- Assert.AreEqual(1, entry.Size);
-
- factory.Setting = ZipEntryFactory.TimeSetting.LastWriteTime;
- entry = factory.MakeFileEntry(tempFile);
- Assert.AreEqual(lastWriteTime, entry.DateTime, "Write time failure");
- Assert.AreEqual(1, entry.Size);
- }
- finally
- {
- File.Delete(tempFile);
- }
-
- // Do the same for directories
- // Note the seconds returned will be even!
- createTime = new DateTime(2090, 2, 27, 11, 7, 56);
- lastWriteTime = new DateTime(2107, 12, 31, 23, 59, 58);
- lastAccessTime = new DateTime(1980, 1, 1, 1, 0, 0);
-
- Directory.SetCreationTime(tempDir, createTime);
- Directory.SetLastWriteTime(tempDir, lastWriteTime);
- Directory.SetLastAccessTime(tempDir, lastAccessTime);
-
- factory.Setting = ZipEntryFactory.TimeSetting.CreateTime;
- entry = factory.MakeDirectoryEntry(tempDir);
- Assert.AreEqual(createTime, entry.DateTime, "Directory create time failure");
- Assert.IsTrue((entry.ExternalFileAttributes & (int)FileAttributes.Directory) == (int)FileAttributes.Directory);
-
- factory.Setting = ZipEntryFactory.TimeSetting.LastAccessTime;
- entry = factory.MakeDirectoryEntry(tempDir);
- Assert.AreEqual(lastAccessTime, entry.DateTime, "Directory access time failure");
-
- factory.Setting = ZipEntryFactory.TimeSetting.LastWriteTime;
- entry = factory.MakeDirectoryEntry(tempDir);
- Assert.AreEqual(lastWriteTime, entry.DateTime, "Directory write time failure");
+ factory = new ZipEntryFactory();
+
+ factory.GetAttributes = ~((int)FileAttributes.ReadOnly);
+ factory.SetAttributes = (int)FileAttributes.ReadOnly;
+ combinedAttributes = (int)(FileAttributes.ReadOnly | FileAttributes.Hidden);
+
+ entry = factory.MakeFileEntry(tempFile);
+ Assert.AreEqual(entry.ExternalFileAttributes, combinedAttributes);
+ Assert.AreEqual(1, entry.Size);
}
finally
{
- Directory.Delete(tempDir, true);
+ File.Delete(tempFile);
}
}
+ finally
+ {
+ Directory.Delete(tempDir, true);
+ }
+
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(ZipEntryFactory.TimeSetting.CreateTime)]
+ [TestCase(ZipEntryFactory.TimeSetting.LastAccessTime)]
+ [TestCase(ZipEntryFactory.TimeSetting.LastWriteTime)]
+ public void CreatedFileEntriesUsesExpectedTime(ZipEntryFactory.TimeSetting timeSetting)
+ {
+ string tempDir = GetTempFilePath();
+ if (tempDir == null) Assert.Inconclusive("No permission to execute this test?");
+
+ tempDir = Path.Combine(tempDir, "SharpZipTest");
+
+ // Note the seconds returned will be even!
+ var expectedTime = new DateTime(2100, 2, 27, 11, 07, 56);
+
+ Directory.CreateDirectory(tempDir);
+
+ try
+ {
+
+ string tempFile = Path.Combine(tempDir, "SharpZipTest.Zip");
+
+ using (FileStream f = File.Create(tempFile, 1024))
+ {
+ f.WriteByte(0);
+ }
+
+ DateTime fileTime = DateTime.MinValue;
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.CreateTime) {
+ File.SetCreationTime(tempFile, expectedTime);
+ fileTime = File.GetCreationTime(tempFile);
+ }
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.LastAccessTime){
+ File.SetLastAccessTime(tempFile, expectedTime);
+ fileTime = File.GetLastAccessTime(tempFile);
+ }
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.LastWriteTime) {
+ File.SetLastWriteTime(tempFile, expectedTime);
+ fileTime = File.GetLastWriteTime(tempFile);
+ }
+
+ if(fileTime != expectedTime) {
+ Assert.Inconclusive("File time could not be altered");
+ }
+
+ var factory = new ZipEntryFactory();
+
+ factory.Setting = timeSetting;
+
+ var entry = factory.MakeFileEntry(tempFile);
+ Assert.AreEqual(expectedTime, entry.DateTime);
+ Assert.AreEqual(1, entry.Size);
+
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+ }
+
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ [TestCase(ZipEntryFactory.TimeSetting.CreateTime)]
+ [TestCase(ZipEntryFactory.TimeSetting.LastAccessTime)]
+ [TestCase(ZipEntryFactory.TimeSetting.LastWriteTime)]
+ public void CreatedDirectoryEntriesUsesExpectedTime(ZipEntryFactory.TimeSetting timeSetting)
+ {
+ string tempDir = GetTempFilePath();
+ if (tempDir == null) Assert.Inconclusive("No permission to execute this test?");
+
+ tempDir = Path.Combine(tempDir, "SharpZipTest");
+
+ // Note the seconds returned will be even!
+ var expectedTime = new DateTime(2100, 2, 27, 11, 07, 56);
+
+ Directory.CreateDirectory(tempDir);
+
+ try
+ {
+
+ string tempFile = Path.Combine(tempDir, "SharpZipTest.Zip");
+
+ using (FileStream f = File.Create(tempFile, 1024))
+ {
+ f.WriteByte(0);
+ }
+
+ DateTime dirTime = DateTime.MinValue;
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.CreateTime) {
+ Directory.SetCreationTime(tempFile, expectedTime);
+ dirTime = Directory.GetCreationTime(tempDir);
+ }
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.LastAccessTime){
+ Directory.SetLastAccessTime(tempDir, expectedTime);
+ dirTime = Directory.GetLastAccessTime(tempDir);
+ }
+
+ if (timeSetting == ZipEntryFactory.TimeSetting.LastWriteTime) {
+ Directory.SetLastWriteTime(tempDir, expectedTime);
+ dirTime = Directory.GetLastWriteTime(tempDir);
+ }
+
+ if(dirTime != expectedTime) {
+ Assert.Inconclusive("Directory time could not be altered");
+ }
+
+ var factory = new ZipEntryFactory();
+
+ factory.Setting = timeSetting;
+
+ var entry = factory.MakeDirectoryEntry(tempDir);
+ Assert.AreEqual(expectedTime, entry.DateTime);
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+ }
+
}
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs
index 2babfafd2..4b08f7519 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipEntryHandling.cs
@@ -195,10 +195,12 @@ public void DateAndTime()
// Over the limit are set to max.
ze.DateTime = new DateTime(2108, 1, 1);
+ ze.DosTime = ze.DosTime;
Assert.AreEqual(new DateTime(2107, 12, 31, 23, 59, 58), ze.DateTime);
// Under the limit are set to min.
ze.DateTime = new DateTime(1906, 12, 4);
+ ze.DosTime = ze.DosTime;
Assert.AreEqual(new DateTime(1980, 1, 1, 0, 0, 0), ze.DateTime);
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
index c79137130..e594cd17f 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs
@@ -5,6 +5,8 @@
using System;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
+using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -22,6 +24,7 @@ public void NullStreamDetected()
try
{
+ // ReSharper disable once ExpressionIsAlwaysNull
bad = new ZipFile(nullStream);
}
catch
@@ -59,7 +62,7 @@ public void Zip64Entries()
}
zipFile.CommitUpdate();
- Assert.IsTrue(zipFile.TestArchive(true));
+ Assert.That(zipFile, Does.PassTestArchive());
Assert.AreEqual(target, zipFile.Count, "Incorrect number of entries stored");
}
}
@@ -78,7 +81,7 @@ public void EmbeddedArchive()
f.Add(m, "a.dat");
f.Add(m, "b.dat");
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
byte[] rawArchive = memStream.ToArray();
@@ -114,7 +117,7 @@ public void Zip64Useage()
f.Add(m, "a.dat");
f.Add(m, "b.dat");
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
byte[] rawArchive = memStream.ToArray();
@@ -209,7 +212,7 @@ public void FakeZip64Locator()
f.BeginUpdate(new MemoryArchiveStorage());
f.Add(m, "a.dat", CompressionMethod.Stored);
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
memStream.Seek(0, SeekOrigin.Begin);
@@ -236,30 +239,30 @@ public void Zip64Offset()
[Category("Zip")]
public void BasicEncryption()
{
- const string TestValue = "0001000";
+ const string testValue = "0001000";
var memStream = new MemoryStream();
- using (ZipFile f = new ZipFile(memStream))
+ using (var zf = new ZipFile(memStream))
{
- f.IsStreamOwner = false;
- f.Password = "Hello";
+ zf.IsStreamOwner = false;
+ zf.Password = "Hello";
- var m = new StringMemoryDataSource(TestValue);
- f.BeginUpdate(new MemoryArchiveStorage());
- f.Add(m, "a.dat");
- f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true), "Archive test should pass");
+ var m = new StringMemoryDataSource(testValue);
+ zf.BeginUpdate(new MemoryArchiveStorage());
+ zf.Add(m, "a.dat");
+ zf.CommitUpdate();
+ Assert.IsTrue(zf.TestArchive(testData: true), "Archive test should pass");
}
- using (ZipFile g = new ZipFile(memStream))
+ using (var zf = new ZipFile(memStream))
{
- g.Password = "Hello";
- ZipEntry ze = g[0];
+ zf.Password = "Hello";
+ var ze = zf[0];
Assert.IsTrue(ze.IsCrypted, "Entry should be encrypted");
- using (StreamReader r = new StreamReader(g.GetInputStream(0)))
+ using (var r = new StreamReader(zf.GetInputStream(entryIndex: 0)))
{
- string data = r.ReadToEnd();
- Assert.AreEqual(TestValue, data);
+ var data = r.ReadToEnd();
+ Assert.AreEqual(testValue, data);
}
}
}
@@ -269,38 +272,38 @@ public void BasicEncryption()
[Category("CreatesTempFile")]
public void BasicEncryptionToDisk()
{
- const string TestValue = "0001000";
- string tempFile = GetTempFilePath();
+ const string testValue = "0001000";
+ var tempFile = GetTempFilePath();
Assert.IsNotNull(tempFile, "No permission to execute this test?");
tempFile = Path.Combine(tempFile, "SharpZipTest.Zip");
- using (ZipFile f = ZipFile.Create(tempFile))
+ using (var zf = ZipFile.Create(tempFile))
{
- f.Password = "Hello";
+ zf.Password = "Hello";
- var m = new StringMemoryDataSource(TestValue);
- f.BeginUpdate();
- f.Add(m, "a.dat");
- f.CommitUpdate();
+ var m = new StringMemoryDataSource(testValue);
+ zf.BeginUpdate();
+ zf.Add(m, "a.dat");
+ zf.CommitUpdate();
}
- using (ZipFile f = new ZipFile(tempFile))
+ using (var zf = new ZipFile(tempFile))
{
- f.Password = "Hello";
- Assert.IsTrue(f.TestArchive(true), "Archive test should pass");
+ zf.Password = "Hello";
+ Assert.IsTrue(zf.TestArchive(testData: true), "Archive test should pass");
}
- using (ZipFile g = new ZipFile(tempFile))
+ using (var zf = new ZipFile(tempFile))
{
- g.Password = "Hello";
- ZipEntry ze = g[0];
+ zf.Password = "Hello";
+ ZipEntry ze = zf[0];
Assert.IsTrue(ze.IsCrypted, "Entry should be encrypted");
- using (StreamReader r = new StreamReader(g.GetInputStream(0)))
+ using (var r = new StreamReader(zf.GetInputStream(entryIndex: 0)))
{
- string data = r.ReadToEnd();
- Assert.AreEqual(TestValue, data);
+ var data = r.ReadToEnd();
+ Assert.AreEqual(testValue, data);
}
}
@@ -311,14 +314,14 @@ public void BasicEncryptionToDisk()
[Category("Zip")]
public void AddEncryptedEntriesToExistingArchive()
{
- const string TestValue = "0001000";
+ const string testValue = "0001000";
var memStream = new MemoryStream();
using (ZipFile f = new ZipFile(memStream))
{
f.IsStreamOwner = false;
f.UseZip64 = UseZip64.Off;
- var m = new StringMemoryDataSource(TestValue);
+ var m = new StringMemoryDataSource(testValue);
f.BeginUpdate(new MemoryArchiveStorage());
f.Add(m, "a.dat");
f.CommitUpdate();
@@ -333,10 +336,10 @@ public void AddEncryptedEntriesToExistingArchive()
using (StreamReader r = new StreamReader(g.GetInputStream(0)))
{
string data = r.ReadToEnd();
- Assert.AreEqual(TestValue, data);
+ Assert.AreEqual(testValue, data);
}
- var n = new StringMemoryDataSource(TestValue);
+ var n = new StringMemoryDataSource(testValue);
g.Password = "Axolotyl";
g.UseZip64 = UseZip64.Off;
@@ -351,7 +354,7 @@ public void AddEncryptedEntriesToExistingArchive()
using (StreamReader r = new StreamReader(g.GetInputStream(0)))
{
string data = r.ReadToEnd();
- Assert.AreEqual(TestValue, data);
+ Assert.AreEqual(testValue, data);
}
}
}
@@ -365,7 +368,7 @@ private void TryDeleting(byte[] master, int totalEntries, int additions, params
{
f.IsStreamOwner = false;
Assert.AreEqual(totalEntries, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
f.BeginUpdate(new MemoryArchiveStorage());
for (int i = 0; i < additions; ++i)
@@ -399,7 +402,7 @@ private void TryDeleting(byte[] master, int totalEntries, int additions, params
{
f.IsStreamOwner = false;
Assert.AreEqual(totalEntries, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
f.BeginUpdate(new MemoryArchiveStorage());
for (int i = 0; i < additions; ++i)
@@ -438,17 +441,21 @@ public void AddAndDeleteEntriesMemory()
f.IsStreamOwner = false;
f.BeginUpdate(new MemoryArchiveStorage());
- f.Add(new StringMemoryDataSource("Hello world"), @"z:\a\a.dat");
+ f.Add(new StringMemoryDataSource("Hello world"), Utils.SystemRoot + @"a\a.dat");
f.Add(new StringMemoryDataSource("Another"), @"\b\b.dat");
f.Add(new StringMemoryDataSource("Mr C"), @"c\c.dat");
f.Add(new StringMemoryDataSource("Mrs D was a star"), @"d\d.dat");
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
+ foreach (ZipEntry entry in f)
+ {
+ Console.WriteLine($" - {entry.Name}");
+ }
}
byte[] master = memStream.ToArray();
- TryDeleting(master, 4, 1, @"z:\a\a.dat");
+ TryDeleting(master, 4, 1, Utils.SystemRoot + @"a\a.dat");
TryDeleting(master, 4, 1, @"\a\a.dat");
TryDeleting(master, 4, 1, @"a/a.dat");
@@ -500,27 +507,27 @@ public void AddAndDeleteEntries()
f.Add(addFile2);
f.AddDirectory(addDirectory);
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
using (ZipFile f = new ZipFile(tempFile))
{
Assert.AreEqual(3, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
// Delete file
f.BeginUpdate();
f.Delete(f[0]);
f.CommitUpdate();
Assert.AreEqual(2, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
// Delete directory
f.BeginUpdate();
f.Delete(f[1]);
f.CommitUpdate();
Assert.AreEqual(1, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
File.Delete(addFile);
@@ -582,6 +589,29 @@ public void RoundTripInMemory()
}
}
+ ///
+ /// Simple async round trip test for ZipFile class
+ ///
+ [TestCase(CompressionMethod.Stored)]
+ [TestCase(CompressionMethod.Deflated)]
+ [TestCase(CompressionMethod.BZip2)]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task RoundTripInMemoryAsync(CompressionMethod compressionMethod)
+ {
+ var storage = new MemoryStream();
+ MakeZipFile(storage, compressionMethod, false, "", 10, 1024, "");
+
+ using (ZipFile zipFile = new ZipFile(storage))
+ {
+ foreach (ZipEntry e in zipFile)
+ {
+ Stream instream = zipFile.GetInputStream(e);
+ await CheckKnownEntryAsync(instream, 1024);
+ }
+ }
+ }
+
[Test]
[Category("Zip")]
public void AddToEmptyArchive()
@@ -603,7 +633,7 @@ public void AddToEmptyArchive()
f.Add(addFile);
f.CommitUpdate();
Assert.AreEqual(1, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
}
using (ZipFile f = new ZipFile(tempFile))
@@ -613,7 +643,7 @@ public void AddToEmptyArchive()
f.Delete(f[0]);
f.CommitUpdate();
Assert.AreEqual(0, f.Count);
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
f.Close();
}
@@ -638,7 +668,7 @@ public void CreateEmptyArchive()
{
f.BeginUpdate();
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
f.Close();
}
@@ -655,28 +685,25 @@ public void CreateEmptyArchive()
[Category("CreatesTempFile")]
public void CreateArchiveWithNoCompression()
{
-
- using (var sourceFile = Utils.GetDummyFile())
- using (var zipFile = Utils.GetDummyFile(0))
+ using var sourceFile = Utils.GetDummyFile();
+ using var zipFile = Utils.GetDummyFile(0);
+ var inputContent = File.ReadAllText(sourceFile);
+ using (var zf = ZipFile.Create(zipFile))
{
- var inputContent = File.ReadAllText(sourceFile.Filename);
- using (ZipFile f = ZipFile.Create(zipFile.Filename))
- {
- f.BeginUpdate();
- f.Add(sourceFile.Filename, CompressionMethod.Stored);
- f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
- f.Close();
- }
+ zf.BeginUpdate();
+ zf.Add(sourceFile, CompressionMethod.Stored);
+ zf.CommitUpdate();
+ Assert.That(zf, Does.PassTestArchive());
+ zf.Close();
+ }
- using (ZipFile f = new ZipFile(zipFile.Filename))
+ using (var zf = new ZipFile(zipFile))
+ {
+ Assert.AreEqual(1, zf.Count);
+ using (var sr = new StreamReader(zf.GetInputStream(zf[0])))
{
- Assert.AreEqual(1, f.Count);
- using (var sr = new StreamReader(f.GetInputStream(f[0])))
- {
- var outputContent = sr.ReadToEnd();
- Assert.AreEqual(inputContent, outputContent, "extracted content does not match source content");
- }
+ var outputContent = sr.ReadToEnd();
+ Assert.AreEqual(inputContent, outputContent, "extracted content does not match source content");
}
}
}
@@ -838,7 +865,7 @@ public void ArchiveTesting()
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsTrue(testFile.TestArchive(true), "Unexpected error in archive detected");
+ Assert.That(testFile, Does.PassTestArchive(), "Unexpected error in archive detected");
byte[] corrupted = new byte[compressedData.Length];
Array.Copy(compressedData, corrupted, compressedData.Length);
@@ -849,7 +876,7 @@ public void ArchiveTesting()
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsFalse(testFile.TestArchive(true), "Error in archive not detected");
+ Assert.That(testFile, Does.Not.PassTestArchive(), "Error in archive not detected");
}
}
@@ -863,7 +890,7 @@ private void TestDirectoryEntry(MemoryStream s)
var ms2 = new MemoryStream(s.ToArray());
using (ZipFile zf = new ZipFile(ms2))
{
- Assert.IsTrue(zf.TestArchive(true));
+ Assert.That(zf, Does.PassTestArchive());
}
}
@@ -875,28 +902,28 @@ public void TestDirectoryEntry()
TestDirectoryEntry(new MemoryStreamWithoutSeek());
}
- private void TestEncryptedDirectoryEntry(MemoryStream s)
+ private void TestEncryptedDirectoryEntry(MemoryStream s, int aesKeySize)
{
var outStream = new ZipOutputStream(s);
outStream.Password = "Tonto hand me a beer";
outStream.IsStreamOwner = false;
- outStream.PutNextEntry(new ZipEntry("YeUnreadableDirectory/"));
+ outStream.PutNextEntry(new ZipEntry("YeUnreadableDirectory/") { AESKeySize = aesKeySize } );
outStream.Close();
var ms2 = new MemoryStream(s.ToArray());
using (ZipFile zf = new ZipFile(ms2))
{
- Assert.IsTrue(zf.TestArchive(true));
+ Assert.That(zf, Does.PassTestArchive());
}
}
[Test]
[Category("Zip")]
- public void TestEncryptedDirectoryEntry()
+ public void TestEncryptedDirectoryEntry([Values(0, 128, 256)]int aesKeySize)
{
- TestEncryptedDirectoryEntry(new MemoryStream());
- TestEncryptedDirectoryEntry(new MemoryStreamWithoutSeek());
+ TestEncryptedDirectoryEntry(new MemoryStream(), aesKeySize);
+ TestEncryptedDirectoryEntry(new MemoryStreamWithoutSeek(), aesKeySize);
}
[Test]
@@ -916,7 +943,7 @@ public void Crypto_AddEncryptedEntryToExistingArchiveSafe()
testFile.Add(new StringMemoryDataSource("No3"), "No3", CompressionMethod.Stored);
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
rawData = ms.ToArray();
}
@@ -924,14 +951,14 @@ public void Crypto_AddEncryptedEntryToExistingArchiveSafe()
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
testFile.BeginUpdate(new MemoryArchiveStorage(FileUpdateMode.Safe));
testFile.Password = "pwd";
testFile.Add(new StringMemoryDataSource("Zapata!"), "encrypttest.xml");
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
int entryIndex = testFile.FindEntry("encrypttest.xml", true);
Assert.IsNotNull(entryIndex >= 0);
@@ -954,12 +981,12 @@ public void Crypto_AddEncryptedEntryToExistingArchiveDirect()
testFile.Add(new StringMemoryDataSource("No3"), "No3", CompressionMethod.Stored);
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
testFile.IsStreamOwner = true;
testFile.BeginUpdate();
@@ -967,7 +994,7 @@ public void Crypto_AddEncryptedEntryToExistingArchiveDirect()
testFile.Add(new StringMemoryDataSource("Zapata!"), "encrypttest.xml");
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
int entryIndex = testFile.FindEntry("encrypttest.xml", true);
Assert.IsNotNull(entryIndex >= 0);
@@ -980,45 +1007,40 @@ public void Crypto_AddEncryptedEntryToExistingArchiveDirect()
[Category("Unicode")]
public void UnicodeNames()
{
- using (var memStream = new MemoryStream())
+ using var memStream = new MemoryStream();
+ using (var f = new ZipFile(memStream))
{
- using (ZipFile f = new ZipFile(memStream))
- {
- f.IsStreamOwner = false;
-
- f.BeginUpdate(new MemoryArchiveStorage());
- foreach ((string language, string name, _) in StringTesting.GetTestSamples())
- {
- f.Add(new StringMemoryDataSource(language), name,
- CompressionMethod.Deflated, true);
- }
- f.CommitUpdate();
+ f.IsStreamOwner = false;
- Assert.IsTrue(f.TestArchive(true));
- }
- memStream.Seek(0, SeekOrigin.Begin);
- using (var zf = new ZipFile(memStream))
+ f.BeginUpdate(new MemoryArchiveStorage());
+ foreach (var (language, name, _) in StringTesting.TestSamples)
{
- foreach (string name in StringTesting.Filenames)
- {
- //int index = zf.FindEntry(name, true);
- var content = "";
- var index = zf.FindEntry(name, true);
- var entry = zf[index];
+ f.Add(new StringMemoryDataSource(language), name,
+ CompressionMethod.Deflated, useUnicodeText: true);
+ }
+ f.CommitUpdate();
- using (var entryStream = zf.GetInputStream(entry))
- using (var sr = new StreamReader(entryStream))
- {
- content = sr.ReadToEnd();
- }
+ Assert.That(f, Does.PassTestArchive());
+ }
+ memStream.Seek(0, SeekOrigin.Begin);
+ using (var zf = new ZipFile(memStream))
+ {
+ foreach (var name in StringTesting.Filenames)
+ {
+ string content;
+ var index = zf.FindEntry(name, ignoreCase: true);
+ var entry = zf[index];
- //var content =
+ using (var entryStream = zf.GetInputStream(entry))
+ using (var sr = new StreamReader(entryStream))
+ {
+ content = sr.ReadToEnd();
+ }
- Console.WriteLine($"Entry #{index}: {name}, Content: {content}");
+ TestContext.WriteLine($"Entry #{index}: {name}, Content: {content}");
- Assert.IsTrue(index >= 0);
- Assert.AreEqual(name, entry.Name);
- }
+ Assert.IsTrue(index >= 0);
+ Assert.AreEqual(name, entry.Name);
}
}
}
@@ -1038,12 +1060,12 @@ public void UpdateCommentOnlyInMemory()
testFile.Add(new StringMemoryDataSource("No3"), "No3", CompressionMethod.Stored);
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("", testFile.ZipFileComment);
testFile.IsStreamOwner = false;
@@ -1051,12 +1073,12 @@ public void UpdateCommentOnlyInMemory()
testFile.SetComment("Here is my comment");
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(ms))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("Here is my comment", testFile.ZipFileComment);
}
}
@@ -1083,24 +1105,24 @@ public void UpdateCommentOnlyOnDisk()
testFile.Add(new StringMemoryDataSource("No3"), "No3", CompressionMethod.Stored);
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(tempFile))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("", testFile.ZipFileComment);
testFile.BeginUpdate(new DiskArchiveStorage(testFile, FileUpdateMode.Direct));
testFile.SetComment("Here is my comment");
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(tempFile))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("Here is my comment", testFile.ZipFileComment);
}
File.Delete(tempFile);
@@ -1114,24 +1136,24 @@ public void UpdateCommentOnlyOnDisk()
testFile.Add(new StringMemoryDataSource("No3"), "No3", CompressionMethod.Stored);
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(tempFile))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("", testFile.ZipFileComment);
testFile.BeginUpdate();
testFile.SetComment("Here is my comment");
testFile.CommitUpdate();
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
}
using (ZipFile testFile = new ZipFile(tempFile))
{
- Assert.IsTrue(testFile.TestArchive(true));
+ Assert.That(testFile, Does.PassTestArchive());
Assert.AreEqual("Here is my comment", testFile.ZipFileComment);
}
File.Delete(tempFile);
@@ -1164,7 +1186,7 @@ public void NameFactory()
CompressionMethod.Deflated, true);
}
f.CommitUpdate();
- Assert.IsTrue(f.TestArchive(true));
+ Assert.That(f, Does.PassTestArchive());
foreach (string name in names)
{
@@ -1214,7 +1236,7 @@ public void NestedArchive()
using (ZipFile nested = new ZipFile(zipFile.GetInputStream(0)))
{
- Assert.IsTrue(nested.TestArchive(true));
+ Assert.That(nested, Does.PassTestArchive());
Assert.AreEqual(1, nested.Count);
Stream nestedStream = nested.GetInputStream(0);
@@ -1434,57 +1456,28 @@ public void FileStreamNotClosedWhenNotOwner()
}
///
- /// Check that input stream is closed when construction fails and leaveOpen is false
+ /// Check that input stream is only closed when construction fails and leaveOpen is false
///
[Test]
[Category("Zip")]
- public void StreamClosedOnError()
+ public void StreamClosedOnError([Values(true, false)] bool leaveOpen)
{
var ms = new TrackedMemoryStream(new byte[32]);
Assert.IsFalse(ms.IsClosed, "Underlying stream should NOT be closed initially");
- bool blewUp = false;
- try
+ Assert.Throws(() =>
{
- using (var zipFile = new ZipFile(ms, false))
- {
- Assert.Fail("Exception not thrown");
- }
- }
- catch
- {
- blewUp = true;
- }
+ using var zf = new ZipFile(ms, leaveOpen);
+ }, "Should have failed to load the file");
- Assert.IsTrue(blewUp, "Should have failed to load the file");
- Assert.IsTrue(ms.IsClosed, "Underlying stream should be closed");
- }
-
- ///
- /// Check that input stream is not closed when construction fails and leaveOpen is true
- ///
- [Test]
- [Category("Zip")]
- public void StreamNotClosedOnError()
- {
- var ms = new TrackedMemoryStream(new byte[32]);
-
- Assert.IsFalse(ms.IsClosed, "Underlying stream should NOT be closed initially");
- bool blewUp = false;
- try
+ if (leaveOpen)
{
- using (var zipFile = new ZipFile(ms, true))
- {
- Assert.Fail("Exception not thrown");
- }
+ Assert.IsFalse(ms.IsClosed, "Underlying stream should NOT be closed");
}
- catch
+ else
{
- blewUp = true;
+ Assert.IsTrue(ms.IsClosed, "Underlying stream should be closed");
}
-
- Assert.IsTrue(blewUp, "Should have failed to load the file");
- Assert.IsFalse(ms.IsClosed, "Underlying stream should NOT be closed");
}
[Test]
@@ -1557,16 +1550,269 @@ public void HostSystemPersistedFromZipFile()
public void AddingAnAESEncryptedEntryShouldThrow()
{
var memStream = new MemoryStream();
- using (ZipFile zof = new ZipFile(memStream))
+ using var zof = new ZipFile(memStream);
+ var entry = new ZipEntry("test")
{
- var entry = new ZipEntry("test")
+ AESKeySize = 256,
+ };
+
+ zof.BeginUpdate();
+ var exception = Assert.Throws(() => zof.Add(new StringMemoryDataSource("foo"), entry));
+ Assert.That(exception?.Message, Is.EqualTo("Creation of AES encrypted entries is not supported"));
+ }
+
+ ///
+ /// Test that we can add a file entry and set the name to sometihng other than the name of the file.
+ ///
+ [Test]
+ [Category("Zip")]
+ [Category("CreatesTempFile")]
+ public void AddFileWithAlternateName()
+ {
+ // Create a unique name that will be different from the file name
+ var fileName = Utils.GetDummyFileName();
+
+ using var sourceFile = Utils.GetDummyFile(size: 16);
+ using var outputFile = Utils.GetTempFile();
+ var inputContent = File.ReadAllText(sourceFile);
+ using (var zf = ZipFile.Create(outputFile))
+ {
+ zf.BeginUpdate();
+
+ // Add a file with the unique display name
+ zf.Add(sourceFile, fileName);
+
+ zf.CommitUpdate();
+ zf.Close();
+ }
+
+ using (var zipFile = new ZipFile(outputFile))
+ {
+ Assert.That(zipFile.Count, Is.EqualTo(1));
+
+ var fileEntry = zipFile.GetEntry(fileName);
+ Assert.That(fileEntry, Is.Not.Null);
+
+ using (var sr = new StreamReader(zipFile.GetInputStream(fileEntry)))
{
- AESKeySize = 256
- };
+ var outputContent = sr.ReadToEnd();
+ Assert.AreEqual(inputContent, outputContent, "extracted content does not match source content");
+ }
+ }
+ }
+
+ ///
+ /// Test a zip file using BZip2 compression.
+ ///
+ [TestCase(true)]
+ [TestCase(false)]
+ [Category("Zip")]
+ public void ZipWithBZip2Compression(bool encryptEntries)
+ {
+ string password = "pwd";
- zof.BeginUpdate();
- var exception = Assert.Throws(() => zof.Add(new StringMemoryDataSource("foo"), entry));
- Assert.That(exception.Message, Is.EqualTo("Creation of AES encrypted entries is not supported"));
+ using (var memStream = new MemoryStream())
+ {
+ using (ZipFile f = new ZipFile(memStream, leaveOpen: true))
+ {
+ if (encryptEntries)
+ f.Password = password;
+
+ f.BeginUpdate(new MemoryArchiveStorage());
+
+ var m = new StringMemoryDataSource("BZip2Compressed");
+ f.Add(m, "a.dat", CompressionMethod.BZip2);
+
+ var m2 = new StringMemoryDataSource("DeflateCompressed");
+ f.Add(m2, "b.dat", CompressionMethod.Deflated);
+ f.CommitUpdate();
+ Assert.That(f, Does.PassTestArchive());
+ }
+
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ using (ZipFile f = new ZipFile(memStream))
+ {
+ if (encryptEntries)
+ f.Password = password;
+
+ {
+ var entry = f.GetEntry("a.dat");
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2");
+ Assert.That(entry.Version, Is.EqualTo(ZipConstants.VersionBZip2), "Entry version should be 46");
+ Assert.That(entry.IsCrypted, Is.EqualTo(encryptEntries));
+
+ using (var reader = new StreamReader(f.GetInputStream(entry)))
+ {
+ string contents = reader.ReadToEnd();
+ Assert.That(contents, Is.EqualTo("BZip2Compressed"), "extract string must match original string");
+ }
+ }
+
+ {
+ var entry = f.GetEntry("b.dat");
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.Deflated), "Compression method should be Deflated");
+ Assert.That(entry.IsCrypted, Is.EqualTo(encryptEntries));
+
+ using (var reader = new StreamReader(f.GetInputStream(entry)))
+ {
+ string contents = reader.ReadToEnd();
+ Assert.That(contents, Is.EqualTo("DeflateCompressed"), "extract string must match original string");
+ }
+ }
+ }
+
+ // @@TODO@@ verify the archive with 7-zip?
+ }
+ }
+
+ ///
+ /// We should be able to read a bzip2 compressed zip file created by 7-zip.
+ ///
+ [Test]
+ [Category("Zip")]
+ public void ShouldReadBZip2ZipCreatedBy7Zip()
+ {
+ const string bZip2CompressedZipCreatedBy7Zip =
+ "UEsDBC4AAAAMAIa50U4/rHf5qwAAAK8AAAAJAAAASGVsbG8udHh0QlpoOTFBWSZTWTL8pwYAA" +
+ "BWfgEhlUAAiLUgQP+feMCAAiCKaeiaBobU9JiaAMGmoak9GmRNqPUDQ9T1PQsz/t9B6YvEdvF" +
+ "5dhwXzGE1ooO41A6TtATBEFxFUq6trGtUcSJDyWWWj/S2VwY15fy3IqHi3hHUS+K76zdoDzQa" +
+ "VGE/4YkYZe3JAtv1EsIqIsiTnnktIbBo1R4xY3JZEOm2BvwLuSKcKEgZflODAUEsBAj8ALgAA" +
+ "AAwAhrnRTj+sd/mrAAAArwAAAAkAJAAAAAAAAAAgAAAAAAAAAEhlbGxvLnR4dAoAIAAAAAAAA" +
+ "QAYAO97MLZZJdUB73swtlkl1QEK0UTFWCXVAVBLBQYAAAAAAQABAFsAAADSAAAAAAA=";
+
+ const string originalText =
+ "SharpZipLib (#ziplib, formerly NZipLib) is a compression library that supports Zip files using both stored and deflate compression methods, PKZIP 2.0 style and AES encryption.";
+
+ var fileBytes = Convert.FromBase64String(bZip2CompressedZipCreatedBy7Zip);
+
+ using var input = new MemoryStream(fileBytes, writable: false);
+ using var zf = new ZipFile(input);
+ var entry = zf.GetEntry("Hello.txt");
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2");
+ Assert.That(entry.Version, Is.EqualTo(ZipConstants.VersionBZip2), "Entry version should be 46");
+
+ using var reader = new StreamReader(zf.GetInputStream(entry));
+ var contents = reader.ReadToEnd();
+ Assert.That(contents, Is.EqualTo(originalText), "extract string must match original string");
+ }
+
+ ///
+ /// We should be able to read a bzip2 compressed / AES encrypted zip file created by 7-zip.
+ ///
+ [Test]
+ [Category("Zip")]
+ public void ShouldReadAESBZip2ZipCreatedBy7Zip()
+ {
+ const string bZip2CompressedZipCreatedBy7Zip =
+ "UEsDBDMAAQBjAIa50U4AAAAAxwAAAK8AAAAJAAsASGVsbG8udHh0AZkHAAIAQUUDDAAYg6jqf" +
+ "kvZClVMOtgmqKT0/8I9fMPgo96myxw9hLQUhKj1Qczi3fT7QIhAnAKU+u03nA8rCKGWmDI5Qz" +
+ "qPREy95boQVDPwmwEsWksv3GAWzMfzZUhmB/TgIJlA34a4yP0f2ucy3/QCQYo8QcHjBtjWX5b" +
+ "dZn0+fwY9Ci7q8JSI8zNSbgQ0Ert/lIJ9MxQ4lzBxMl4LySkd104cDPh/FslTAcPtHoy8Mf1c" +
+ "vnI1uICMgjWVeTqYrvSvt2uuHnqr4AiehArFiXTnUEsBAj8AMwABAGMAhrnRTgAAAADHAAAAr" +
+ "wAAAAkALwAAAAAAAAAgAAAAAAAAAEhlbGxvLnR4dAoAIAAAAAAAAQAYAO97MLZZJdUBYdnjul" +
+ "kl1QEK0UTFWCXVAQGZBwACAEFFAwwAUEsFBgAAAAABAAEAZgAAAPkAAAAAAA==";
+
+ const string originalText =
+ "SharpZipLib (#ziplib, formerly NZipLib) is a compression library that supports Zip files using both stored and deflate compression methods, PKZIP 2.0 style and AES encryption.";
+
+ var fileBytes = Convert.FromBase64String(bZip2CompressedZipCreatedBy7Zip);
+
+ using var input = new MemoryStream(fileBytes, writable: false);
+ using var zf = new ZipFile(input);
+ zf.Password = "password";
+
+ var entry = zf.GetEntry("Hello.txt");
+ Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2");
+ Assert.That(entry.Version, Is.EqualTo(ZipConstants.VERSION_AES), "Entry version should be 51");
+ Assert.That(entry.IsCrypted, Is.True, "Entry should be encrypted");
+ Assert.That(entry.AESKeySize, Is.EqualTo(256), "AES Keysize should be 256");
+
+ using var reader = new StreamReader(zf.GetInputStream(entry));
+ var contents = reader.ReadToEnd();
+ Assert.That(contents, Is.EqualTo(originalText), "extract string must match original string");
+ }
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when deleting items in a zip
+ ///
+ /// Whether Zip64 should be used in the test archive
+ [TestCase(UseZip64.On)]
+ [TestCase(UseZip64.Off)]
+ [Category("Zip")]
+ public void TestDescriptorUpdateOnDelete(UseZip64 useZip64)
+ {
+ MemoryStream msw = new MemoryStreamWithoutSeek();
+ using (ZipOutputStream outStream = new ZipOutputStream(msw))
+ {
+ outStream.UseZip64 = useZip64;
+ outStream.IsStreamOwner = false;
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
+ outStream.WriteByte(89);
+
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin2"));
+ outStream.WriteByte(91);
+ }
+
+ var zipData = msw.ToArray();
+ Assert.That(zipData, Does.PassTestArchive());
+
+ using (var memoryStream = new MemoryStream(zipData))
+ {
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate();
+ zipFile.Delete("StripedMarlin");
+ zipFile.CommitUpdate();
+ }
+
+ memoryStream.Position = 0;
+
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ Assert.That(zipFile, Does.PassTestArchive());
+ }
+ }
+ }
+
+ ///
+ /// Test for https://github.com/icsharpcode/SharpZipLib/issues/147, when adding items to a zip
+ ///
+ /// Whether Zip64 should be used in the test archive
+ [TestCase(UseZip64.On)]
+ [TestCase(UseZip64.Off)]
+ [Category("Zip")]
+ public void TestDescriptorUpdateOnAdd(UseZip64 useZip64)
+ {
+ MemoryStream msw = new MemoryStreamWithoutSeek();
+ using (ZipOutputStream outStream = new ZipOutputStream(msw))
+ {
+ outStream.UseZip64 = useZip64;
+ outStream.IsStreamOwner = false;
+ outStream.PutNextEntry(new ZipEntry("StripedMarlin"));
+ outStream.WriteByte(89);
+ }
+
+ var zipData = msw.ToArray();
+ Assert.That(zipData, Does.PassTestArchive());
+
+ using (var memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(zipData, 0, zipData.Length);
+
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ zipFile.BeginUpdate();
+ zipFile.Add(new StringMemoryDataSource("stripey"), "Zebra");
+ zipFile.CommitUpdate();
+ }
+
+ memoryStream.Position = 0;
+
+ using (var zipFile = new ZipFile(memoryStream, leaveOpen: true))
+ {
+ Assert.That(zipFile, Does.PassTestArchive());
+ }
}
}
}
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipNameTransformHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipNameTransformHandling.cs
index 634b9a0ab..5ec8a07e8 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipNameTransformHandling.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipNameTransformHandling.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using System;
using System.IO;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -16,8 +17,6 @@ public void Basic()
var t = new ZipNameTransform();
TestFile(t, "abcdef", "abcdef");
- TestFile(t, @"\\uncpath\d1\file1", "file1");
- TestFile(t, @"C:\absolute\file2", "absolute/file2");
// This is ignored but could be converted to 'file3'
TestFile(t, @"./file3", "./file3");
@@ -28,50 +27,73 @@ public void Basic()
// Trick filenames.
TestFile(t, @".....file3", ".....file3");
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Platform("Win")]
+ public void Basic_Windows()
+ {
+ var t = new ZipNameTransform();
+ TestFile(t, @"\\uncpath\d1\file1", "file1");
+ TestFile(t, @"C:\absolute\file2", "absolute/file2");
+
TestFile(t, @"c::file", "_file");
}
+
+ [Test]
+ [Category("Zip")]
+ [Platform(Exclude="Win")]
+ public void Basic_Posix()
+ {
+ var t = new ZipNameTransform();
+ TestFile(t, @"backslash_path\file1", "backslash_path/file1");
+ TestFile(t, "/absolute/file2", "absolute/file2");
+
+ TestFile(t, @"////////:file", "_file");
+ }
[Test]
public void TooLong()
{
var zt = new ZipNameTransform();
- var veryLong = new string('x', 65536);
- try
- {
- zt.TransformDirectory(veryLong);
- Assert.Fail("Expected an exception");
- }
- catch (PathTooLongException)
- {
- }
+ var tooLong = new string('x', 65536);
+ Assert.Throws(() => zt.TransformDirectory(tooLong));
}
[Test]
public void LengthBoundaryOk()
{
var zt = new ZipNameTransform();
- string veryLong = "c:\\" + new string('x', 65535);
- try
- {
- zt.TransformDirectory(veryLong);
- }
- catch
- {
- Assert.Fail("Expected no exception");
- }
+ var tooLongWithRoot = Utils.SystemRoot + new string('x', 65535);
+ Assert.DoesNotThrow(() => zt.TransformDirectory(tooLongWithRoot));
}
[Test]
[Category("Zip")]
- public void NameTransforms()
+ [Platform("Win")]
+ public void NameTransforms_Windows()
{
INameTransform t = new ZipNameTransform(@"C:\Slippery");
Assert.AreEqual("Pongo/Directory/", t.TransformDirectory(@"C:\Slippery\Pongo\Directory"), "Value should be trimmed and converted");
Assert.AreEqual("PoNgo/Directory/", t.TransformDirectory(@"c:\slipperY\PoNgo\Directory"), "Trimming should be case insensitive");
- Assert.AreEqual("slippery/Pongo/Directory/", t.TransformDirectory(@"d:\slippery\Pongo\Directory"), "Trimming should be case insensitive");
+ Assert.AreEqual("slippery/Pongo/Directory/", t.TransformDirectory(@"d:\slippery\Pongo\Directory"), "Trimming should account for root");
Assert.AreEqual("Pongo/File", t.TransformFile(@"C:\Slippery\Pongo\File"), "Value should be trimmed and converted");
}
+
+ [Test]
+ [Category("Zip")]
+ [Platform(Exclude="Win")]
+ public void NameTransforms_Posix()
+ {
+ INameTransform t = new ZipNameTransform(@"/Slippery");
+ Assert.AreEqual("Pongo/Directory/", t.TransformDirectory(@"/Slippery\Pongo\Directory"), "Value should be trimmed and converted");
+ Assert.AreEqual("PoNgo/Directory/", t.TransformDirectory(@"/slipperY\PoNgo\Directory"), "Trimming should be case insensitive");
+ Assert.AreEqual("slippery/Pongo/Directory/", t.TransformDirectory(@"/slippery/slippery/Pongo/Directory"), "Trimming should account for root");
+
+ Assert.AreEqual("Pongo/File", t.TransformFile(@"/Slippery/Pongo/File"), "Value should be trimmed and converted");
+ }
///
/// Test ZipEntry static file name cleaning methods
@@ -80,10 +102,16 @@ public void NameTransforms()
[Category("Zip")]
public void FilenameCleaning()
{
- Assert.AreEqual(0, string.Compare(ZipEntry.CleanName("hello"), "hello", StringComparison.Ordinal));
- Assert.AreEqual(0, string.Compare(ZipEntry.CleanName(@"z:\eccles"), "eccles", StringComparison.Ordinal));
- Assert.AreEqual(0, string.Compare(ZipEntry.CleanName(@"\\server\share\eccles"), "eccles", StringComparison.Ordinal));
- Assert.AreEqual(0, string.Compare(ZipEntry.CleanName(@"\\server\share\dir\eccles"), "dir/eccles", StringComparison.Ordinal));
+ Assert.AreEqual("hello", ZipEntry.CleanName("hello"));
+ if(Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ Assert.AreEqual("eccles", ZipEntry.CleanName(@"z:\eccles"));
+ Assert.AreEqual("eccles", ZipEntry.CleanName(@"\\server\share\eccles"));
+ Assert.AreEqual("dir/eccles", ZipEntry.CleanName(@"\\server\share\dir\eccles"));
+ }
+ else {
+ Assert.AreEqual("eccles", ZipEntry.CleanName(@"/eccles"));
+ }
}
[Test]
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs
new file mode 100644
index 000000000..aff027bf1
--- /dev/null
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStreamAsyncTests.cs
@@ -0,0 +1,125 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using ICSharpCode.SharpZipLib.Zip;
+using ICSharpCode.SharpZipLib.Tests.TestSupport;
+using NUnit.Framework;
+
+namespace ICSharpCode.SharpZipLib.Tests.Zip
+{
+ [TestFixture]
+ public class ZipStreamAsyncTests
+ {
+ [Test]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task WriteZipStreamUsingAsync()
+ {
+#if NETCOREAPP3_1_OR_GREATER
+ await using var ms = new MemoryStream();
+
+ await using (var outStream = new ZipOutputStream(ms){IsStreamOwner = false})
+ {
+ await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+ }
+
+ ZipTesting.AssertValidZip(ms);
+#else
+ await Task.CompletedTask;
+ Assert.Ignore("Async Using is not supported");
+#endif
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task WriteZipStreamAsync ()
+ {
+ using var ms = new MemoryStream();
+
+ using(var outStream = new ZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.FinishAsync(CancellationToken.None);
+ }
+
+ ZipTesting.AssertValidZip(ms);
+ }
+
+
+ [Test]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task WriteZipStreamWithAesAsync()
+ {
+ using var ms = new MemoryStream();
+ var password = "f4ls3p0s1t1v3";
+
+ using (var outStream = new ZipOutputStream(ms){IsStreamOwner = false, Password = password})
+ {
+ await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"){AESKeySize = 256});
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"){AESKeySize = 256});
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.FinishAsync(CancellationToken.None);
+ }
+
+ ZipTesting.AssertValidZip(ms, password);
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task WriteZipStreamWithZipCryptoAsync()
+ {
+ using var ms = new MemoryStream();
+ var password = "f4ls3p0s1t1v3";
+
+ using (var outStream = new ZipOutputStream(ms){IsStreamOwner = false, Password = password})
+ {
+ await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"){AESKeySize = 0});
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"){AESKeySize = 0});
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.FinishAsync(CancellationToken.None);
+ }
+
+ ZipTesting.AssertValidZip(ms, password, false);
+ }
+
+ [Test]
+ [Category("Zip")]
+ [Category("Async")]
+ public async Task WriteReadOnlyZipStreamAsync ()
+ {
+ using var ms = new MemoryStreamWithoutSeek();
+
+ using(var outStream = new ZipOutputStream(ms) { IsStreamOwner = false })
+ {
+ await outStream.PutNextEntryAsync(new ZipEntry("FirstFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.PutNextEntryAsync(new ZipEntry("SecondFile"));
+ await Utils.WriteDummyDataAsync(outStream, 12);
+
+ await outStream.FinishAsync(CancellationToken.None);
+ }
+
+ ZipTesting.AssertValidZip(new MemoryStream(ms.ToArray()));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs
index eff2e007b..4a0c9954f 100644
--- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs
+++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs
@@ -7,6 +7,7 @@
using System.IO;
using System.Security;
using System.Text;
+using System.Threading.Tasks;
namespace ICSharpCode.SharpZipLib.Tests.Zip
{
@@ -288,7 +289,7 @@ protected static byte ScatterValue(byte rhs)
return (byte)((rhs * 253 + 7) & 0xff);
}
- private static void AddKnownDataToEntry(ZipOutputStream zipStream, int size)
+ private static void AddKnownDataToEntry(Stream zipStream, int size)
{
if (size > 0)
{
@@ -387,6 +388,27 @@ protected void MakeZipFile(Stream storage, bool isOwner,
}
}
+ protected void MakeZipFile(Stream storage, CompressionMethod compressionMethod, bool isOwner,
+ string entryNamePrefix, int entries, int size, string comment)
+ {
+ using (ZipFile f = new ZipFile(storage, leaveOpen: !isOwner))
+ {
+ f.BeginUpdate();
+ f.SetComment(comment);
+
+ for (int i = 0; i < entries; ++i)
+ {
+ var data = new MemoryStream();
+ AddKnownDataToEntry(data, size);
+
+ var m = new MemoryDataSource(data.ToArray());
+ f.Add(m, entryNamePrefix + (i + 1), compressionMethod);
+ }
+
+ f.CommitUpdate();
+ }
+ }
+
#endregion MakeZipFile Entries
protected static void CheckKnownEntry(Stream inStream, int expectedCount)
@@ -408,6 +430,25 @@ protected static void CheckKnownEntry(Stream inStream, int expectedCount)
Assert.AreEqual(expectedCount, total, "Wrong number of bytes read from entry");
}
+ protected static async Task CheckKnownEntryAsync(Stream inStream, int expectedCount)
+ {
+ byte[] buffer = new byte[1024];
+
+ int bytesRead;
+ int total = 0;
+ byte nextValue = 0;
+ while ((bytesRead = await inStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ total += bytesRead;
+ for (int i = 0; i < bytesRead; ++i)
+ {
+ Assert.AreEqual(nextValue, buffer[i], "Wrong value read from entry");
+ nextValue = ScatterValue(nextValue);
+ }
+ }
+ Assert.AreEqual(expectedCount, total, "Wrong number of bytes read from entry");
+ }
+
protected byte ReadByteChecked(Stream stream)
{
int rawValue = stream.ReadByte();
diff --git a/tools/appveyor-test.ps1 b/tools/appveyor-test.ps1
index b46519cb6..0005b5c3d 100644
--- a/tools/appveyor-test.ps1
+++ b/tools/appveyor-test.ps1
@@ -5,7 +5,7 @@ $resxml = ".\docs\nunit3-test-results-debug.xml";
#$tester = "nunit3-console .\test\ICSharpCode.SharpZipLib.Tests\bin\$($env:CONFIGURATION)\netcoreapp2.0\ICSharpCode.SharpZipLib.Tests.dll"
# Bootstrapper:
-$tester = "dotnet run -f netcoreapp2 -p $proj -c $env:CONFIGURATION";
+$tester = "dotnet run -f netcoreapp3.1 -p $proj -c $env:CONFIGURATION";
iex "$tester --explore=tests.xml";
[xml]$xml = Get-Content("tests.xml");
diff --git a/tools/release-notes/README.md b/tools/release-notes/README.md
new file mode 100644
index 000000000..90eb018a9
--- /dev/null
+++ b/tools/release-notes/README.md
@@ -0,0 +1,10 @@
+## Requirements
+```
+npm install -g git-release-notes
+```
+
+## Usage
+```
+git release-notes -f release-notes.json PREVIOUS..CURRENT release-notes-md.ejs
+```
+Where PREVIOUS is the previous release tag, and CURRENT is the current release tag
diff --git a/tools/release-notes/release-notes-md.ejs b/tools/release-notes/release-notes-md.ejs
new file mode 100644
index 000000000..afde8c298
--- /dev/null
+++ b/tools/release-notes/release-notes-md.ejs
@@ -0,0 +1,31 @@
+<%
+const typeGroups = {
+ feats: { title: 'Features:', types: ['feat'] },
+ fixes: { title: 'Fixes:', types: ['fix'] },
+ etc: {
+ title: 'Other changes (not related to library code):',
+ types: ['docs','style','refactor','perf','test','build','ci','chore']
+ },
+ unknown: { title: 'Unknown:', types: ['?'] },
+}
+
+const commitTypes = {
+ feat: '✨', fix: '🐛', docs: '📚', style: '💎',
+ refactor: '🔨', perf: '🚀', test: '🚨', build: '📦',
+ ci: '⚙️', chore: '🔧', ['?']: '❓',
+}
+
+for(const group of Object.values(typeGroups)){
+ const groupCommits = commits.filter(c => group.types.includes(c.type));
+ if (groupCommits.length < 1) continue;
+%>
+## <%=group.title%>
+<% for (const {issue, title, authorName, authorUser, scope, type} of groupCommits) { %>
+* <%=commitTypes[type]%>
+<%=issue ? ` [[#${issue}](https://github.com/icsharpcode/SharpZipLib/pull/${issue})]\n` : ''-%>
+<%=scope ? ` \`${scope}\`\n` : ''-%>
+ __<%=title-%>__
+ by <%=authorUser ? `[_${authorName}_](https://github.com/${authorUser})` : `_${authorName}_`%>
+<% } %>
+
+<% } %>
diff --git a/tools/release-notes/release-notes.js b/tools/release-notes/release-notes.js
new file mode 100644
index 000000000..ce18ccac5
--- /dev/null
+++ b/tools/release-notes/release-notes.js
@@ -0,0 +1,76 @@
+const https = require('https')
+
+const authorUsers = {}
+
+/**
+ * @param {string} email
+ * @param {string} prId
+ * @returns {Promise} User login if found */
+const getAuthorUser = async (email, prId) => {
+ const lookupUser = authorUsers[email];
+ if (lookupUser) return lookupUser;
+
+ const match = /[0-9]+\+([^@]+)@users\.noreply\.github\.com/.exec(email);
+ if (match) {
+ return match[1];
+ }
+
+ const pr = await new Promise((resolve, reject) => {
+ console.warn(`Looking up GitHub user for PR #${prId} (${email})...`)
+ https.get(`https://api.github.com/repos/icsharpcode/sharpziplib/pulls/${prId}`, {
+ headers: {Accept: 'application/vnd.github.v3+json', 'User-Agent': 'release-notes-script/0.3.1'}
+ }, (res) => {
+ res.setEncoding('utf8');
+ let chunks = '';
+ res.on('data', (chunk) => chunks += chunk);
+ res.on('end', () => resolve(JSON.parse(chunks)));
+ res.on('error', reject);
+ }).on('error', reject);
+ }).catch(e => {
+ console.error(`Could not get GitHub user (${email}): ${e}}`)
+ return null;
+ });
+
+ if (!pr) {
+ console.error(`Could not get GitHub user (${email})}`)
+ return null;
+ } else {
+ const user = pr.user.login;
+ console.warn(`Resolved email ${email} to user ${user}`)
+ authorUsers[email] = user;
+ return user;
+ }
+}
+
+/**
+ * @typedef {{issue?: string, sha1: string, authorEmail: string, title: string, type: string}} Commit
+ * @param {{commits: Commit[], range: string, dateFnsFormat: ()=>any, debug: (...p[]) => void}} data
+ * @param {(data: {commits: Commit[], extra: {[key: string]: any}}) => void} callback
+ * */
+module.exports = (data, callback) => {
+ // Migrates commits in the old format to conventional commit style, omitting any commits in neither format
+ const normalizedCommits = data.commits.flatMap(c => {
+ if (c.type) return [c]
+ const match = /^(?:Merge )?(?:PR ?)?#(\d+):? (.*)/.exec(c.title)
+ if (match != null) {
+ const [, issue, title] = match
+ return [{...c, title, issue, type: '?'}]
+ } else {
+ console.warn(`Skipping commit [${c.sha1.substr(0, 7)}] "${c.title}"!`);
+ return [];
+ }
+ });
+
+ const commitAuthoredBy = email => commit => commit.authorEmail === email && commit.issue ? [commit.issue] : []
+ const authorEmails = new Set(normalizedCommits.map(c => c.authorEmail));
+ Promise.all(
+ Array
+ .from(authorEmails.values(), e => [e, normalizedCommits.flatMap(commitAuthoredBy(e))])
+ .map(async ([email, prs]) => [email, await getAuthorUser(email, ...prs)])
+ )
+ .then(Object.fromEntries)
+ .then(authorUsers => callback({
+ commits: normalizedCommits.map(c => ({...c, authorUser: authorUsers[c.authorEmail]})),
+ extra: {}
+ }))
+};
diff --git a/tools/release-notes/release-notes.json b/tools/release-notes/release-notes.json
new file mode 100644
index 000000000..7ad7733d1
--- /dev/null
+++ b/tools/release-notes/release-notes.json
@@ -0,0 +1,5 @@
+{
+ "title" : "^([a-z]+)(?:\\(([\\w\\$\\.]*)\\))?\\: (.*?)(?: \\(#(\\d+)\\))?$",
+ "meaning": ["type", "scope", "title", "issue"],
+ "script": "release-notes.js"
+}