Compare commits

..

5 Commits

Author SHA1 Message Date
Adam Hathcock
4aa98aa3e2 run CI on release branch 2025-12-08 11:12:38 +00:00
Adam Hathcock
41c4263a66 Merge pull request #1067 from adamhathcock/adam/fix-arj-factory
Fix: Should not throw on ARJ detection
2025-12-08 11:04:24 +00:00
Adam Hathcock
2215d60394 Update tests/SharpCompress.Test/ArchiveTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 10:50:39 +00:00
Adam Hathcock
a8c33ab729 mark for 0.42.1 2025-12-08 10:50:07 +00:00
Adam Hathcock
eede5aa9cc Should not throw on ARJ detection 2025-12-08 10:45:50 +00:00
33 changed files with 322 additions and 956 deletions

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- 'master'
- 'release'
pull_request:
types: [ opened, synchronize, reopened, ready_for_review ]
@@ -17,7 +18,7 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
dotnet-version: 8.0.x
- run: dotnet run --project build/build.csproj
- uses: actions/upload-artifact@v5
with:

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ tests/TestArchives/*/Scratch
tests/TestArchives/*/Scratch2
.vs
tools
.vscode
.idea/
.DS_Store

View File

@@ -1,9 +0,0 @@
{
"recommendations": [
"ms-dotnettools.csdevkit",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime",
"csharpier.csharpier-vscode",
"formulahendry.dotnet-test-explorer"
]
}

97
.vscode/launch.json vendored
View File

@@ -1,97 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Tests (net10.0)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "dotnet",
"args": [
"test",
"${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj",
"-f",
"net10.0",
"--no-build",
"--verbosity=normal"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Debug Specific Test (net10.0)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "dotnet",
"args": [
"test",
"${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj",
"-f",
"net10.0",
"--no-build",
"--filter",
"FullyQualifiedName~${input:testName}"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Debug Performance Tests",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "dotnet",
"args": [
"run",
"--project",
"${workspaceFolder}/tests/SharpCompress.Performance/SharpCompress.Performance.csproj",
"--no-build"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Debug Build Script",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"run",
"--project",
"${workspaceFolder}/build/build.csproj",
"--",
"${input:buildTarget}"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false
}
],
"inputs": [
{
"id": "testName",
"type": "promptString",
"description": "Enter test name or pattern (e.g., TestMethodName or ClassName)",
"default": ""
},
{
"id": "buildTarget",
"type": "pickString",
"description": "Select build target",
"options": [
"clean",
"restore",
"build",
"test",
"format",
"publish",
"default"
],
"default": "build"
}
]
}

29
.vscode/settings.json vendored
View File

@@ -1,29 +0,0 @@
{
"dotnet.defaultSolution": "SharpCompress.sln",
"files.exclude": {
"**/bin": true,
"**/obj": true
},
"files.watcherExclude": {
"**/bin/**": true,
"**/obj/**": true,
"**/artifacts/**": true
},
"search.exclude": {
"**/bin": true,
"**/obj": true,
"**/artifacts": true
},
"editor.formatOnSave": false,
"[csharp]": {
"editor.defaultFormatter": "csharpier.csharpier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
}
},
"csharpier.enableDebugLogs": false,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.enableEditorConfigSupport": true,
"dotnet-test-explorer.testProjectPath": "tests/**/*.csproj"
}

178
.vscode/tasks.json vendored
View File

