├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── Directory.Build.props
├── LICENSE.txt
├── NOTICE.txt
├── PRIVACY.md
├── Pipelines
├── recursive-extractor-pr.yml
└── recursive-extractor-release.yml
├── README.md
├── RecursiveExtractor.Cli.Tests
├── CliTests
│ └── CliTests.cs
├── RecursiveExtractor.Cli.Tests.csproj
└── Usings.cs
├── RecursiveExtractor.Cli
├── ExtractCommandOptions.cs
├── Properties
│ └── launchSettings.json
├── RecursiveExtractor.Cli.csproj
├── RecursiveExtractor.Cli.csproj.user
└── RecursiveExtractorClient.cs
├── RecursiveExtractor.Tests
├── ExtractorTests
│ ├── BaseExtractorTestClass.cs
│ ├── DisposeBehaviorTests.cs
│ ├── EncryptedArchiveTests.cs
│ ├── ExpectedNumFilesTests.cs
│ ├── FilterTests.cs
│ ├── MiniMagicTests.cs
│ ├── MiscTests.cs
│ ├── TestQuinesAndSlip.cs
│ └── TimeOutTests.cs
├── RecursiveExtractor.Tests.csproj
├── SanitizePathTests.cs
├── TestData
│ ├── Bombs
│ │ ├── 10GB.7z.bz2
│ │ ├── 10GB.bz2
│ │ ├── 10GB.gz.bz2
│ │ ├── 10GB.rar.bz2
│ │ ├── 10GB.xz.bz2
│ │ ├── 10GB.zip.bz2
│ │ ├── zblg.zip
│ │ ├── zbsm.zip
│ │ ├── zip-slip-win.tar
│ │ ├── zip-slip-win.zip
│ │ ├── zip-slip.tar
│ │ └── zip-slip.zip
│ ├── TestData
│ │ ├── Foo
│ │ │ ├── Bar
│ │ │ │ └── Dolor.txt
│ │ │ └── Ipsum.txt
│ │ └── Lorem.txt
│ └── TestDataArchives
│ │ ├── .gitattributes
│ │ ├── 100trees.7z
│ │ ├── Empty.vmdk
│ │ ├── EmptyFile.txt
│ │ ├── EncryptedWithPlainNames.7z
│ │ ├── EncryptedWithPlainNames.rar4
│ │ ├── HfsSampleUDCO.dmg
│ │ ├── Lorem.txt
│ │ ├── TestData.7z
│ │ ├── TestData.a
│ │ ├── TestData.bsd.ar
│ │ ├── TestData.cdr
│ │ ├── TestData.iso
│ │ ├── TestData.rar
│ │ ├── TestData.rar4
│ │ ├── TestData.tar
│ │ ├── TestData.tar.bz2
│ │ ├── TestData.tar.gz
│ │ ├── TestData.tar.xz
│ │ ├── TestData.vhdx
│ │ ├── TestData.wim
│ │ ├── TestData.zip
│ │ ├── TestDataArchivesNested.zip
│ │ ├── TestDataCorrupt.tar
│ │ ├── TestDataCorrupt.tar.zip
│ │ ├── TestDataCorruptWim.zip
│ │ ├── TestDataEncrypted.7z
│ │ ├── TestDataEncrypted.rar
│ │ ├── TestDataEncrypted.rar4
│ │ ├── TestDataEncryptedAES.zip
│ │ ├── TestDataEncryptedZipCrypto.zip
│ │ ├── TestDataForFilters.7z
│ │ ├── UdfTest.iso
│ │ ├── UdfTestWithMultiSystem.iso
│ │ └── sysvbanner_1.0-17fakesync1_amd64.deb
└── TestPathHelpers.cs
├── RecursiveExtractor.sln
├── RecursiveExtractor
├── ArFile.cs
├── DebArchiveFile.cs
├── ExtractionStatusCode.cs
├── Extractor.cs
├── ExtractorOptions.cs
├── Extractors
│ ├── AsyncExtractorInterface.cs
│ ├── BZip2Extractor.cs
│ ├── DebExtractor.cs
│ ├── DiscCommon.cs
│ ├── ExtractorInterface.cs
│ ├── GnuArExtractor.cs
│ ├── GzipExtractor.cs
│ ├── IsoExtractor.cs
│ ├── RarExtractor.cs
│ ├── SevenZipExtractor.cs
│ ├── TarExtractor.cs
│ ├── UdfExtractor.cs
│ ├── VhdExtractor.cs
│ ├── VhdxExtractor.cs
│ ├── VmdkExtractor.cs
│ ├── WimExtractor.cs
│ ├── XzExtractor.cs
│ └── ZipExtractor.cs
├── FileEntry.cs
├── FileEntryInfo.cs
├── MiniMagic.cs
├── README.md
├── Range.cs
├── RecursiveExtractor.csproj
├── ResourceGovernor.cs
├── StreamFactory.cs
└── TempPath.cs
├── SECURITY.md
├── icon-128.png
└── version.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Don't mess with .ar files
5 | *.ar binary
6 | *.deb binary
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | paths:
6 | - RecursiveExtractor.Blazor
7 | - RecursiveExtractor.Cli
8 | - RecursiveExtractor.Tests
9 | - RecursiveExtractor
10 | pull_request:
11 | schedule:
12 | - cron: '0 5 * * 2'
13 |
14 | jobs:
15 | CodeQL-Build:
16 |
17 | strategy:
18 | fail-fast: false
19 |
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - name: Checkout repository
24 | uses: actions/checkout@v2
25 | with:
26 | fetch-depth: 0
27 |
28 | - name: Setup .NET Core SDK
29 | uses: actions/setup-dotnet@v1.7.2
30 | with:
31 | dotnet-version: 8.0.x
32 |
33 | - name: Initialize CodeQL
34 | uses: github/codeql-action/init@v2
35 | with:
36 | languages: csharp
37 |
38 | - name: Build RecursiveExtractor
39 | run: |
40 | dotnet restore RecursiveExtractor.sln
41 | dotnet build -c Release
42 |
43 | - name: Perform CodeQL Analysis
44 | uses: github/codeql-action/analyze@v2
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | .idea/
4 | .vs
5 | RecursiveExtractor.sln.DotSettings.user
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/RecursiveExtractor.Cli/bin/Debug/netcoreapp3.1/RecursiveExtractor.Cli.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/RecursiveExtractor.Cli",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach",
24 | "processId": "${command:pickProcess}"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet-test-explorer.testProjectPath": "**/*Tests.csproj"
3 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "--framework",
11 | "netcoreapp3.1",
12 | "${workspaceFolder}/RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj",
13 | "/property:GenerateFullPaths=true",
14 | "/consoleloggerparameters:NoSummary"
15 | ],
16 | "problemMatcher": "$msCompile"
17 | },
18 | {
19 | "label": "publish",
20 | "command": "dotnet",
21 | "type": "process",
22 | "args": [
23 | "publish",
24 | "${workspaceFolder}/RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj",
25 | "/property:GenerateFullPaths=true",
26 | "/consoleloggerparameters:NoSummary"
27 | ],
28 | "problemMatcher": "$msCompile"
29 | },
30 | {
31 | "label": "watch",
32 | "command": "dotnet",
33 | "type": "process",
34 | "args": [
35 | "watch",
36 | "run",
37 | "${workspaceFolder}/RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj",
38 | "/property:GenerateFullPaths=true",
39 | "/consoleloggerparameters:NoSummary"
40 | ],
41 | "problemMatcher": "$msCompile"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 3.7.115
6 | all
7 |
8 |
9 |
10 |
11 |
12 | true
13 | true
14 | Recommended
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) Microsoft Corporation. All rights reserved.
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy and Telemetry Notice
2 |
3 | ## Data Collection
4 |
5 | The Recursive Extractor software itself does *not* collect information about your
6 | use of the software, and therefore does not send such information to any source,
7 | including to Microsoft. Nevertheless, our privacy statement is located at
8 | https://go.microsoft.com/fwlink/?LinkID=824704.
--------------------------------------------------------------------------------
/Pipelines/recursive-extractor-pr.yml:
--------------------------------------------------------------------------------
1 | # Azure Pipelines
2 | # https://aka.ms/yaml
3 |
4 | name: RecursiveExtractor_PR_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
5 | trigger: none
6 | pr:
7 | branches:
8 | include:
9 | - main
10 |
11 | resources:
12 | repositories:
13 | - repository: templates
14 | type: git
15 | name: SecurityEngineering/OSS-Tools-Pipeline-Templates
16 | ref: refs/tags/v2.0.1
17 | - repository: 1esPipelines
18 | type: git
19 | name: 1ESPipelineTemplates/1ESPipelineTemplates
20 | ref: refs/tags/release
21 |
22 | variables:
23 | BuildConfiguration: 'Release'
24 | DotnetVersion: '9.0.x'
25 |
26 | extends:
27 | template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
28 | parameters:
29 | pool:
30 | name: MSSecurity-1ES-Build-Agents-Pool
31 | image: MSSecurity-1ES-Windows-2022
32 | os: windows
33 | sdl:
34 | armory:
35 | enabled: false
36 | stages:
37 | - stage: Test
38 | dependsOn: []
39 | jobs:
40 | - template: dotnet-test-job.yml@templates
41 | parameters:
42 | jobName: 'lib_dotnet_test_windows'
43 | dotnetVersions: ['8.0.x', '9.0.x']
44 | projectPath: 'RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj'
45 | poolName: MSSecurity-1ES-Build-Agents-Pool
46 | poolImage: MSSecurity-1ES-Windows-2022
47 | poolOs: windows
48 | dotnetTestArgs: '-- --coverage --report-trx'
49 | - template: dotnet-test-job.yml@templates
50 | parameters:
51 | jobName: 'cli_dotnet_test_windows'
52 | dotnetVersions: ['8.0.x', '9.0.x']
53 | projectPath: 'RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj'
54 | poolName: MSSecurity-1ES-Build-Agents-Pool
55 | poolImage: MSSecurity-1ES-Windows-2022
56 | poolOs: windows
57 | dotnetTestArgs: '-- --coverage --report-trx'
58 |
59 | - stage: Build
60 | dependsOn:
61 | - Test
62 | jobs:
63 | - template: nuget-build-job.yml@templates
64 | parameters:
65 | jobName: 'pack_lib'
66 | buildConfiguration: ${{ variables.BuildConfiguration }}
67 | dotnetVersion: ${{ variables.DotnetVersion }}
68 | projectPath: 'RecursiveExtractor/RecursiveExtractor.csproj'
69 | projectName: 'RecursiveExtractor'
70 | customPackFlags: '/p:ContinuousIntegrationBuild=true'
71 | artifactName: 'lib-archive'
72 | preBuild:
73 | - template: nbgv-set-version-steps.yml@templates
74 | - template: nuget-build-job.yml@templates
75 | parameters:
76 | jobName: 'pack_cli'
77 | buildConfiguration: ${{ variables.BuildConfiguration }}
78 | dotnetVersion: ${{ variables.DotnetVersion }}
79 | projectPath: 'RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj'
80 | projectName: 'RecursiveExtractor_CLI'
81 | customPackFlags: '/p:ContinuousIntegrationBuild=true'
82 | artifactName: 'cli-archive'
83 | preBuild:
84 | - template: nbgv-set-version-steps.yml@templates
85 |
--------------------------------------------------------------------------------
/Pipelines/recursive-extractor-release.yml:
--------------------------------------------------------------------------------
1 | name: RecursiveExtractor_Release_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
2 | trigger: none
3 | pr: none
4 |
5 | resources:
6 | repositories:
7 | - repository: templates
8 | type: git
9 | name: Data/OSS-Tools-Pipeline-Templates
10 | ref: refs/tags/v2.0.1
11 | - repository: 1esPipelines
12 | type: git
13 | name: 1ESPipelineTemplates/1ESPipelineTemplates
14 | ref: refs/tags/release
15 |
16 | variables:
17 | BuildConfiguration: 'Release'
18 | DotnetVersion: '9.0.x'
19 |
20 | extends:
21 | template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
22 | parameters:
23 | pool:
24 | name: MSSecurity-1ES-Build-Agents-Pool
25 | image: MSSecurity-1ES-Windows-2022
26 | os: windows
27 | sdl:
28 | armory:
29 | enabled: false
30 | sourceRepositoriesToScan:
31 | exclude:
32 | - repository: 1esPipelines
33 | - repository: templates
34 | stages:
35 | - stage: Test
36 | dependsOn: []
37 | jobs:
38 | - template: dotnet-test-job.yml@templates
39 | parameters:
40 | jobName: 'lib_dotnet_test_windows'
41 | dotnetVersions: ['8.0.x', '9.0.x']
42 | projectPath: 'RecursiveExtractor.Tests/RecursiveExtractor.Tests.csproj'
43 | poolName: MSSecurity-1ES-Build-Agents-Pool
44 | poolImage: MSSecurity-1ES-Windows-2022
45 | poolOs: windows
46 | dotnetTestArgs: '-- --coverage --report-trx'
47 | - template: dotnet-test-job.yml@templates
48 | parameters:
49 | jobName: 'cli_dotnet_test_windows'
50 | dotnetVersions: ['8.0.x', '9.0.x']
51 | projectPath: 'RecursiveExtractor.Cli.Tests/RecursiveExtractor.Cli.Tests.csproj'
52 | poolName: MSSecurity-1ES-Build-Agents-Pool
53 | poolImage: MSSecurity-1ES-Windows-2022
54 | poolOs: windows
55 | dotnetTestArgs: '-- --coverage --report-trx'
56 |
57 | - stage: Build
58 | dependsOn:
59 | - Test
60 | jobs:
61 | - template: nuget-build-job.yml@templates
62 | parameters:
63 | jobName: 'pack_lib'
64 | buildConfiguration: ${{ variables.BuildConfiguration }}
65 | dotnetVersion: ${{ variables.DotnetVersion }}
66 | projectPath: 'RecursiveExtractor/RecursiveExtractor.csproj'
67 | projectName: 'RecursiveExtractor'
68 | customPackFlags: '/p:ContinuousIntegrationBuild=true'
69 | artifactName: 'lib-archive'
70 | preBuild:
71 | - template: nbgv-set-version-steps.yml@templates
72 | - template: nuget-build-job.yml@templates
73 | parameters:
74 | jobName: 'pack_cli'
75 | buildConfiguration: ${{ variables.BuildConfiguration }}
76 | dotnetVersion: ${{ variables.DotnetVersion }}
77 | projectPath: 'RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj'
78 | projectName: 'RecursiveExtractor_CLI'
79 | customPackFlags: '/p:ContinuousIntegrationBuild=true'
80 | artifactName: 'cli-archive'
81 | preBuild:
82 | - template: nbgv-set-version-steps.yml@templates
83 |
84 | - stage: Release
85 | dependsOn:
86 | - Build
87 | condition: succeeded()
88 | jobs:
89 | - job: sign_hash_release
90 | displayName: Code Sign, Generate Hashes, Publish Public Releases
91 | templateContext:
92 | outputs:
93 | - output: pipelineArtifact
94 | path: '$(Build.StagingDirectory)'
95 | artifact: 'Signed_Binaries_$(System.JobId)_$(System.JobAttempt)'
96 | # see https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/features/outputs/nuget-packages
97 | - output: nuget
98 | useDotNetTask: false
99 | packagesToPush: '$(Build.StagingDirectory)/*.nupkg'
100 | packageParentPath: '$(Build.StagingDirectory)'
101 | nuGetFeedType: external
102 | publishPackageMetadata: true
103 | publishFeedCredentials: 'sdl-oss-nuget-publish'
104 | steps:
105 | - template: nbgv-set-version-steps.yml@templates
106 | - task: UseDotNet@2
107 | inputs:
108 | packageType: 'sdk'
109 | version: '6.0.x' # ESRP requires a specific version.
110 | - task: DownloadPipelineArtifact@2
111 | inputs:
112 | displayName: 'Download lib-archive'
113 | buildType: 'current'
114 | artifactName: 'lib-archive'
115 | targetPath: $(Build.BinariesDirectory)\Unsigned_Binaries\
116 | - task: DownloadPipelineArtifact@2
117 | inputs:
118 | displayName: 'Download cli-archive'
119 | buildType: 'current'
120 | artifactName: 'cli-archive'
121 | targetPath: $(Build.BinariesDirectory)\Unsigned_Binaries\
122 | - task: ExtractFiles@1
123 | displayName: Extract Artifacts for Signing
124 | inputs:
125 | archiveFilePatterns: '$(Build.BinariesDirectory)\Unsigned_Binaries\*.zip'
126 | destinationFolder: '$(Build.BinariesDirectory)'
127 | cleanDestinationFolder: false
128 | overwriteExistingFiles: true
129 | - task: AntiMalware@4
130 | displayName: Anti-Malware Scan
131 | inputs:
132 | InputType: 'Basic'
133 | ScanType: 'CustomScan'
134 | FileDirPath: '$(Build.BinariesDirectory)'
135 | EnableServices: true
136 | SupportLogOnError: true
137 | TreatSignatureUpdateFailureAs: 'Warning'
138 | SignatureFreshness: 'UpToDate'
139 | TreatStaleSignatureAs: 'Warning'
140 | - task: EsrpCodeSigning@5
141 | displayName: Code Sign Nuget Packages
142 | inputs:
143 | ConnectedServiceName: 'oss-esrp-signing-recext-v5-connection'
144 | AppRegistrationClientId: 'caf746ee-b288-4155-8cc0-0bedca65f230'
145 | AppRegistrationTenantId: '33e01921-4d64-4f8c-a055-5bdaffd5e33d'
146 | AuthAKVName: 'oss-signing-vault'
147 | AuthCertName: 'oss-recursive-auth-cert'
148 | AuthSignCertName: 'oss-recursive-signing-cert'
149 | FolderPath: '$(Build.BinariesDirectory)'
150 | Pattern: '*.nupkg, *.snupkg'
151 | signConfigType: 'inlineSignParams'
152 | inlineOperation: |
153 | [
154 | {
155 | "KeyCode" : "CP-401405",
156 | "OperationCode" : "NuGetSign",
157 | "Parameters" : {},
158 | "ToolName" : "sign",
159 | "ToolVersion" : "1.0"
160 | },
161 | {
162 | "KeyCode" : "CP-401405",
163 | "OperationCode" : "NuGetVerify",
164 | "Parameters" : {},
165 | "ToolName" : "sign",
166 | "ToolVersion" : "1.0"
167 | }
168 | ]
169 | SessionTimeout: '60'
170 | MaxConcurrency: '50'
171 | MaxRetryAttempts: '5'
172 | - powershell: 'Get-ChildItem -Path ''$(Build.BinariesDirectory)'' -Recurse CodeSign* | foreach { Remove-Item -Path $_.FullName }'
173 | displayName: 'Delete Code Sign Summaries'
174 | - task: PowerShell@2
175 | displayName: Move NuGet Packages
176 | inputs:
177 | targetType: 'inline'
178 | script: |
179 | mv $env:BUILD_BINARIESDIRECTORY/*.nupkg $env:BUILD_STAGINGDIRECTORY/
180 | mv $env:BUILD_BINARIESDIRECTORY/*.snupkg $env:BUILD_STAGINGDIRECTORY/
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli.Tests/CliTests/CliTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. Licensed under the MIT License.
2 |
3 | using Microsoft.CST.RecursiveExtractor;
4 | using Microsoft.CST.RecursiveExtractor.Cli;
5 | using Microsoft.CST.RecursiveExtractor.Tests;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 | using RecursiveExtractor.Tests.ExtractorTests;
8 | using System;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Threading;
12 |
13 | namespace RecursiveExtractor.Tests.CliTests
14 | {
15 | [TestClass]
16 | public class CliTests : BaseExtractorTestClass
17 | {
18 | [DataTestMethod]
19 | [DataRow("TestData.zip", 5)]
20 | [DataRow("TestData.7z")]
21 | [DataRow("TestData.tar", 6)]
22 | [DataRow("TestData.rar")]
23 | [DataRow("TestData.rar4")]
24 | [DataRow("TestData.tar.bz2", 6)]
25 | [DataRow("TestData.tar.gz", 6)]
26 | [DataRow("TestData.tar.xz")]
27 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)]
28 | [DataRow("TestData.a")]
29 | [DataRow("TestData.bsd.ar")]
30 | [DataRow("TestData.iso")]
31 | [DataRow("TestData.vhdx")]
32 | [DataRow("TestData.wim")]
33 | [DataRow("EmptyFile.txt", 1)]
34 | [DataRow("TestDataArchivesNested.Zip", 54)]
35 | public void ExtractArchiveParallel(string fileName, int expectedNumFiles = 3)
36 | {
37 | ExtractArchive(fileName, expectedNumFiles, false);
38 | }
39 |
40 | internal void ExtractArchive(string fileName, int expectedNumFiles, bool singleThread)
41 | {
42 | var directory = TestPathHelpers.GetFreshTestDirectory();
43 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
44 | RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions() { Input = path, Output = directory, Verbose = true, SingleThread = singleThread});
45 | var files = Array.Empty();
46 | Thread.Sleep(100);
47 | if (Directory.Exists(directory))
48 | {
49 | files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray();
50 | }
51 | Assert.AreEqual(expectedNumFiles, files.Length);
52 | }
53 |
54 | [DataTestMethod]
55 | [DataRow("TestData.zip", 5)]
56 | [DataRow("TestData.7z")]
57 | [DataRow("TestData.tar", 6)]
58 | [DataRow("TestData.rar")]
59 | [DataRow("TestData.rar4")]
60 | [DataRow("TestData.tar.bz2", 6)]
61 | [DataRow("TestData.tar.gz", 6)]
62 | [DataRow("TestData.tar.xz")]
63 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)]
64 | [DataRow("TestData.a")]
65 | [DataRow("TestData.bsd.ar")]
66 | [DataRow("TestData.iso")]
67 | [DataRow("TestData.vhdx")]
68 | [DataRow("TestData.wim")]
69 | [DataRow("EmptyFile.txt", 1)]
70 | [DataRow("TestDataArchivesNested.Zip", 54)]
71 | public void ExtractArchiveSingleThread(string fileName, int expectedNumFiles = 3)
72 | {
73 | ExtractArchive(fileName, expectedNumFiles, true);
74 | }
75 |
76 | [DataTestMethod]
77 | [DataRow("TestDataForFilters.7z")]
78 | public void ExtractArchiveWithAllowFilters(string fileName, int expectedNumFiles = 1)
79 | {
80 | var directory = TestPathHelpers.GetFreshTestDirectory();
81 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
82 | var newpath = TempPath.GetTempFilePath();
83 | File.Copy(path, newpath,true);
84 | RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions()
85 | {
86 | Input = newpath,
87 | Output = directory,
88 | Verbose = true,
89 | AllowFilters = new string[]
90 | {
91 | "*.cs"
92 | }
93 | });
94 | var files = Array.Empty();
95 | if (Directory.Exists(directory))
96 | {
97 | files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray();
98 | }
99 | Assert.AreEqual(expectedNumFiles, files.Length);
100 | }
101 |
102 | [DataTestMethod]
103 | [DataRow("TestDataForFilters.7z")]
104 | public void ExtractArchiveWithDenyFilters(string fileName, int expectedNumFiles = 2)
105 | {
106 | var directory = TestPathHelpers.GetFreshTestDirectory();
107 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
108 | var newpath = TempPath.GetTempFilePath();
109 | File.Copy(path, newpath, true);
110 | RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions()
111 | {
112 | Input = newpath,
113 | Output = directory,
114 | Verbose = true,
115 | DenyFilters = new string[]
116 | {
117 | "*.cs"
118 | }
119 | });
120 | var files = Array.Empty();
121 | if (Directory.Exists(directory))
122 | {
123 | files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray();
124 | }
125 | Assert.AreEqual(expectedNumFiles, files.Length);
126 | }
127 |
128 | [DataTestMethod]
129 | [DataRow("TestDataEncrypted.7z")]
130 | [DataRow("TestDataEncryptedAes.zip")]
131 | [DataRow("TestDataEncrypted.rar4")]
132 | public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3)
133 | {
134 | var directory = TestPathHelpers.GetFreshTestDirectory();
135 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
136 | var passwords = EncryptedArchiveTests.TestArchivePasswords.Values.SelectMany(x => x);
137 | RecursiveExtractorClient.ExtractCommand(new ExtractCommandOptions() { Input = path, Output = directory, Verbose = true, Passwords = passwords });
138 | string[] files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToArray();
139 | Assert.AreEqual(expectedNumFiles, files.Length);
140 | }
141 |
142 | protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
143 | }
144 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
2 |
3 |
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli/ExtractCommandOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System.Collections.Generic;
3 |
4 | namespace Microsoft.CST.RecursiveExtractor.Cli
5 | {
6 | [Verb("extract", true, HelpText = "Extract an archive recursively.")]
7 | public class ExtractCommandOptions
8 | {
9 | [Option('i',"input", HelpText = "The name of the archive to extract.", Required = true)]
10 | public string Input { get; set; } = string.Empty;
11 |
12 | [Option('o', "output", HelpText = "The directory to extract to.", Default = ".")]
13 | public string Output { get; set; } = string.Empty;
14 |
15 | [Option('p', "passwords", Required = false, HelpText = "Comma-separated list of passwords to use.", Separator = ',')]
16 | public IEnumerable? Passwords { get; set; }
17 |
18 | [Option('a', "allow-globs", Required = false, HelpText = "Comma-separated list of glob expressions. When set, files are ONLY written to disk if they match one of these filters.", Separator = ',')]
19 | public IEnumerable? AllowFilters { get; set; }
20 |
21 | [Option('d', "deny-globs", Required = false, HelpText = "Comma-separated list of glob expressions. When set, files are NOT written to disk if they match one of these filters.", Separator = ',')]
22 | public IEnumerable? DenyFilters { get; set; }
23 |
24 | [Option('R', "raw-extensions", Required = false, HelpText = "Comma-separated list of file extensions to treat as raw files (don't recurse into).", Separator = ',')]
25 | public IEnumerable? RawExtensions { get; set; }
26 |
27 | [Option('n', "no-recursion", Required = false, HelpText = "Disable recursive extraction.")]
28 | public bool DisableRecursion { get; set; }
29 |
30 | [Option('s', "single-thread", Required = false, HelpText = "Disable parallelized extraction.")]
31 | public bool SingleThread { get; set; }
32 |
33 | [Option(HelpText = "Set logging to 'verbose'.")]
34 | public bool Verbose { get; set; }
35 |
36 | [Option(HelpText = "Set logging to 'debug'.")]
37 | public bool Debug { get; set; }
38 |
39 | [Option(HelpText = "Output the names of all files extracted.")]
40 | public bool PrintNames { get; set; }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "RecursiveExtractor.Cli": {
4 | "commandName": "Project",
5 | "commandLineArgs": "--input C:\\Users\\gstocco\\ExampleFaultyNumberOfEntries.tar --output C:\\Users\\gstocco\\ExampleFaultyNumberOfEntriesOut"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0;net9.0
6 | 10.0
7 | enable
8 | Microsoft.CST.RecursiveExtractor.Cli
9 | © Microsoft Corporation. All rights reserved.
10 | Recursive Extractor
11 | Microsoft
12 | Microsoft
13 | RecursiveExtractor is able to process the following formats: ar, bzip2, deb, gzip, iso, tar, vhd, vhdx, vmdk, wim, xzip, and zip. RecursiveExtractor automatically detects the archive type and fails gracefully when attempting to process malformed content.
14 | false
15 | true
16 | Microsoft.CST.RecursiveExtractor.CLI
17 | 0.0.0
18 | https://github.com/microsoft/RecursiveExtractor
19 | unzip extract extractor
20 | RecursiveExtractor
21 | LICENSE.txt
22 | icon-128.png
23 | true
24 | snupkg
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli/RecursiveExtractor.Cli.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Project
5 | --input /Users/gabe/Documents/GitHub/RecursiveExtractor/RecursiveExtractor.Tests/TestData/Nested.zip --output /Users/gabe/Documents/GitHub/RecursiveExtractor/NestedExtracted --verbose
6 | true
7 |
8 |
--------------------------------------------------------------------------------
/RecursiveExtractor.Cli/RecursiveExtractorClient.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text.RegularExpressions;
7 | using NLog;
8 | using NLog.Config;
9 | using NLog.Targets;
10 | namespace Microsoft.CST.RecursiveExtractor.Cli
11 | {
12 | public static class RecursiveExtractorClient
13 | {
14 | public static int Main(string[] args)
15 | {
16 | return CommandLine.Parser.Default.ParseArguments(args)
17 | .MapResult(
18 | (ExtractCommandOptions opts) => ExtractCommand(opts),
19 | _ => 1);
20 | }
21 |
22 | public static int ExtractCommand(ExtractCommandOptions options)
23 | {
24 | var config = new LoggingConfiguration();
25 | var consoleTarget = new ConsoleTarget
26 | {
27 | Name = "console",
28 | Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}",
29 | };
30 | if (options.Verbose)
31 | {
32 | config.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget, "*");
33 | }
34 | else if (options.Debug)
35 | {
36 | config.AddRule(LogLevel.Debug, LogLevel.Fatal, consoleTarget, "*");
37 | }
38 | else
39 | {
40 | config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget, "*");
41 | }
42 |
43 | LogManager.Configuration = config;
44 |
45 | var extractor = new Extractor();
46 | var extractorOptions = new ExtractorOptions()
47 | {
48 | ExtractSelfOnFail = true,
49 | Parallel = !options.SingleThread,
50 | RawExtensions = options.RawExtensions ?? Array.Empty(),
51 | Recurse = !options.DisableRecursion,
52 | AllowFilters = options.AllowFilters ?? Array.Empty(),
53 | DenyFilters = options.DenyFilters ?? Array.Empty()
54 | };
55 | if (options.Passwords?.Any() ?? false)
56 | {
57 | extractorOptions.Passwords = new Dictionary>()
58 | {
59 | {
60 | new Regex(".*",RegexOptions.Compiled),
61 | options.Passwords.ToList()
62 | }
63 | };
64 | }
65 | var exitCode = ExtractionStatusCode.Ok;
66 | try
67 | {
68 | exitCode = extractor.ExtractToDirectory(options.Output, options.Input, extractorOptions, options.PrintNames);
69 | }
70 | catch(Exception e)
71 | {
72 | Logger.Error($"Exception while extracting. {e.GetType()}:{e.Message} ({e.StackTrace})");
73 | exitCode = ExtractionStatusCode.Failure;
74 | }
75 |
76 | return (int)exitCode;
77 | }
78 | private readonly static NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/BaseExtractorTestClass.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor.Tests;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NLog;
4 | using NLog.Config;
5 | using NLog.Targets;
6 |
7 | namespace RecursiveExtractor.Tests.ExtractorTests;
8 |
9 | public class BaseExtractorTestClass
10 | {
11 | [ClassCleanup]
12 | public static void ClassCleanup()
13 | {
14 | TestPathHelpers.DeleteTestDirectory();
15 | }
16 |
17 | [ClassInitialize]
18 | public static void ClassInitialize(TestContext context)
19 | {
20 | var config = new LoggingConfiguration();
21 | var consoleTarget = new ConsoleTarget
22 | {
23 | Name = "console",
24 | Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}",
25 | };
26 | config.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget, "*");
27 |
28 | LogManager.Configuration = config;
29 | }
30 | protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
31 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/DisposeBehaviorTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.CST.RecursiveExtractor.Tests;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Threading.Tasks;
8 |
9 | namespace RecursiveExtractor.Tests.ExtractorTests;
10 |
11 | [TestClass]
12 | public class DisposeBehaviorTests : BaseExtractorTestClass
13 | {
14 | [DataTestMethod]
15 | [DataRow("TestData.7z", 3, false)]
16 | [DataRow("TestData.tar", 6, false)]
17 | [DataRow("TestData.rar", 3, false)]
18 | [DataRow("TestData.rar4", 3, false)]
19 | [DataRow("TestData.tar.bz2", 6, false)]
20 | [DataRow("TestData.tar.gz", 6, false)]
21 | [DataRow("TestData.tar.xz", 3, false)]
22 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)]
23 | [DataRow("TestData.a", 3, false)]
24 | [DataRow("TestData.bsd.ar", 3, false)]
25 | [DataRow("TestData.iso", 3, false)]
26 | [DataRow("TestData.vhdx", 3, false)]
27 | [DataRow("TestData.wim", 3, false)]
28 | [DataRow("EmptyFile.txt", 1, false)]
29 | [DataRow("TestData.zip", 5, true)]
30 | [DataRow("TestData.7z", 3, true)]
31 | [DataRow("TestData.tar", 6, true)]
32 | [DataRow("TestData.rar", 3, true)]
33 | [DataRow("TestData.rar4", 3, true)]
34 | [DataRow("TestData.tar.bz2", 6, true)]
35 | [DataRow("TestData.tar.gz", 6, true)]
36 | [DataRow("TestData.tar.xz", 3, true)]
37 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)]
38 | [DataRow("TestData.a", 3, true)]
39 | [DataRow("TestData.bsd.ar", 3, true)]
40 | [DataRow("TestData.iso", 3, true)]
41 | [DataRow("TestData.vhdx", 3, true)]
42 | [DataRow("TestData.wim", 3, true)]
43 | [DataRow("EmptyFile.txt", 1, true)]
44 | [DataRow("TestDataArchivesNested.Zip", 54, true)]
45 | [DataRow("TestDataArchivesNested.Zip", 54, false)]
46 | [DataRow("TestDataArchivesNested.Zip", 54, true)]
47 | [DataRow("TestDataArchivesNested.Zip", 54, false)]
48 | public void ExtractArchiveAndDisposeWhileEnumerating(string fileName, int expectedNumFiles = 3,
49 | bool parallel = false)
50 | {
51 | var extractor = new Extractor();
52 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
53 | var results = extractor.Extract(path, new ExtractorOptions() { Parallel = parallel });
54 | var disposedResults = new List();
55 | foreach (var file in results)
56 | {
57 | disposedResults.Add(file);
58 | using var theStream = file.Content;
59 | // Do something with the stream.
60 | _ = theStream.ReadByte();
61 | }
62 |
63 | Assert.AreEqual(expectedNumFiles, disposedResults.Count);
64 | foreach (var disposedResult in disposedResults)
65 | {
66 | Assert.ThrowsException(() => disposedResult.Content.Position);
67 | }
68 | }
69 |
70 | [DataTestMethod]
71 | [DataRow("TestData.7z", 3, false)]
72 | [DataRow("TestData.tar", 6, false)]
73 | [DataRow("TestData.rar", 3, false)]
74 | [DataRow("TestData.rar4", 3, false)]
75 | [DataRow("TestData.tar.bz2", 6, false)]
76 | [DataRow("TestData.tar.gz", 6, false)]
77 | [DataRow("TestData.tar.xz", 3, false)]
78 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, false)]
79 | [DataRow("TestData.a", 3, false)]
80 | [DataRow("TestData.bsd.ar", 3, false)]
81 | [DataRow("TestData.iso", 3, false)]
82 | [DataRow("TestData.vhdx", 3, false)]
83 | [DataRow("TestData.wim", 3, false)]
84 | [DataRow("EmptyFile.txt", 1, false)]
85 | [DataRow("TestData.zip", 5, true)]
86 | [DataRow("TestData.7z", 3, true)]
87 | [DataRow("TestData.tar", 6, true)]
88 | [DataRow("TestData.rar", 3, true)]
89 | [DataRow("TestData.rar4", 3, true)]
90 | [DataRow("TestData.tar.bz2", 6, true)]
91 | [DataRow("TestData.tar.gz", 6, true)]
92 | [DataRow("TestData.tar.xz", 3, true)]
93 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8, true)]
94 | [DataRow("TestData.a", 3, true)]
95 | [DataRow("TestData.bsd.ar", 3, true)]
96 | [DataRow("TestData.iso", 3, true)]
97 | [DataRow("TestData.vhdx", 3, true)]
98 | [DataRow("TestData.wim", 3, true)]
99 | [DataRow("EmptyFile.txt", 1, true)]
100 | [DataRow("TestDataArchivesNested.Zip", 54, true)]
101 | [DataRow("TestDataArchivesNested.Zip", 54, false)]
102 | [DataRow("TestDataArchivesNested.Zip", 54, true)]
103 | [DataRow("TestDataArchivesNested.Zip", 54, false)]
104 | public async Task ExtractArchiveAndDisposeWhileEnumeratingAsync(string fileName, int expectedNumFiles = 3,
105 | bool parallel = false)
106 | {
107 | var extractor = new Extractor();
108 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
109 | var results = extractor.ExtractAsync(path, new ExtractorOptions() { Parallel = parallel });
110 | var disposedResults = new List();
111 | await foreach (var file in results)
112 | {
113 | disposedResults.Add(file);
114 | using var theStream = file.Content;
115 | // Do something with the stream.
116 | _ = theStream.ReadByte();
117 | }
118 |
119 | Assert.AreEqual(expectedNumFiles, disposedResults.Count);
120 | foreach (var disposedResult in disposedResults)
121 | {
122 | Assert.ThrowsException(() => disposedResult.Content.Position);
123 | }
124 | }
125 |
126 | [DataTestMethod]
127 | [DataRow("TestData.zip")]
128 | public void EnsureDisposedWithExtractToDirectory(string fileName)
129 | {
130 | var directory = TestPathHelpers.GetFreshTestDirectory();
131 | var copyDirectory = TestPathHelpers.GetFreshTestDirectory();
132 | Directory.CreateDirectory(copyDirectory);
133 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
134 | // Make a copy of the archive so it can be deleted later to confirm properly disposed
135 | var copyPath = Path.Combine(copyDirectory, fileName);
136 | File.Copy(path, copyPath);
137 | var extractor = new Extractor();
138 | extractor.ExtractToDirectory(directory, copyPath);
139 | File.Delete(copyPath);
140 | if (Directory.Exists(directory))
141 | {
142 | Directory.Delete(directory, true);
143 | }
144 | if (Directory.Exists(copyDirectory)) {
145 | Directory.Delete(copyDirectory, true);
146 | }
147 | }
148 |
149 | [DataTestMethod]
150 | [DataRow("TestData.zip")]
151 | public async Task EnsureDisposedWithExtractToDirectoryAsync(string fileName)
152 | {
153 | var directory = TestPathHelpers.GetFreshTestDirectory();
154 | var copyDirectory = TestPathHelpers.GetFreshTestDirectory();
155 | Directory.CreateDirectory(copyDirectory);
156 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
157 | // Make a copy of the archive so it can be deleted later to confirm properly disposed
158 | var copyPath = Path.Combine(copyDirectory, fileName);
159 | File.Copy(path, copyPath);
160 | var extractor = new Extractor();
161 | await extractor.ExtractToDirectoryAsync(directory, copyPath);
162 | File.Delete(copyPath);
163 | if (Directory.Exists(directory))
164 | {
165 | Directory.Delete(directory, true);
166 | }
167 | if (Directory.Exists(copyDirectory))
168 | {
169 | Directory.Delete(copyDirectory, true);
170 | }
171 | }
172 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/EncryptedArchiveTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace RecursiveExtractor.Tests.ExtractorTests;
10 |
11 | [TestClass]
12 | public class EncryptedArchiveTests : BaseExtractorTestClass
13 | {
14 | [DataTestMethod]
15 | [DataRow("TestDataEncryptedZipCrypto.zip")]
16 | [DataRow("TestDataEncryptedAes.zip")]
17 | [DataRow("TestDataEncrypted.7z")]
18 | [DataRow("TestDataEncrypted.rar4")]
19 | [DataRow("TestDataEncrypted.rar")]
20 | public void FileTypeSetCorrectlyForEncryptedArchives(string fileName, int expectedNumFiles = 1)
21 | {
22 | var extractor = new Extractor();
23 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
24 | var results = extractor.Extract(path, new ExtractorOptions() { Parallel = false }).ToList();
25 | Assert.AreEqual(expectedNumFiles, results.Count());
26 | Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus);
27 | }
28 |
29 | [DataTestMethod]
30 | [DataRow("TestDataEncryptedZipCrypto.zip")]
31 | [DataRow("TestDataEncryptedAes.zip")]
32 | [DataRow("TestDataEncrypted.7z")]
33 | [DataRow("TestDataEncrypted.rar4")]
34 | [DataRow("TestDataEncrypted.rar")]
35 | public async Task FileTypeSetCorrectlyForEncryptedArchivesAsync(string fileName, int expectedNumFiles = 1)
36 | {
37 | var extractor = new Extractor();
38 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
39 | var results = new List();
40 | await foreach (var entry in extractor.ExtractAsync(path, new ExtractorOptions()))
41 | {
42 | results.Add(entry);
43 | }
44 |
45 | Assert.AreEqual(expectedNumFiles, results.Count);
46 | Assert.AreEqual(FileEntryStatus.EncryptedArchive, results.First().EntryStatus);
47 | }
48 |
49 | [DataTestMethod]
50 | [DataRow("TestDataEncryptedZipCrypto.zip")]
51 | [DataRow("TestDataEncryptedAes.zip")]
52 | [DataRow("TestDataEncrypted.7z")]
53 | [DataRow("TestDataEncrypted.rar4")]
54 | [DataRow("EncryptedWithPlainNames.7z")]
55 | [DataRow("EncryptedWithPlainNames.rar4")]
56 | //[DataRow("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517
57 | public void ExtractEncryptedArchive(string fileName, int expectedNumFiles = 3)
58 | {
59 | var extractor = new Extractor();
60 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
61 | var results = extractor.Extract(path, new ExtractorOptions() { Passwords = TestArchivePasswords, ExtractSelfOnFail = false })
62 | .ToList(); // Make this a list so it fully populates
63 | Assert.AreEqual(expectedNumFiles, results.Count);
64 | Assert.AreEqual(0, results.Count(x => x.EntryStatus == FileEntryStatus.EncryptedArchive || x.EntryStatus == FileEntryStatus.FailedArchive));
65 | }
66 |
67 | [DataTestMethod]
68 | [DataRow("TestDataEncryptedZipCrypto.zip")]
69 | [DataRow("TestDataEncryptedAes.zip")]
70 | [DataRow("TestDataEncrypted.7z")]
71 | [DataRow("TestDataEncrypted.rar4")]
72 | [DataRow("EncryptedWithPlainNames.7z")]
73 | [DataRow("EncryptedWithPlainNames.rar4")]
74 | //[DataRow("TestDataEncrypted.rar")] // RAR5 is not yet supported by SharpCompress: https://github.com/adamhathcock/sharpcompress/issues/517
75 | public async Task ExtractEncryptedArchiveAsync(string fileName, int expectedNumFiles = 3)
76 | {
77 | var extractor = new Extractor();
78 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
79 | var results = extractor.ExtractAsync(path, new ExtractorOptions() { Passwords = TestArchivePasswords, ExtractSelfOnFail = false });
80 | var numEntries = 0;
81 | var numEntriesEncrypted = 0;
82 | await foreach (var entry in results)
83 | {
84 | numEntries++;
85 | if (entry.EntryStatus == FileEntryStatus.EncryptedArchive || entry.EntryStatus == FileEntryStatus.FailedArchive)
86 | {
87 | numEntriesEncrypted++;
88 | }
89 | }
90 |
91 | Assert.AreEqual(expectedNumFiles, numEntries);
92 | Assert.AreEqual(0, numEntriesEncrypted);
93 | }
94 |
95 |
96 | public static Dictionary> TestArchivePasswords { get; } = new Dictionary>()
97 | {
98 | {
99 | new Regex("EncryptedZipCrypto.zip"), new List()
100 | {
101 | "AnIncorrectPassword", "TestData", // ZipCrypto Encrypted
102 | }
103 | },
104 | {
105 | new Regex("EncryptedAes.zip"), new List()
106 | {
107 | "AnIncorrectPassword", "TestData" // AES Encrypted
108 | }
109 | },
110 | {
111 | new Regex("\\.7z"), new List()
112 | {
113 | "AnIncorrectPassword",
114 | "TestData", // TestDataEncrypted.7z, EncryptedWithPlainNames.7z, NestedEncrypted.7z
115 | }
116 | },
117 | { new Regex("\\.rar"), new List() { "AnIncorrectPassword", "TestData" } }
118 | };
119 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/FilterTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace RecursiveExtractor.Tests.ExtractorTests;
9 |
10 | [TestClass]
11 | public class FilterTests : BaseExtractorTestClass
12 | {
13 | [DataTestMethod]
14 | [DataRow("TestData.zip")]
15 | [DataRow("TestData.7z")]
16 | [DataRow("TestData.tar")]
17 | [DataRow("TestData.rar")]
18 | [DataRow("TestData.rar4")]
19 | [DataRow("TestData.tar.bz2")]
20 | [DataRow("TestData.tar.gz")]
21 | [DataRow("TestData.tar.xz")]
22 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)]
23 | [DataRow("TestData.a", 0)]
24 | [DataRow("TestData.bsd.ar", 0)]
25 | [DataRow("TestData.iso")]
26 | [DataRow("TestData.vhdx")]
27 | [DataRow("TestData.wim")]
28 | [DataRow("EmptyFile.txt", 0)]
29 | [DataRow("TestDataArchivesNested.Zip", 9)]
30 | public async Task ExtractArchiveAsyncAllowFiltered(string fileName, int expectedNumFiles = 1)
31 | {
32 | var extractor = new Extractor();
33 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
34 | var results = extractor.ExtractAsync(path,
35 | new ExtractorOptions() { AllowFilters = new string[] { "**/Bar/**", "**/TestData.tar" } });
36 | var numResults = 0;
37 | await foreach (var result in results)
38 | {
39 | numResults++;
40 | }
41 |
42 | Assert.AreEqual(expectedNumFiles, numResults);
43 | }
44 |
45 | [DataTestMethod]
46 | [DataRow("TestData.zip")]
47 | [DataRow("TestData.7z")]
48 | [DataRow("TestData.tar")]
49 | [DataRow("TestData.rar")]
50 | [DataRow("TestData.rar4")]
51 | [DataRow("TestData.tar.bz2")]
52 | [DataRow("TestData.tar.gz")]
53 | [DataRow("TestData.tar.xz")]
54 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)]
55 | [DataRow("TestData.a", 0)]
56 | [DataRow("TestData.bsd.ar", 0)]
57 | [DataRow("TestData.iso")]
58 | [DataRow("TestData.vhdx")]
59 | [DataRow("TestData.wim")]
60 | [DataRow("EmptyFile.txt", 0)]
61 | [DataRow("TestDataArchivesNested.Zip", 9)]
62 | public void ExtractArchiveAllowFiltered(string fileName, int expectedNumFiles = 1)
63 | {
64 | var extractor = new Extractor();
65 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
66 | var results = extractor.Extract(path,
67 | new ExtractorOptions() { AllowFilters = new string[] { "**/Bar/**", "**/TestData.tar" } });
68 | Assert.AreEqual(expectedNumFiles, results.Count());
69 | }
70 |
71 | [DataTestMethod]
72 | [DataRow("TestData.zip")]
73 | [DataRow("TestData.7z")]
74 | [DataRow("TestData.tar")]
75 | [DataRow("TestData.rar")]
76 | [DataRow("TestData.rar4")]
77 | [DataRow("TestData.tar.bz2")]
78 | [DataRow("TestData.tar.gz")]
79 | [DataRow("TestData.tar.xz")]
80 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 0)]
81 | [DataRow("TestData.a", 0)]
82 | [DataRow("TestData.bsd.ar", 0)]
83 | [DataRow("TestData.iso")]
84 | [DataRow("TestData.vhdx")]
85 | [DataRow("TestData.wim")]
86 | [DataRow("EmptyFile.txt", 0)]
87 | [DataRow("TestDataArchivesNested.Zip", 9)]
88 | public void ExtractArchiveParallelAllowFiltered(string fileName, int expectedNumFiles = 1)
89 | {
90 | var extractor = new Extractor();
91 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
92 | var results = extractor.Extract(path,
93 | new ExtractorOptions() { Parallel = true, AllowFilters = new string[] { "**/Bar/**", "**/TestData.tar" } });
94 | Assert.AreEqual(expectedNumFiles, results.Count());
95 | }
96 |
97 | [DataTestMethod]
98 | [DataRow("TestData.zip", 4)]
99 | [DataRow("TestData.7z")]
100 | [DataRow("TestData.tar", 5)]
101 | [DataRow("TestData.rar")]
102 | [DataRow("TestData.rar4")]
103 | [DataRow("TestData.tar.bz2", 5)]
104 | [DataRow("TestData.tar.gz", 5)]
105 | [DataRow("TestData.tar.xz")]
106 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)]
107 | [DataRow("TestData.a", 3)]
108 | [DataRow("TestData.bsd.ar", 3)]
109 | [DataRow("TestData.iso")]
110 | [DataRow("TestData.vhdx")]
111 | [DataRow("TestData.wim")]
112 | [DataRow("EmptyFile.txt", 1)]
113 | [DataRow("TestDataArchivesNested.Zip", 45)]
114 | public void ExtractArchiveDenyFiltered(string fileName, int expectedNumFiles = 2)
115 | {
116 | var extractor = new Extractor();
117 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
118 | var results = extractor.Extract(path, new ExtractorOptions() { DenyFilters = new string[] { "**/Bar/**" } });
119 | Assert.AreEqual(expectedNumFiles, results.Count());
120 | }
121 |
122 | [DataTestMethod]
123 | [DataRow("TestData.zip", 4)]
124 | [DataRow("TestData.7z")]
125 | [DataRow("TestData.tar", 5)]
126 | [DataRow("TestData.rar")]
127 | [DataRow("TestData.rar4")]
128 | [DataRow("TestData.tar.bz2", 5)]
129 | [DataRow("TestData.tar.gz", 5)]
130 | [DataRow("TestData.tar.xz")]
131 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)]
132 | [DataRow("TestData.a", 3)]
133 | [DataRow("TestData.bsd.ar", 3)]
134 | [DataRow("TestData.iso")]
135 | [DataRow("TestData.vhdx")]
136 | [DataRow("TestData.wim")]
137 | [DataRow("EmptyFile.txt", 1)]
138 | [DataRow("TestDataArchivesNested.Zip", 45)]
139 | public void ExtractArchiveParallelDenyFiltered(string fileName, int expectedNumFiles = 2)
140 | {
141 | var extractor = new Extractor();
142 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
143 | var results = extractor.Extract(path,
144 | new ExtractorOptions() { Parallel = true, DenyFilters = new string[] { "**/Bar/**" } });
145 | Assert.AreEqual(expectedNumFiles, results.Count());
146 | }
147 |
148 | [DataTestMethod]
149 | [DataRow("TestData.zip", 4)]
150 | [DataRow("TestData.7z")]
151 | [DataRow("TestData.tar", 5)]
152 | [DataRow("TestData.rar")]
153 | [DataRow("TestData.rar4")]
154 | [DataRow("TestData.tar.bz2", 5)]
155 | [DataRow("TestData.tar.gz", 5)]
156 | [DataRow("TestData.tar.xz")]
157 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", 8)]
158 | [DataRow("TestData.a", 3)]
159 | [DataRow("TestData.bsd.ar", 3)]
160 | [DataRow("TestData.iso")]
161 | [DataRow("TestData.vhdx")]
162 | [DataRow("TestData.wim")]
163 | [DataRow("EmptyFile.txt", 1)]
164 | [DataRow("TestDataArchivesNested.Zip", 45)]
165 | public async Task ExtractArchiveAsyncDenyFiltered(string fileName, int expectedNumFiles = 2)
166 | {
167 | var extractor = new Extractor();
168 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
169 | var results =
170 | extractor.ExtractAsync(path, new ExtractorOptions() { DenyFilters = new string[] { "**/Bar/**" } });
171 | var numResults = 0;
172 | await foreach (var result in results)
173 | {
174 | numResults++;
175 | }
176 |
177 | Assert.AreEqual(expectedNumFiles, numResults);
178 | }
179 |
180 | [DataTestMethod]
181 | [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new ArchiveFileType[] { }, false)]
182 | [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.TAR }, new ArchiveFileType[] { }, true)]
183 | [DataRow(ArchiveFileType.ZIP, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, true)]
184 | [DataRow(ArchiveFileType.TAR, new ArchiveFileType[] { }, new[] { ArchiveFileType.ZIP }, false)]
185 | [DataRow(ArchiveFileType.ZIP, new[] { ArchiveFileType.ZIP }, new[] { ArchiveFileType.ZIP }, false)]
186 | public void TestArchiveTypeFilters(ArchiveFileType typeToCheck, IEnumerable denyTypes,
187 | IEnumerable allowTypes, bool expected)
188 | {
189 | ExtractorOptions opts = new() { AllowTypes = allowTypes, DenyTypes = denyTypes };
190 | Assert.AreEqual(expected, opts.IsAcceptableType(typeToCheck));
191 | }
192 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/MiniMagicTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.IO;
4 |
5 | namespace RecursiveExtractor.Tests.ExtractorTests;
6 |
7 | [TestClass]
8 | public class MiniMagicTests : BaseExtractorTestClass
9 | {
10 | [DataTestMethod]
11 | [DataRow("TestData.zip", ArchiveFileType.ZIP)]
12 | [DataRow("TestData.7z", ArchiveFileType.P7ZIP)]
13 | [DataRow("TestData.Tar", ArchiveFileType.TAR)]
14 | [DataRow("TestData.rar", ArchiveFileType.RAR5)]
15 | [DataRow("TestData.rar4", ArchiveFileType.RAR)]
16 | [DataRow("TestData.tar.bz2", ArchiveFileType.BZIP2)]
17 | [DataRow("TestData.tar.gz", ArchiveFileType.GZIP)]
18 | [DataRow("TestData.tar.xz", ArchiveFileType.XZ)]
19 | [DataRow("sysvbanner_1.0-17fakesync1_amd64.deb", ArchiveFileType.DEB)]
20 | [DataRow("TestData.a", ArchiveFileType.AR)]
21 | [DataRow("TestData.iso", ArchiveFileType.ISO_9660)]
22 | [DataRow("UdfTest.iso", ArchiveFileType.UDF)]
23 | [DataRow("TestData.vhdx", ArchiveFileType.VHDX)]
24 | [DataRow("TestData.wim", ArchiveFileType.WIM)]
25 | [DataRow("Empty.vmdk", ArchiveFileType.VMDK)]
26 | [DataRow("HfsSampleUDCO.dmg", ArchiveFileType.DMG)]
27 | [DataRow("EmptyFile.txt", ArchiveFileType.UNKNOWN)]
28 | public void TestMiniMagic(string fileName, ArchiveFileType expectedArchiveFileType)
29 | {
30 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
31 | using var fs = new FileStream(path, FileMode.Open);
32 | // Test just based on the content
33 | var fileEntry = new FileEntry("NoName", fs);
34 |
35 | // We make sure the expected type matches and we have reset the stream
36 | Assert.AreEqual(expectedArchiveFileType, fileEntry.ArchiveType);
37 | Assert.AreEqual(0, fileEntry.Content.Position);
38 |
39 | // Should also work if the stream doesn't start at 0
40 | fileEntry.Content.Position = 10;
41 | Assert.AreEqual(expectedArchiveFileType, fileEntry.ArchiveType);
42 | Assert.AreEqual(10, fileEntry.Content.Position);
43 | }
44 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/MiscTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace RecursiveExtractor.Tests.ExtractorTests;
9 |
10 | [TestClass]
11 | public class MiscTests
12 | {
13 | [DataTestMethod]
14 | [DataRow("TestDataCorrupt.tar", false, 0, 1)]
15 | [DataRow("TestDataCorrupt.tar", true, 1, 1)]
16 | [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)]
17 | [DataRow("TestDataCorrupt.tar.zip", true, 0, 2)]
18 | public async Task ExtractCorruptArchiveAsync(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures, int expectedNumFiles)
19 | {
20 | var extractor = new Extractor();
21 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
22 | var results = await extractor.ExtractAsync(path,
23 | new ExtractorOptions() { RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync();
24 |
25 |
26 | Assert.AreEqual(expectedNumFiles, results.Count);
27 | var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive);
28 | Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives);
29 | }
30 |
31 | [DataTestMethod]
32 | [DataRow("Lorem.txt", true, 1)]
33 | [DataRow("Lorem.txt", false, 0)]
34 | public async Task ExtractFlatFileAsync(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures)
35 | {
36 | var extractor = new Extractor();
37 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName);
38 | var results = await extractor.ExtractAsync(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToListAsync();
39 |
40 | Assert.AreEqual(1, results.Count);
41 | var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive);
42 | Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives);
43 | }
44 |
45 |
46 |
47 | [DataTestMethod]
48 | [DataRow("TestDataCorrupt.tar", false, 0, 1)]
49 | [DataRow("TestDataCorrupt.tar", true, 1, 1)]
50 | [DataRow("TestDataCorrupt.tar.zip", false, 0, 2)]
51 | [DataRow("TestDataCorrupt.tar.zip", true, 0, 2)]
52 | [DataRow("TestDataCorruptWim.zip", true, 0, 0)]
53 | public void ExtractCorruptArchive(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures, int expectedNumFiles)
54 | {
55 | var extractor = new Extractor();
56 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
57 | var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList();
58 |
59 | Assert.AreEqual(expectedNumFiles, results.Count);
60 | var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive);
61 | Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives);
62 | }
63 |
64 | [DataTestMethod]
65 | [DataRow("Lorem.txt", true, 1)]
66 | [DataRow("Lorem.txt", false, 0)]
67 | public void ExtractFlatFile(string fileName, bool requireTopLevelToBeArchive, int expectedNumFailures)
68 | {
69 | var extractor = new Extractor();
70 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestData", fileName);
71 | var results = extractor.Extract(path, new ExtractorOptions(){ RequireTopLevelToBeArchive = requireTopLevelToBeArchive }).ToList();
72 |
73 | Assert.AreEqual(1, results.Count);
74 | var actualNumberOfFailedArchives = results.Count(x => x.EntryStatus == FileEntryStatus.FailedArchive);
75 | Assert.AreEqual(expectedNumFailures, actualNumberOfFailedArchives);
76 | }
77 |
78 | [DataTestMethod]
79 | [DataRow("EmptyFile.txt")]
80 | [DataRow("TestData.zip", ".zip")]
81 | public void ExtractAsRaw(string fileName, string? RawExtension = null)
82 | {
83 | var extractor = new Extractor();
84 | var options = new ExtractorOptions()
85 | {
86 | RawExtensions = RawExtension is null ? new List() : new List() { RawExtension }
87 | };
88 | var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", fileName);
89 |
90 | var results = extractor.Extract(path, options);
91 | Assert.AreEqual(1, results.Count());
92 | }
93 | }
--------------------------------------------------------------------------------
/RecursiveExtractor.Tests/ExtractorTests/TestQuinesAndSlip.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CST.RecursiveExtractor;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using SharpCompress.Archives.Tar;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace RecursiveExtractor.Tests.ExtractorTests;
11 |
12 | [TestClass]
13 | public class TestQuinesAndSlip : BaseExtractorTestClass
14 | {
15 | public static IEnumerable