Compare commits

...

18 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
e95e996d17 Remove unused variable from test
Address code review feedback: remove unused testFile variable

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2025-12-08 09:26:28 +00:00
copilot-swe-agent[bot]
bfa0e4a808 Fix ArjFactory.IsArchive to not throw on non-archive files
- Wrap ArjMainHeader.Read call in try-catch block
- Return false on InvalidDataException instead of propagating
- Add tests for ArchiveFactory.IsArchive with non-archive files
- Fixes regression in 0.42.0 where IsArchive threw exceptions

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2025-12-08 09:18:11 +00:00
copilot-swe-agent[bot]
3752462c70 Initial plan 2025-12-08 09:09:59 +00:00
Adam Hathcock
2a3086a0d7 Merge pull request #1060 from HeroponRikiBestest/rar-7z-password 2025-12-03 19:58:47 +00:00
HeroponRikIBestest
41c3cc1a18 Csharpier 2025-12-03 12:05:16 -05:00
HeroponRikIBestest
1b1df86a11 Improve logic 2025-12-02 10:02:49 -05:00
HeroponRikIBestest
e0660e7775 Add tests 2025-12-02 09:55:24 -05:00
HeroponRikIBestest
99a6c4de88 Add archive-level IsEncrypted flag 2025-12-02 09:47:06 -05:00
Adam Hathcock
ffa765bd97 Merge pull request #1057 from adamhathcock/adam/add-copilot-instructions 2025-11-30 13:48:55 +00:00
Adam Hathcock
b1696524b3 Merge pull request #1058 from adamhathcock/copilot/sub-pr-1057 2025-11-30 13:48:31 +00:00
copilot-swe-agent[bot]
6a37c55085 Consolidate agent instructions into AGENTS.md
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2025-11-30 12:52:22 +00:00
copilot-swe-agent[bot]
9c1c6fff9f Initial plan 2025-11-30 12:49:36 +00:00
Adam Hathcock
db8c6f4bcb first pass of instructions...consolidate? 2025-11-30 12:47:57 +00:00
Adam Hathcock
ff17ecda7d Merge pull request #1055 from adamhathcock/adam/vscode-fixes
add vscode config
2025-11-30 12:47:16 +00:00
Adam Hathcock
692058677c Merge pull request #1056 from adamhathcock/copilot/sub-pr-1055
Fix launch.json debug configurations to use net10.0
2025-11-30 12:41:38 +00:00
copilot-swe-agent[bot]
1e90d69912 Update launch.json to use net10.0 instead of net8.0
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2025-11-30 12:22:31 +00:00
copilot-swe-agent[bot]
64a1cc68e1 Initial plan 2025-11-30 12:20:37 +00:00
Adam Hathcock
20353f35ff add vscode config 2025-11-30 12:05:08 +00:00
14 changed files with 416 additions and 4 deletions

1
.gitignore vendored
View File

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

9
.vscode/extensions.json vendored Normal file
View File

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

97
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,97 @@
{
"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 Normal file
View File

@@ -0,0 +1,29 @@
{
"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 Normal file
View File

@@ -0,0 +1,178 @@
{
"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,6 +49,30 @@ 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.
@@ -116,3 +140,18 @@ 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

@@ -161,6 +161,11 @@ 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

@@ -84,6 +84,8 @@ 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,6 +205,8 @@ 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

@@ -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

@@ -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 #1060)
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,4 +633,13 @@ 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,6 +224,15 @@ 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()
{

Binary file not shown.