@@ -1,178 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/SharpCompress.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-release",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/SharpCompress.sln",
"-c",
"Release",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile",
"group": "build"
},
{
"label": "build-library",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/SharpCompress/SharpCompress.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile",
"group": "build"
},
{
"label": "restore",
"command": "dotnet",
"type": "process",
"args": [
"restore",
"${workspaceFolder}/SharpCompress.sln"
],
"problemMatcher": "$msCompile"
},
{
"label": "clean",
"command": "dotnet",
"type": "process",
"args": [
"clean",
"${workspaceFolder}/SharpCompress.sln"
],
"problemMatcher": "$msCompile"
},
{
"label": "test",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj",
"--no-build",
"--verbosity=normal"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "test",
"isDefault": true
},
"dependsOn": "build"
},
{
"label": "test-net10",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj",
"-f",
"net10.0",
"--no-build",
"--verbosity=normal"
],
"problemMatcher": "$msCompile",
"group": "test",
"dependsOn": "build"
},
{
"label": "test-net48",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj",
"-f",
"net48",
"--no-build",
"--verbosity=normal"
],
"problemMatcher": "$msCompile",
"group": "test",
"dependsOn": "build"
},
{
"label": "format",
"command": "dotnet",
"type": "process",
"args": [
"csharpier",
"."
],
"problemMatcher": []
},
{
"label": "format-check",
"command": "dotnet",
"type": "process",
"args": [
"csharpier",
"check",
"."
],
"problemMatcher": []
},
{
"label": "run-build-script",
"command": "dotnet",
"type": "process",
"args": [
"run",
"--project",
"${workspaceFolder}/build/build.csproj"
],
"problemMatcher": "$msCompile"
},
{
"label": "pack",
"command": "dotnet",
"type": "process",
"args": [
"pack",
"${workspaceFolder}/src/SharpCompress/SharpCompress.csproj",
"-c",
"Release",
"-o",
"${workspaceFolder}/artifacts/"
],
"problemMatcher": "$msCompile",
"dependsOn": "build-release"
},
{
"label": "performance-tests",
"command": "dotnet",
"type": "process",
"args": [
"run",
"--project",
"${workspaceFolder}/tests/SharpCompress.Performance/SharpCompress.Performance.csproj",
"-c",
"Release"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -49,30 +49,6 @@ SharpCompress is a pure C# compression library supporting multiple archive forma
- Use `dotnet test` to run tests
- Solution file: `SharpCompress.sln`
### Directory Structure
```
src/SharpCompress/
├── Archives/ # IArchive implementations (Zip, Tar, Rar, 7Zip, GZip)
├── Readers/ # IReader implementations (forward-only)
├── Writers/ # IWriter implementations (forward-only)
├── Compressors/ # Low-level compression streams (BZip2, Deflate, LZMA, etc.)
├── Factories/ # Format detection and factory pattern
├── Common/ # Shared types (ArchiveType, Entry, Options)
├── Crypto/ # Encryption implementations
└── IO/ # Stream utilities and wrappers
tests/SharpCompress.Test/
├── Zip/, Tar/, Rar/, SevenZip/, GZip/, BZip2/ # Format-specific tests
├── TestBase.cs # Base test class with helper methods
└── TestArchives/ # Test data (not checked into main test project)
```
### Factory Pattern
All format types implement factory interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) for auto-detection:
- `ReaderFactory.Open()` - Auto-detects format by probing stream
- `WriterFactory.Open()` - Creates writer for specified `ArchiveType`
- Factories located in: `src/SharpCompress/Factories/`
## Nullable Reference Types
- Declare variables non-nullable, and check for `null` at entry points.
@@ -140,18 +116,3 @@ SharpCompress supports multiple archive and compression formats:
- Use test archives from `tests/TestArchives` directory for consistency.
- Test stream disposal and `LeaveStreamOpen` behavior.
- Test edge cases: empty archives, large files, corrupted archives, encrypted archives.
### Test Organization
- Base class: `TestBase` - Provides `TEST_ARCHIVES_PATH`, `SCRATCH_FILES_PATH`, temp directory management
- Framework: xUnit with AwesomeAssertions
- Test archives: `tests/TestArchives/` - Use existing archives, don't create new ones unnecessarily
- Match naming style of nearby test files
## Common Pitfalls
1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't
2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
4. **Tar + non-seekable stream** - Must provide file size or it will throw
5. **Multi-framework differences** - Some features differ between .NET Framework and modern .NET (e.g., Mono.Posix)
6. **Format detection** - Use `ReaderFactory.Open()` for auto-detection, test with actual archive files

View File

@@ -4,17 +4,17 @@
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="JetBrains.Profiler.SelfApi" Version="2.5.14" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="10.0.0" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.21" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
</ItemGroup>

View File

@@ -22,16 +22,11 @@
| 7Zip (4) | LZMA, LZMA2, BZip2, PPMd, BCJ, BCJ2, Deflate | Decompress | SevenZipArchive | N/A | N/A |
1. SOLID Rars are only supported in the RarReader API.
2. Zip format supports pkware and WinzipAES encryption. However, encrypted LZMA is not supported. Zip64 reading/writing is supported but only with seekable streams as the Zip spec doesn't support Zip64 data in post data descriptors. Deflate64 is only supported for reading. See [Zip Format Notes](#zip-format-notes) for details on multi-volume archives and streaming behavior.
2. Zip format supports pkware and WinzipAES encryption. However, encrypted LZMA is not supported. Zip64 reading/writing is supported but only with seekable streams as the Zip spec doesn't support Zip64 data in post data descriptors. Deflate64 is only supported for reading.
3. The Tar format requires a file size in the header. If no size is specified to the TarWriter and the stream is not seekable, then an exception will be thrown.
4. The 7Zip format doesn't allow for reading as a forward-only stream so 7Zip is only supported through the Archive API
5. LZip has no support for extra data like the file name or timestamp. There is a default filename used when looking at the entry Key on the archive.
### Zip Format Notes
- Multi-volume/split ZIP archives require ZipArchive (seekable streams) as ZipReader cannot seek across volume files.
- ZipReader processes entries from LocalEntry headers (which include directory entries ending with `/`) and intentionally skips DirectoryEntry headers from the central directory, as they are redundant in streaming mode - all entry data comes from LocalEntry headers which ZipReader has already processed.
## Compression Streams
For those who want to directly compress/decompress bits. The single file formats are represented here as well. However, BZip2, LZip and XZ have no metadata (GZip has a little) so using them without something like a Tar file makes little sense.

View File

@@ -1,6 +1,6 @@
# SharpCompress
SharpCompress is a compression library in pure C# for .NET Framework 4.8, .NET 8.0 and .NET 10.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip, unzstd, unarc and unarj with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0 and NET 8.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip, unzstd, unarc and unarj with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream).

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bullseye" />

View File

@@ -1,7 +1,7 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"net8.0": {
"Bullseye": {
"type": "Direct",
"requested": "[6.0.0, )",

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"version": "8.0.100",
"rollForward": "latestFeature"
}
}

View File

@@ -161,11 +161,6 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtra
/// </summary>
public virtual bool IsSolid => false;
/// <summary>
/// Archive is ENCRYPTED (this means the Archive has password-protected files).
/// </summary>
public virtual bool IsEncrypted => false;
/// <summary>
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
/// </summary>

View File

@@ -3,10 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Readers;
@@ -135,10 +131,10 @@ public static class ArchiveFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return FindFactory<T>(stream, finfo.Name);
return FindFactory<T>(stream);
}
private static T FindFactory<T>(Stream stream, string? fileName = null)
private static T FindFactory<T>(Stream stream)
where T : IFactory
{
stream.NotNull(nameof(stream));
@@ -163,16 +159,6 @@ public static class ArchiveFactory
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
// Check if this is a compressed tar file (tar.bz2, tar.lz, etc.)
// These formats are supported by ReaderFactory but not by ArchiveFactory
var compressedTarMessage = TryGetCompressedTarMessage(stream, fileName);
if (compressedTarMessage != null)
{
throw new InvalidOperationException(compressedTarMessage);
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new InvalidOperationException(
@@ -262,111 +248,4 @@ public static class ArchiveFactory
}
public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory();
/// <summary>
/// Checks if the stream is a compressed tar file (tar.bz2, tar.lz, etc.) that should use ReaderFactory instead.
/// Returns an error message if detected, null otherwise.
/// </summary>
private static string? TryGetCompressedTarMessage(Stream stream, string? fileName)
{
var startPosition = stream.Position;
try
{
// Check if it's a BZip2 file
if (BZip2Stream.IsBZip2(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
// Try to decompress and check if it contains a tar archive
using var decompressed = new BZip2Stream(stream, CompressionMode.Decompress, true);
if (IsTarStream(decompressed))
{
return "This appears to be a tar.bz2 archive. The Archive API requires seekable streams, but decompression streams are not seekable. "
+ "Please use ReaderFactory.Open() instead for forward-only extraction, "
+ "or decompress the file first and then open the resulting tar file with ArchiveFactory.Open().";
}
return null;
}
stream.Seek(startPosition, SeekOrigin.Begin);
// Check if it's an LZip file
if (LZipStream.IsLZipFile(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
// Try to decompress and check if it contains a tar archive
using var decompressed = new LZipStream(stream, CompressionMode.Decompress);
if (IsTarStream(decompressed))
{
return "This appears to be a tar.lz archive. The Archive API requires seekable streams, but decompression streams are not seekable. "
+ "Please use ReaderFactory.Open() instead for forward-only extraction, "
+ "or decompress the file first and then open the resulting tar file with ArchiveFactory.Open().";
}
return null;
}
// Check file extension as a fallback for other compressed tar formats
if (fileName != null)
{
var lowerFileName = fileName.ToLowerInvariant();
if (
lowerFileName.EndsWith(".tar.bz2")
|| lowerFileName.EndsWith(".tbz")
|| lowerFileName.EndsWith(".tbz2")
|| lowerFileName.EndsWith(".tb2")
|| lowerFileName.EndsWith(".tz2")
|| lowerFileName.EndsWith(".tar.lz")
|| lowerFileName.EndsWith(".tar.xz")
|| lowerFileName.EndsWith(".txz")
|| lowerFileName.EndsWith(".tar.zst")
|| lowerFileName.EndsWith(".tar.zstd")
|| lowerFileName.EndsWith(".tzst")
|| lowerFileName.EndsWith(".tzstd")
|| lowerFileName.EndsWith(".tar.z")
|| lowerFileName.EndsWith(".tz")
|| lowerFileName.EndsWith(".taz")
)
{
return $"The file '{fileName}' appears to be a compressed tar archive. The Archive API requires seekable streams, but decompression streams are not seekable. "
+ "Please use ReaderFactory.Open() instead for forward-only extraction, "
+ "or decompress the file first and then open the resulting tar file with ArchiveFactory.Open().";
}
}
return null;
}
catch
{
// If we can't determine, just return null and let the normal error handling proceed
return null;
}
finally
{
try
{
stream.Seek(startPosition, SeekOrigin.Begin);
}
catch
{
// Ignore seek failures
}
}
}
/// <summary>
/// Checks if a stream contains a tar archive by trying to read a tar header.
/// </summary>
private static bool IsTarStream(Stream stream)
{
try
{
var tarHeader = new TarHeader(new ArchiveEncoding());
return tarHeader.Read(new BinaryReader(stream));
}
catch
{
return false;
}
}
}

View File

@@ -84,8 +84,6 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
public override bool IsSolid => Volumes.First().IsSolidArchive;
public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted;
public virtual int MinVersion => Volumes.First().MinVersion;
public virtual int MaxVersion => Volumes.First().MaxVersion;

View File

@@ -205,8 +205,6 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
.GroupBy(x => x.FilePart.Folder)
.Any(folder => folder.Count() > 1);
public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted;
public override long TotalSize =>
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;

View File

@@ -1,7 +1,6 @@
using System;
using System.Buffers.Binary;
using System.Security.Cryptography;
using System.Text;
namespace SharpCompress.Common.Zip;
@@ -20,24 +19,8 @@ internal class WinzipAesEncryptionData
{
_keySize = keySize;
#if NETFRAMEWORK
#if NETFRAMEWORK || NETSTANDARD2_0
var rfc2898 = new Rfc2898DeriveBytes(password, salt, RFC2898_ITERATIONS);
KeyBytes = rfc2898.GetBytes(KeySizeInBytes);
IvBytes = rfc2898.GetBytes(KeySizeInBytes);
var generatedVerifyValue = rfc2898.GetBytes(2);
#elif NET10_0_OR_GREATER
var derivedKeySize = (KeySizeInBytes * 2) + 2;
var passwordBytes = Encoding.UTF8.GetBytes(password);
var derivedKey = Rfc2898DeriveBytes.Pbkdf2(
passwordBytes,
salt,
RFC2898_ITERATIONS,
HashAlgorithmName.SHA1,
derivedKeySize
);
KeyBytes = derivedKey.AsSpan(0, KeySizeInBytes).ToArray();
IvBytes = derivedKey.AsSpan(KeySizeInBytes, KeySizeInBytes).ToArray();
var generatedVerifyValue = derivedKey.AsSpan((KeySizeInBytes * 2), 2).ToArray();
#else
var rfc2898 = new Rfc2898DeriveBytes(
password,
@@ -45,10 +28,11 @@ internal class WinzipAesEncryptionData
RFC2898_ITERATIONS,
HashAlgorithmName.SHA1
);
KeyBytes = rfc2898.GetBytes(KeySizeInBytes);
#endif
KeyBytes = rfc2898.GetBytes(KeySizeInBytes); // 16 or 24 or 32 ???
IvBytes = rfc2898.GetBytes(KeySizeInBytes);
var generatedVerifyValue = rfc2898.GetBytes(2);
#endif
var verify = BinaryPrimitives.ReadInt16LittleEndian(passwordVerifyValue);
var generated = BinaryPrimitives.ReadInt16LittleEndian(generatedVerifyValue);

View File

@@ -28,12 +28,19 @@ namespace SharpCompress.Factories
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var arjHeader = new ArjMainHeader(new ArchiveEncoding());
if (arjHeader.Read(stream) == null)
try
{
var arjHeader = new ArjMainHeader(new ArchiveEncoding());
if (arjHeader.Read(stream) == null)
{
return false;
}
return true;
}
catch
{
return false;
}
return true;
}
public IReader OpenReader(Stream stream, ReaderOptions? options) =>

View File

@@ -57,238 +57,19 @@ public class TarFactory
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
if (!stream.CanSeek)
{
return TarArchive.IsTarFile(stream); // For non-seekable streams, just check if it's a tar file
}
var startPosition = stream.Position;
// First check if it's a regular tar file
if (TarArchive.IsTarFile(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin); // Seek back for consistency
return true;
}
// Seek back after the tar file check
stream.Seek(startPosition, SeekOrigin.Begin);
if (compressionOptions == null)
{
return false;
}
try
{
// Try each compression option to see if it contains a tar file
foreach (var testOption in compressionOptions)
{
if (testOption.Type == CompressionType.None)
{
continue; // Skip uncompressed
}
stream.Seek(startPosition, SeekOrigin.Begin);
try
{
if (testOption.CanHandle(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
// Try to decompress and check if it contains a tar archive
// For compression formats that don't support leaveOpen, we need to save/restore position
var positionBeforeDecompress = stream.Position;
Stream? decompressedStream = null;
bool streamWasClosed = false;
try
{
decompressedStream = testOption.Type switch
{
CompressionType.BZip2 => new BZip2Stream(stream, CompressionMode.Decompress, true),
_ => testOption.CreateStream(stream) // For other types, may close the stream
};
if (TarArchive.IsTarFile(decompressedStream))
{
return true;
}
}
catch (ObjectDisposedException)
{
streamWasClosed = true;
throw; // Stream was closed, can't continue
}
finally
{
decompressedStream?.Dispose();
if (!streamWasClosed && stream.CanSeek)
{
try
{
stream.Seek(positionBeforeDecompress, SeekOrigin.Begin);
}
catch
{
// If seek fails, the stream might have been closed
}
}
}
// Seek back to start after decompression attempt
stream.Seek(startPosition, SeekOrigin.Begin);
}
}
catch
{
// If decompression fails, it's not this format - continue to next option
try
{
stream.Seek(startPosition, SeekOrigin.Begin);
}
catch
{
// Ignore seek failures
}
}
}
return false;
}
finally
{
try
{
stream.Seek(startPosition, SeekOrigin.Begin);
}
catch
{
// Ignore seek failures
}
}
}
) => TarArchive.IsTarFile(stream);
#endregion
#region IArchiveFactory
/// <inheritdoc/>
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
readerOptions ??= new ReaderOptions();
// Try to detect and handle compressed tar formats
if (stream.CanSeek)
{
var startPosition = stream.Position;
// Try each compression option to see if we can decompress it
foreach (var testOption in compressionOptions)
{
if (testOption.Type == CompressionType.None)
{
continue; // Skip uncompressed
}
stream.Seek(startPosition, SeekOrigin.Begin);
if (testOption.CanHandle(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
// Decompress the entire stream into a seekable MemoryStream
using var decompressedStream = testOption.CreateStream(stream);
var memoryStream = new MemoryStream();
decompressedStream.CopyTo(memoryStream);
memoryStream.Position = 0;
// Verify it's actually a tar file
if (TarArchive.IsTarFile(memoryStream))
{
memoryStream.Position = 0;
// Return a TarArchive from the decompressed memory stream
// The TarArchive will own the MemoryStream and dispose it when disposed
var options = new ReaderOptions
{
LeaveStreamOpen = false, // Ensure the MemoryStream is disposed with the archive
ArchiveEncoding = readerOptions?.ArchiveEncoding ?? new ArchiveEncoding()
};
return TarArchive.Open(memoryStream, options);
}
memoryStream.Dispose();
}
}
stream.Seek(startPosition, SeekOrigin.Begin);
}
// Fall back to normal tar archive opening
return TarArchive.Open(stream, readerOptions);
}
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
TarArchive.Open(stream, readerOptions);
/// <inheritdoc/>
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
readerOptions ??= new ReaderOptions();
// Try to detect and handle compressed tar formats by file extension and content
using var fileStream = fileInfo.OpenRead();
// Try each compression option
foreach (var testOption in compressionOptions)
{
if (testOption.Type == CompressionType.None)
{
continue; // Skip uncompressed
}
// Check if file extension matches
var fileName = fileInfo.Name.ToLowerInvariant();
if (testOption.KnownExtensions.Any(ext => fileName.EndsWith(ext)))
{
fileStream.Position = 0;
// Verify it's the right compression format
if (testOption.CanHandle(fileStream))
{
fileStream.Position = 0;
// Decompress the entire file into a seekable MemoryStream
using var decompressedStream = testOption.CreateStream(fileStream);
var memoryStream = new MemoryStream();
decompressedStream.CopyTo(memoryStream);
memoryStream.Position = 0;
// Verify it's actually a tar file
if (TarArchive.IsTarFile(memoryStream))
{
memoryStream.Position = 0;
// Return a TarArchive from the decompressed memory stream
// The TarArchive will own the MemoryStream and dispose it when disposed
var options = new ReaderOptions
{
LeaveStreamOpen = false, // Ensure the MemoryStream is disposed with the archive
ArchiveEncoding = readerOptions?.ArchiveEncoding ?? new ArchiveEncoding()
};
return TarArchive.Open(memoryStream, options);
}
memoryStream.Dispose();
}
}
}
// fileStream will be closed by the using statement
// Fall back to normal tar archive opening
return TarArchive.Open(fileInfo, readerOptions);
}
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
TarArchive.Open(fileInfo, readerOptions);
#endregion

View File

@@ -75,14 +75,6 @@ public class ZipReader : AbstractReader<ZipEntry, ZipVolume>
);
}
break;
case ZipHeaderType.DirectoryEntry:
// DirectoryEntry headers in the central directory are intentionally skipped.
// In streaming mode, we can only read forward, and DirectoryEntry headers
// reference LocalEntry headers that have already been processed. The file
// data comes from LocalEntry headers, not DirectoryEntry headers.
// For multi-volume ZIPs where file data spans multiple files, use ZipArchive
// instead, which requires seekable streams.
break;
case ZipHeaderType.DirectoryEnd:
{
yield break;

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<AssemblyTitle>SharpCompress - Pure C# Decompression/Compression</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.42.0</VersionPrefix>
<AssemblyVersion>0.42.0</AssemblyVersion>
<FileVersion>0.42.0</FileVersion>
<VersionPrefix>0.42.1</VersionPrefix>
<AssemblyVersion>0.42.1</AssemblyVersion>
<FileVersion>0.42.1</FileVersion>
<Authors>Adam Hathcock</Authors>
<TargetFrameworks>net48;net8.0;net10.0</TargetFrameworks>
<TargetFrameworks>net48;net481;netstandard2.0;net6.0;net8.0</TargetFrameworks>
<AssemblyName>SharpCompress</AssemblyName>
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
@@ -17,7 +17,7 @@
<Copyright>Copyright (c) 2025 Adam Hathcock</Copyright>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Description>SharpCompress is a compression library for NET 4.8/NET 8.0/NET 10.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented.</Description>
<Description>SharpCompress is a compression library for NET Standard 2.0/NET 4.8/NET 4.8.1/NET 6.0/NET 8.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented.</Description>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<DebugType>embedded</DebugType>
@@ -28,29 +28,31 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0' ">
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
<DefineConstants>$(DefineConstants);DEBUG_STREAMS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net10.0|AnyCPU'">
<DefineConstants>$(DefineConstants);DEBUG_STREAMS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ZstdSharp.Port" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'net481' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="System.Buffers" />
<PackageReference Include="System.Memory" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="System.Memory" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

View File

@@ -4,11 +4,11 @@
".NETFramework,Version=v4.8": {
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "vFuwSLj9QJBbNR0NeNO4YVASUbokxs+i/xbuu8B+Fs4FAZg5QaFa6eGrMaRqTzzNI5tAb97T7BhSxtLckFyiRA==",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
@@ -49,13 +49,12 @@
},
"System.Text.Encoding.CodePages": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "QLP54mIATaBpjGlsZIxga38VPk1G9js0Kw651B+bvrXi2kSgGZYrxJSpM3whhTZCBK4HEBHX3fzfDQMw7CXHGQ==",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
"dependencies": {
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
"System.ValueTuple": "4.6.1"
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"ZstdSharp.Port": {
@@ -96,25 +95,216 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
}
},
".NETFramework,Version=v4.8.1": {
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net481": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"System.Buffers": {
"type": "Direct",
"requested": "[4.6.1, )",
"resolved": "4.6.1",
"contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
},
"System.Memory": {
"type": "Direct",
"requested": "[4.6.3, )",
"resolved": "4.6.3",
"contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
"System.Buffers": "4.6.1",
"System.Numerics.Vectors": "4.6.1",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.ValueTuple": {
"System.Text.Encoding.CodePages": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"ZstdSharp.Port": {
"type": "Direct",
"requested": "[0.8.6, )",
"resolved": "0.8.6",
"contentHash": "iP4jVLQoQmUjMU88g1WObiNr6YKZGvh4aOXn3yOJsHqZsflwRsxZPcIBvNXgjXO3vQKSLctXGLTpcBPLnWPS8A==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net481": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "Vv/20vgHS7VglVOVh8J3Iz/MA+VYKVRp9f7r2qiKBMuzviTOmocG70yq0Q8T5OTmCONkEAIJwETD1zhEfLkAXQ=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.6.1",
"contentHash": "+RJT4qaekpZ7DDLhf+LTjq+E48jieKiY9ulJ+BoxKmZblIJfIJT8Ufcaa/clQqnYvWs8jugfGSMu8ylS0caG0w=="
"contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
}
},
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
".NETStandard,Version=v2.0": {
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"NETStandard.Library": {
"type": "Direct",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"System.Memory": {
"type": "Direct",
"requested": "[4.6.3, )",
"resolved": "4.6.3",
"contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
"System.Buffers": "4.6.1",
"System.Numerics.Vectors": "4.6.1",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Text.Encoding.CodePages": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"ZstdSharp.Port": {
"type": "Direct",
"requested": "[0.8.6, )",
"resolved": "0.8.6",
"contentHash": "iP4jVLQoQmUjMU88g1WObiNr6YKZGvh4aOXn3yOJsHqZsflwRsxZPcIBvNXgjXO3vQKSLctXGLTpcBPLnWPS8A==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.6.1",
"contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Buffers": {
"type": "CentralTransitive",
"requested": "[4.6.1, )",
"resolved": "4.6.1",
"contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
}
},
"net6.0": {
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
@@ -145,9 +335,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
"requested": "[8.0.21, )",
"resolved": "8.0.21",
"contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Profiler.SelfApi" />

View File

@@ -1,7 +1,7 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"net8.0": {
"JetBrains.Profiler.SelfApi": {
"type": "Direct",
"requested": "[2.5.14, )",

View File

@@ -1,63 +0,0 @@
using System;
using System.IO;
using SharpCompress.Archives;
using Xunit;
namespace SharpCompress.Test;
public class ArchiveFactoryCompressedTarTests : TestBase
{
[Fact]
public void ArchiveFactory_Open_TarBz2_ThrowsHelpfulException()
{
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2");
var exception = Assert.Throws<InvalidOperationException>(() =>
{
using var archive = ArchiveFactory.Open(testFile);
});
Assert.Contains("tar.bz2", exception.Message);
Assert.Contains("ReaderFactory", exception.Message);
}
[Fact]
public void ArchiveFactory_Open_TarLz_ThrowsHelpfulException()
{
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.lz");
var exception = Assert.Throws<InvalidOperationException>(() =>
{
using var archive = ArchiveFactory.Open(testFile);
});
Assert.Contains("tar.lz", exception.Message);
Assert.Contains("ReaderFactory", exception.Message);
}
[Fact]
public void ArchiveFactory_Open_TarBz2Stream_ThrowsHelpfulException()
{
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2");
using var stream = File.OpenRead(testFile);
var exception = Assert.Throws<InvalidOperationException>(() =>
{
using var archive = ArchiveFactory.Open(stream);
});
Assert.Contains("tar.bz2", exception.Message);
Assert.Contains("ReaderFactory", exception.Message);
}
[Fact]
public void ArchiveFactory_Open_TarLzStream_ThrowsHelpfulException()
{
var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.lz");
using var stream = File.OpenRead(testFile);
var exception = Assert.Throws<InvalidOperationException>(() =>
{
using var archive = ArchiveFactory.Open(stream);
});
Assert.Contains("tar.lz", exception.Message);
Assert.Contains("ReaderFactory", exception.Message);
}
}

View File

@@ -654,4 +654,31 @@ public class ArchiveTests : ReaderTests
Assert.Equal(3, archive.Entries.Count());
}
}
[Fact]
public void ArchiveFactory_IsArchive_NonArchiveFile_ShouldReturnFalse()
{
// Test that ArchiveFactory.IsArchive returns false instead of throwing
// when called on a non-archive file (regression test for issue #1064)
using (var stream = new MemoryStream(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }))
{
var result = ArchiveFactory.IsArchive(stream, out var type);
Assert.False(result);
Assert.Null(type);
}
}
[Fact]
public void ArchiveFactory_IsArchive_ValidArchive_ShouldReturnTrue()
{
// Test that ArchiveFactory.IsArchive correctly identifies valid archives
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip");
using (var stream = File.OpenRead(testArchive))
{
var result = ArchiveFactory.IsArchive(stream, out var type);
Assert.True(result);
Assert.Equal(ArchiveType.Zip, type);
}
}
}

View File

@@ -633,13 +633,4 @@ public class RarArchiveTests : ArchiveTests
"Rar5.encrypted_filesOnly.rar",
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"
);
[Fact]
public void Rar_TestEncryptedDetection()
{
using var passwordProtectedFilesArchive = RarArchive.Open(
Path.Combine(TEST_ARCHIVES_PATH, "Rar.encrypted_filesOnly.rar")
);
Assert.True(passwordProtectedFilesArchive.IsEncrypted);
}
}

View File

@@ -224,15 +224,6 @@ public class SevenZipArchiveTests : ArchiveTests
);
}
[Fact]
public void SevenZipArchive_TestEncryptedDetection()
{
using var passwordProtectedFilesArchive = SevenZipArchive.Open(
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.encryptedFiles.7z")
);
Assert.True(passwordProtectedFilesArchive.IsEncrypted);
}
[Fact]
public void SevenZipArchive_TestSolidDetection()
{

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0;net48</TargetFrameworks>
<TargetFrameworks>net8.0;net48</TargetFrameworks>
<AssemblyName>SharpCompress.Test</AssemblyName>
<PackageId>SharpCompress.Test</PackageId>
<AssemblyOriginatorKeyFile>SharpCompress.Test.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net10.0|AnyCPU'">
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
<DefineConstants>$(DefineConstants);DEBUG_STREAMS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))">

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Archives;
using SharpCompress.Common;
using SharpCompress.IO;
@@ -399,41 +397,4 @@ public class ZipReaderTests : ReaderTests
Assert.Equal("second.txt", reader.Entry.Key);
Assert.Equal(197, reader.Entry.Size);
}
[Fact]
public void ZipReader_Returns_Same_Entries_As_ZipArchive()
{
// Verifies that ZipReader and ZipArchive return the same entries
// for standard single-volume ZIP files. ZipReader processes LocalEntry
// headers sequentially, while ZipArchive uses DirectoryEntry headers
// from the central directory and seeks to LocalEntry headers for data.
var testFiles = new[] { "Zip.none.zip", "Zip.deflate.zip", "Zip.none.issue86.zip" };
foreach (var testFile in testFiles)
{
var path = Path.Combine(TEST_ARCHIVES_PATH, testFile);
var readerKeys = new List<string>();
using (var stream = File.OpenRead(path))
using (var reader = ZipReader.Open(stream))
{
while (reader.MoveToNextEntry())
{
readerKeys.Add(reader.Entry.Key!);
}
}
var archiveKeys = new List<string>();
using (var archive = Archives.Zip.ZipArchive.Open(path))
{
foreach (var entry in archive.Entries)
{
archiveKeys.Add(entry.Key!);
}
}
Assert.Equal(archiveKeys.Count, readerKeys.Count);
Assert.Equal(archiveKeys.OrderBy(k => k), readerKeys.OrderBy(k => k));
}
}
}

View File

@@ -13,11 +13,11 @@
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[18.0.1, )",
"resolved": "18.0.1",
"contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==",
"requested": "[18.0.0, )",
"resolved": "18.0.0",
"contentHash": "bvxj2Asb7nT+tqOFFerrhQeEjUYLwx0Poi0Rznu63WbqN+A4uDn1t5NWXfAOOQsF6lpmK6N2v+Vvgso7KWZS7g==",
"dependencies": {
"Microsoft.CodeCoverage": "18.0.1"
"Microsoft.CodeCoverage": "18.0.0"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
@@ -29,12 +29,6 @@
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
}
},
"Mono.Posix.NETStandard": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "vSN/L1uaVwKsiLa95bYu2SGkF0iY3xMblTfxc8alSziPuVfJpj3geVqHGAA75J7cZkMuKpFVikz82Lo6y6LLdA=="
},
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
@@ -57,8 +51,8 @@
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "18.0.1",
"contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA=="
"resolved": "18.0.0",
"contentHash": "DFPhMrsIofgJ1DDU3ModqqRArDm15/bNl4ecmcuBspZkZ4ONYnCC0R8U27WzK7cYv6r8l6Q/fRmvg7cb+I/dJA=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net48": {
"type": "Transitive",
@@ -98,17 +92,12 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.6.1",
"contentHash": "+RJT4qaekpZ7DDLhf+LTjq+E48jieKiY9ulJ+BoxKmZblIJfIJT8Ufcaa/clQqnYvWs8jugfGSMu8ylS0caG0w=="
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
@@ -152,20 +141,20 @@
"sharpcompress": {
"type": "Project",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "[10.0.0, )",
"Microsoft.Bcl.AsyncInterfaces": "[8.0.0, )",
"System.Buffers": "[4.6.1, )",
"System.Memory": "[4.6.3, )",
"System.Text.Encoding.CodePages": "[10.0.0, )",
"System.Text.Encoding.CodePages": "[8.0.0, )",
"ZstdSharp.Port": "[0.8.6, )"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "vFuwSLj9QJBbNR0NeNO4YVASUbokxs+i/xbuu8B+Fs4FAZg5QaFa6eGrMaRqTzzNI5tAb97T7BhSxtLckFyiRA==",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Buffers": {
@@ -187,13 +176,12 @@
},
"System.Text.Encoding.CodePages": {
"type": "CentralTransitive",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "QLP54mIATaBpjGlsZIxga38VPk1G9js0Kw651B+bvrXi2kSgGZYrxJSpM3whhTZCBK4HEBHX3fzfDQMw7CXHGQ==",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
"dependencies": {
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
"System.ValueTuple": "4.6.1"
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"ZstdSharp.Port": {
@@ -208,7 +196,7 @@
}
}
},
"net10.0": {
"net8.0": {
"AwesomeAssertions": {
"type": "Direct",
"requested": "[9.3.0, )",
@@ -217,12 +205,12 @@
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[18.0.1, )",
"resolved": "18.0.1",
"contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==",
"requested": "[18.0.0, )",
"resolved": "18.0.0",
"contentHash": "bvxj2Asb7nT+tqOFFerrhQeEjUYLwx0Poi0Rznu63WbqN+A4uDn1t5NWXfAOOQsF6lpmK6N2v+Vvgso7KWZS7g==",
"dependencies": {
"Microsoft.CodeCoverage": "18.0.1",
"Microsoft.TestPlatform.TestHost": "18.0.1"
"Microsoft.CodeCoverage": "18.0.0",
"Microsoft.TestPlatform.TestHost": "18.0.0"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
@@ -259,8 +247,8 @@
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "18.0.1",
"contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA=="
"resolved": "18.0.0",
"contentHash": "DFPhMrsIofgJ1DDU3ModqqRArDm15/bNl4ecmcuBspZkZ4ONYnCC0R8U27WzK7cYv6r8l6Q/fRmvg7cb+I/dJA=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
@@ -269,18 +257,18 @@
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "18.0.1",
"contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==",
"resolved": "18.0.0",
"contentHash": "Al/a99ymb8UdEEh6DKNiaoFn5i8fvX5PdM9LfU9Z/Q8NJrlyHHzF+LRHLbR+t89gRsJ2fFMpwYxgEn3eH1BQwA==",
"dependencies": {
"System.Reflection.Metadata": "8.0.0"
}
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "18.0.1",
"contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==",
"resolved": "18.0.0",
"contentHash": "aAxE8Thr9ZHGrljOYaDeLJqitQi75iE4xeEFn6CEGFirlHSn1KwpKPniuEn6zCLZ90Z3XqNlrC3ZJTuvBov45w==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "18.0.1",
"Microsoft.TestPlatform.ObjectModel": "18.0.0",
"Newtonsoft.Json": "13.0.3"
}
},