From 1993673a22f6edffed3139793b014f9843bc0ee1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 10:42:07 +0000 Subject: [PATCH 01/25] Initial plan From 131bd2b7b88b9d4ec44f6470011a4cf5c6556926 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:03:48 +0000 Subject: [PATCH 02/25] Drop .NET 6 support and add .NET 10 support - Updated SharpCompress.csproj target frameworks from net48;net481;netstandard2.0;net6.0;net8.0 to net48;net481;netstandard2.0;net8.0;net10.0 - Updated test and build projects to use .NET 10 - Updated global.json to .NET 10 SDK - Updated CI workflow to use .NET 10 - Fixed deprecated Rfc2898DeriveBytes constructor for .NET 10 (SYSLIB0060) - Updated package description and README to reflect new supported frameworks - Updated package versions for .NET 10 compatibility Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .github/workflows/dotnetcore.yml | 2 +- Directory.Packages.props | 2 +- README.md | 2 +- build/build.csproj | 2 +- build/packages.lock.json | 2 +- global.json | 2 +- .../Common/Zip/WinzipAesEncryptionData.cs | 22 +++++++++++++--- src/SharpCompress/SharpCompress.csproj | 11 +++++--- src/SharpCompress/packages.lock.json | 14 +++++++--- .../SharpCompress.Performance.csproj | 2 +- .../packages.lock.json | 2 +- .../SharpCompress.Test.csproj | 4 +-- tests/SharpCompress.Test/packages.lock.json | 26 ++++++------------- 13 files changed, 54 insertions(+), 39 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index fa268a67..3f6bef75 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - run: dotnet run --project build/build.csproj - uses: actions/upload-artifact@v5 with: diff --git a/Directory.Packages.props b/Directory.Packages.props index 216d1de4..cdce4631 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,7 +14,7 @@ - + diff --git a/README.md b/README.md index f333d497..6cf8505b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SharpCompress -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. +SharpCompress is a compression library in pure C# for .NET Framework 4.8/4.8.1, .NET Standard 2.0, .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. The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream). diff --git a/build/build.csproj b/build/build.csproj index 99e86377..8f5b6f3c 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -1,7 +1,7 @@ Exe - net8.0 + net10.0 diff --git a/build/packages.lock.json b/build/packages.lock.json index bc1f7953..2719488d 100644 --- a/build/packages.lock.json +++ b/build/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "Bullseye": { "type": "Direct", "requested": "[6.0.0, )", diff --git a/global.json b/global.json index 391ba3c2..512142d2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "10.0.100", "rollForward": "latestFeature" } } diff --git a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs index 31322019..da37501b 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs @@ -1,6 +1,7 @@ using System; using System.Buffers.Binary; using System.Security.Cryptography; +using System.Text; namespace SharpCompress.Common.Zip; @@ -21,6 +22,22 @@ internal class WinzipAesEncryptionData #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, @@ -28,11 +45,10 @@ internal class WinzipAesEncryptionData RFC2898_ITERATIONS, HashAlgorithmName.SHA1 ); -#endif - - KeyBytes = rfc2898.GetBytes(KeySizeInBytes); // 16 or 24 or 32 ??? + KeyBytes = rfc2898.GetBytes(KeySizeInBytes); IvBytes = rfc2898.GetBytes(KeySizeInBytes); var generatedVerifyValue = rfc2898.GetBytes(2); +#endif var verify = BinaryPrimitives.ReadInt16LittleEndian(passwordVerifyValue); var generated = BinaryPrimitives.ReadInt16LittleEndian(generatedVerifyValue); diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 27ec5e3e..9c7efdf1 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -6,7 +6,7 @@ 0.42.0 0.42.0 Adam Hathcock - net48;net481;netstandard2.0;net6.0;net8.0 + net48;net481;netstandard2.0;net8.0;net10.0 SharpCompress ../../SharpCompress.snk true @@ -17,7 +17,7 @@ Copyright (c) 2025 Adam Hathcock false false - 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. + SharpCompress is a compression library for NET Standard 2.0/NET 4.8/NET 4.8.1/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. true true embedded @@ -28,17 +28,20 @@ true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - + true $(DefineConstants);DEBUG_STREAMS + + $(DefineConstants);DEBUG_STREAMS + - + diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 3d0d443d..cfaec8f5 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -304,7 +304,13 @@ "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" } }, - "net6.0": { + "net10.0": { + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -335,9 +341,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.21, )", - "resolved": "8.0.21", - "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/tests/SharpCompress.Performance/SharpCompress.Performance.csproj b/tests/SharpCompress.Performance/SharpCompress.Performance.csproj index 460b1d53..cab757e4 100644 --- a/tests/SharpCompress.Performance/SharpCompress.Performance.csproj +++ b/tests/SharpCompress.Performance/SharpCompress.Performance.csproj @@ -1,7 +1,7 @@  Exe - net8.0 + net10.0 diff --git a/tests/SharpCompress.Performance/packages.lock.json b/tests/SharpCompress.Performance/packages.lock.json index 5c6a2b9e..df535fe2 100644 --- a/tests/SharpCompress.Performance/packages.lock.json +++ b/tests/SharpCompress.Performance/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "JetBrains.Profiler.SelfApi": { "type": "Direct", "requested": "[2.5.14, )", diff --git a/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj index 916f67b6..6ee632bd 100644 --- a/tests/SharpCompress.Test/SharpCompress.Test.csproj +++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj @@ -1,12 +1,12 @@  - net8.0;net48 + net10.0;net48 SharpCompress.Test SharpCompress.Test SharpCompress.Test.snk true - + $(DefineConstants);DEBUG_STREAMS diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index 90c5ba11..fbc620a5 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -29,6 +29,12 @@ "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, )", @@ -196,7 +202,7 @@ } } }, - "net8.0": { + "net10.0": { "AwesomeAssertions": { "type": "Direct", "requested": "[9.3.0, )", @@ -258,10 +264,7 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.0.0", - "contentHash": "Al/a99ymb8UdEEh6DKNiaoFn5i8fvX5PdM9LfU9Z/Q8NJrlyHHzF+LRHLbR+t89gRsJ2fFMpwYxgEn3eH1BQwA==", - "dependencies": { - "System.Reflection.Metadata": "8.0.0" - } + "contentHash": "Al/a99ymb8UdEEh6DKNiaoFn5i8fvX5PdM9LfU9Z/Q8NJrlyHHzF+LRHLbR+t89gRsJ2fFMpwYxgEn3eH1BQwA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -277,19 +280,6 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" - }, - "System.Reflection.Metadata": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", - "dependencies": { - "System.Collections.Immutable": "8.0.0" - } - }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", From 8af47548fe7ce8a3c08fe1e3dd3bffeff9826d6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:04:54 +0000 Subject: [PATCH 03/25] Fix .NET 10.0 formatting in README Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cf8505b..e116d728 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SharpCompress -SharpCompress is a compression library in pure C# for .NET Framework 4.8/4.8.1, .NET Standard 2.0, .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.8/4.8.1, .NET Standard 2.0, .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. The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream). From 5aa216bd218b786f351430afb043795eb9e36ac0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:42:05 +0000 Subject: [PATCH 04/25] Remove netstandard2.0 and net481 targets, keep net48, net8.0, net10.0 - Updated target frameworks to net48;net8.0;net10.0 only - Removed netstandard2.0 and net481 package conditions - Updated conditional compilation to use NETFRAMEWORK only - Updated package description and README Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- README.md | 2 +- .../Common/Zip/WinzipAesEncryptionData.cs | 2 +- src/SharpCompress/SharpCompress.csproj | 11 +- src/SharpCompress/packages.lock.json | 202 ------------------ 4 files changed, 5 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index e116d728..172003c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SharpCompress -SharpCompress is a compression library in pure C# for .NET Framework 4.8/4.8.1, .NET Standard 2.0, .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.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. The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream). diff --git a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs index da37501b..b91d7291 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs @@ -20,7 +20,7 @@ internal class WinzipAesEncryptionData { _keySize = keySize; -#if NETFRAMEWORK || NETSTANDARD2_0 +#if NETFRAMEWORK var rfc2898 = new Rfc2898DeriveBytes(password, salt, RFC2898_ITERATIONS); KeyBytes = rfc2898.GetBytes(KeySizeInBytes); IvBytes = rfc2898.GetBytes(KeySizeInBytes); diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 9c7efdf1..fe39bc38 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -6,7 +6,7 @@ 0.42.0 0.42.0 Adam Hathcock - net48;net481;netstandard2.0;net8.0;net10.0 + net48;net8.0;net10.0 SharpCompress ../../SharpCompress.snk true @@ -17,7 +17,7 @@ Copyright (c) 2025 Adam Hathcock false false - SharpCompress is a compression library for NET Standard 2.0/NET 4.8/NET 4.8.1/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. + 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. true true embedded @@ -44,18 +44,13 @@ - + - - - - - diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index cfaec8f5..b3ba340b 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -102,208 +102,6 @@ } } }, - ".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.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": "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" - } - } - }, - ".NETStandard,Version=v2.0": { - "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.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==" - } - }, "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", From d34a47c148f0fc02d68c24a46cec7a4c02a51fb9 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 29 Nov 2025 11:56:20 +0000 Subject: [PATCH 05/25] update dependencies --- Directory.Packages.props | 6 +- src/SharpCompress/packages.lock.json | 30 ++++--- tests/SharpCompress.Test/packages.lock.json | 86 +++++++++++++-------- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cdce4631..e6ec03c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,13 +4,13 @@ - - + + - + diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index b3ba340b..82a5df3d 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -4,11 +4,11 @@ ".NETFramework,Version=v4.8": { "Microsoft.Bcl.AsyncInterfaces": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "vFuwSLj9QJBbNR0NeNO4YVASUbokxs+i/xbuu8B+Fs4FAZg5QaFa6eGrMaRqTzzNI5tAb97T7BhSxtLckFyiRA==", "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.NETFramework.ReferenceAssemblies": { @@ -49,12 +49,13 @@ }, "System.Text.Encoding.CodePages": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "QLP54mIATaBpjGlsZIxga38VPk1G9js0Kw651B+bvrXi2kSgGZYrxJSpM3whhTZCBK4HEBHX3fzfDQMw7CXHGQ==", "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", + "System.ValueTuple": "4.6.1" } }, "ZstdSharp.Port": { @@ -95,11 +96,16 @@ }, "System.Threading.Tasks.Extensions": { "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "+RJT4qaekpZ7DDLhf+LTjq+E48jieKiY9ulJ+BoxKmZblIJfIJT8Ufcaa/clQqnYvWs8jugfGSMu8ylS0caG0w==" } }, "net10.0": { diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index fbc620a5..8e2236f2 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -13,11 +13,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[18.0.0, )", - "resolved": "18.0.0", - "contentHash": "bvxj2Asb7nT+tqOFFerrhQeEjUYLwx0Poi0Rznu63WbqN+A4uDn1t5NWXfAOOQsF6lpmK6N2v+Vvgso7KWZS7g==", + "requested": "[18.0.1, )", + "resolved": "18.0.1", + "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.0" + "Microsoft.CodeCoverage": "18.0.1" } }, "Microsoft.NETFramework.ReferenceAssemblies": { @@ -57,8 +57,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.0", - "contentHash": "DFPhMrsIofgJ1DDU3ModqqRArDm15/bNl4ecmcuBspZkZ4ONYnCC0R8U27WzK7cYv6r8l6Q/fRmvg7cb+I/dJA==" + "resolved": "18.0.1", + "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" }, "Microsoft.NETFramework.ReferenceAssemblies.net48": { "type": "Transitive", @@ -98,12 +98,17 @@ }, "System.Threading.Tasks.Extensions": { "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "+RJT4qaekpZ7DDLhf+LTjq+E48jieKiY9ulJ+BoxKmZblIJfIJT8Ufcaa/clQqnYvWs8jugfGSMu8ylS0caG0w==" + }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", @@ -147,20 +152,20 @@ "sharpcompress": { "type": "Project", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "[8.0.0, )", + "Microsoft.Bcl.AsyncInterfaces": "[10.0.0, )", "System.Buffers": "[4.6.1, )", "System.Memory": "[4.6.3, )", - "System.Text.Encoding.CodePages": "[8.0.0, )", + "System.Text.Encoding.CodePages": "[10.0.0, )", "ZstdSharp.Port": "[0.8.6, )" } }, "Microsoft.Bcl.AsyncInterfaces": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "vFuwSLj9QJBbNR0NeNO4YVASUbokxs+i/xbuu8B+Fs4FAZg5QaFa6eGrMaRqTzzNI5tAb97T7BhSxtLckFyiRA==", "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "System.Buffers": { @@ -182,12 +187,13 @@ }, "System.Text.Encoding.CodePages": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "QLP54mIATaBpjGlsZIxga38VPk1G9js0Kw651B+bvrXi2kSgGZYrxJSpM3whhTZCBK4HEBHX3fzfDQMw7CXHGQ==", "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", + "System.ValueTuple": "4.6.1" } }, "ZstdSharp.Port": { @@ -211,12 +217,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[18.0.0, )", - "resolved": "18.0.0", - "contentHash": "bvxj2Asb7nT+tqOFFerrhQeEjUYLwx0Poi0Rznu63WbqN+A4uDn1t5NWXfAOOQsF6lpmK6N2v+Vvgso7KWZS7g==", + "requested": "[18.0.1, )", + "resolved": "18.0.1", + "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.0", - "Microsoft.TestPlatform.TestHost": "18.0.0" + "Microsoft.CodeCoverage": "18.0.1", + "Microsoft.TestPlatform.TestHost": "18.0.1" } }, "Microsoft.NETFramework.ReferenceAssemblies": { @@ -253,8 +259,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.0", - "contentHash": "DFPhMrsIofgJ1DDU3ModqqRArDm15/bNl4ecmcuBspZkZ4ONYnCC0R8U27WzK7cYv6r8l6Q/fRmvg7cb+I/dJA==" + "resolved": "18.0.1", + "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" }, "Microsoft.NETFramework.ReferenceAssemblies.net461": { "type": "Transitive", @@ -263,15 +269,18 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.0", - "contentHash": "Al/a99ymb8UdEEh6DKNiaoFn5i8fvX5PdM9LfU9Z/Q8NJrlyHHzF+LRHLbR+t89gRsJ2fFMpwYxgEn3eH1BQwA==" + "resolved": "18.0.1", + "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.0", - "contentHash": "aAxE8Thr9ZHGrljOYaDeLJqitQi75iE4xeEFn6CEGFirlHSn1KwpKPniuEn6zCLZ90Z3XqNlrC3ZJTuvBov45w==", + "resolved": "18.0.1", + "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.0", + "Microsoft.TestPlatform.ObjectModel": "18.0.1", "Newtonsoft.Json": "13.0.3" } }, @@ -280,6 +289,19 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", From 0d9d82d7e6b125f4e8c7dba3c329c9fdcc09b9f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:42:43 +0000 Subject: [PATCH 06/25] Initial plan From f4dddcec8ec4fa3e6e23e6499dbf41e70a9c87c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:06:03 +0000 Subject: [PATCH 07/25] Changes before error encountered Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Readers/Zip/ZipReader.cs | 6 +++ .../SharpCompress.Test/Zip/ZipReaderTests.cs | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index 5e82479c..65566f8e 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -75,6 +75,12 @@ public class ZipReader : AbstractReader ); } break; + // 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. case ZipHeaderType.DirectoryEnd: { yield break; diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index c48d98f4..8373718d 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using SharpCompress.Archives; using SharpCompress.Common; using SharpCompress.IO; @@ -397,4 +399,41 @@ 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. Both process entries from + // LocalEntry headers, while ZipArchive also reads DirectoryEntry + // headers from the central directory. + 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(); + using (var stream = File.OpenRead(path)) + using (var reader = ZipReader.Open(stream)) + { + while (reader.MoveToNextEntry()) + { + readerKeys.Add(reader.Entry.Key!); + } + } + + var archiveKeys = new List(); + 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)); + } + } } From a887390c23ccf03d197232d331830f2b6c6533d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:25:35 +0000 Subject: [PATCH 08/25] Add multi-volume ZIP documentation to FORMATS.md Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- FORMATS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FORMATS.md b/FORMATS.md index 473d8eb9..62aeeb24 100644 --- a/FORMATS.md +++ b/FORMATS.md @@ -22,7 +22,7 @@ | 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. +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. Multi-volume/split ZIP archives require ZipArchive (seekable streams) as ZipReader cannot seek across volume files. 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. From 7f911c5219b8c3365f63e1478d2c5a5fe2503a39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:29:07 +0000 Subject: [PATCH 09/25] Add documentation about ZipReader directory and central directory handling Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- FORMATS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FORMATS.md b/FORMATS.md index 62aeeb24..093cef4e 100644 --- a/FORMATS.md +++ b/FORMATS.md @@ -22,7 +22,7 @@ | 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. Multi-volume/split ZIP archives require ZipArchive (seekable streams) as ZipReader cannot seek across volume files. +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. 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. 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. From 840e58fc03ce14ac644cd253a276846c0f0eac73 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 29 Nov 2025 15:36:46 +0000 Subject: [PATCH 10/25] Update tests/SharpCompress.Test/Zip/ZipReaderTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/SharpCompress.Test/Zip/ZipReaderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 8373718d..fc87dc57 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -404,9 +404,9 @@ public class ZipReaderTests : ReaderTests public void ZipReader_Returns_Same_Entries_As_ZipArchive() { // Verifies that ZipReader and ZipArchive return the same entries - // for standard single-volume ZIP files. Both process entries from - // LocalEntry headers, while ZipArchive also reads DirectoryEntry - // headers from the central directory. + // 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) From c5da4167648248b053428ae0d24ce39f600f0430 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:39:36 +0000 Subject: [PATCH 11/25] Apply code review feedback: improve documentation structure and add explicit DirectoryEntry case Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- FORMATS.md | 7 ++++++- src/SharpCompress/Readers/Zip/ZipReader.cs | 14 ++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/FORMATS.md b/FORMATS.md index 093cef4e..7350b25e 100644 --- a/FORMATS.md +++ b/FORMATS.md @@ -22,11 +22,16 @@ | 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. 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. +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. 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. diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index 65566f8e..3a257845 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -75,12 +75,14 @@ public class ZipReader : AbstractReader ); } break; - // 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. + 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; From 20353f35ff8baf564084d7a31a8d4cfbbc4ec519 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 30 Nov 2025 12:05:08 +0000 Subject: [PATCH 12/25] add vscode config --- .gitignore | 1 - .vscode/extensions.json | 9 ++ .vscode/launch.json | 97 ++++++++++++++++++++++ .vscode/settings.json | 29 +++++++ .vscode/tasks.json | 178 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index f101b3b1..42a5e999 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ tests/TestArchives/*/Scratch tests/TestArchives/*/Scratch2 .vs tools -.vscode .idea/ .DS_Store diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..0c9d8a37 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", + "ms-dotnettools.vscode-dotnet-runtime", + "csharpier.csharpier-vscode", + "formulahendry.dotnet-test-explorer" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7bc14714 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,97 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Tests (net8.0)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "dotnet", + "args": [ + "test", + "${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj", + "-f", + "net8.0", + "--no-build", + "--verbosity=normal" + ], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Debug Specific Test (net8.0)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "dotnet", + "args": [ + "test", + "${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj", + "-f", + "net8.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" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..07998539 --- /dev/null +++ b/.vscode/settings.json @@ -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" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..9f59e69d --- /dev/null +++ b/.vscode/tasks.json @@ -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" + } + ] +} From 64a1cc68e1c7adf00947a9c31fe32f65bff4dabe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:20:37 +0000 Subject: [PATCH 13/25] Initial plan From 1e90d69912b2d0dd0fac4a6816622ed2b2381039 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:22:31 +0000 Subject: [PATCH 14/25] Update launch.json to use net10.0 instead of net8.0 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .vscode/launch.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7bc14714..8171a42b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Debug Tests (net8.0)", + "name": "Debug Tests (net10.0)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", @@ -11,7 +11,7 @@ "test", "${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj", "-f", - "net8.0", + "net10.0", "--no-build", "--verbosity=normal" ], @@ -20,7 +20,7 @@ "stopAtEntry": false }, { - "name": "Debug Specific Test (net8.0)", + "name": "Debug Specific Test (net10.0)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", @@ -29,7 +29,7 @@ "test", "${workspaceFolder}/tests/SharpCompress.Test/SharpCompress.Test.csproj", "-f", - "net8.0", + "net10.0", "--no-build", "--filter", "FullyQualifiedName~${input:testName}" From db8c6f4bcb6016ebff90c9937c8b21ae0be7d510 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 30 Nov 2025 12:47:57 +0000 Subject: [PATCH 15/25] first pass of instructions...consolidate? --- .github/copilot-instructions.md | 204 ++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..24178b0e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,204 @@ +# SharpCompress AI Agent Instructions + +## Project Overview +SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard) for .NET Framework 4.8, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios. + +## Architecture & Design Patterns + +### Three-Tier API Design +SharpCompress has three distinct API patterns for different use cases: + +1. **Archive API** (`IArchive`) - Random access on seekable streams + - Use for: File-based archives where you can seek backward/forward + - Example: `ZipArchive.Open()`, `TarArchive.Open()`, `RarArchive.Open()` + - Located in: `src/SharpCompress/Archives/` + +2. **Reader API** (`IReader`) - Forward-only on non-seekable streams + - Use for: Streaming scenarios (network, pipes) where seeking isn't possible + - Example: `ZipReader.Open()`, `TarReader.Open()`, `ReaderFactory.Open()` + - Located in: `src/SharpCompress/Readers/` + +3. **Writer API** (`IWriter`) - Forward-only writing + - Use for: Creating archives in streaming fashion + - Example: `ZipWriter`, `TarWriter`, `WriterFactory.Open()` + - Located in: `src/SharpCompress/Writers/` + +**Important:** 7Zip only supports Archive API due to format design limitations. + +### 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/` + +### Stream Disposal Rules (Changed in v0.21) +**Critical:** SharpCompress closes wrapped streams by default to align with .NET Framework expectations. + +- Always use `ReaderOptions` or `WriterOptions` with `LeaveStreamOpen = true` to prevent disposal +- Example: `new ReaderOptions { LeaveStreamOpen = true }` +- When working with compression streams directly, use `NonDisposingStream` wrapper (if it exists) +- Always wrap operations in `using` blocks for proper disposal + +## Development Workflow + +### Building & Testing +```bash +# Build entire solution +dotnet build SharpCompress.sln + +# Build specific framework (library targets: net48, net481, netstandard2.0, net6.0, net8.0) +dotnet build src/SharpCompress/SharpCompress.csproj -f net8.0 + +# Run tests (targets: net10.0, net48) +dotnet test tests/SharpCompress.Test/SharpCompress.Test.csproj -f net10.0 + +# Custom build script (Bullseye-based) +dotnet run --project build/build.csproj -- test +``` + +### Code Formatting (REQUIRED) +```bash +# Restore CSharpier tool +dotnet tool restore + +# Format code (MUST run before committing) +dotnet csharpier . + +# Check formatting +dotnet csharpier check . +``` +**Never commit without running `dotnet csharpier .` from project root.** + +### VS Code Tasks +- Build: `Ctrl+Shift+B` (Cmd+Shift+B on Mac) +- Test: Use "test" task or F5 to debug tests +- Format: "format" task runs CSharpier + +### Debugging Features +When building with `DEBUG_STREAMS` constant (enabled for net10.0 Debug builds): +- Stream operations emit debug information +- Helps trace stream lifecycle and disposal issues +- See `#if DEBUG_STREAMS` blocks in stream classes + +## Code Conventions + +### Nullable Reference Types +- **All variables are non-nullable by default** +- Check for `null` only at entry points (public APIs) +- Always use `is null` or `is not null` (never `== null` or `!= null`) +- Trust C# null annotations - don't add redundant null checks +- Extension method: `value.NotNull(nameof(value))` validates parameters + +### Async/Await Patterns +- **All I/O operations support async/await** with `CancellationToken` +- Naming: Async methods end with `Async` suffix +- Key async methods: + - `WriteEntryToAsync(stream, cancellationToken)` + - `WriteAllToDirectoryAsync(path, options, cancellationToken)` + - `OpenEntryStreamAsync(cancellationToken)` + - `MoveToNextEntryAsync(cancellationToken)` +- Always provide `CancellationToken` parameter in new async methods + +### C# Style +- Use latest C# features (currently C# 14) +- File-scoped namespaces required (`namespace SharpCompress.Archives;`) +- `var` for all local variables unless type clarity is critical +- Expression-bodied members preferred for simple operations +- Private fields use `_camelCase` prefix (enforced by .editorconfig) +- Constants use `CONSTANT_CASE` (all caps with underscores) + +## Testing Patterns + +### 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 +- **Never emit "Arrange", "Act", "Assert" comments** - code should be self-documenting +- Match naming style of nearby test files + +### Common Test Patterns +```csharp +public class MyFormatTests : TestBase +{ + [Fact] + public void ExtractTest() + { + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "test.zip"); + using (var archive = ZipArchive.Open(testArchive)) + using (var reader = archive.ExtractAllEntries()) + { + reader.WriteAllToDirectory(SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true }); + } + VerifyFiles(); // Compares against ORIGINAL_FILES_PATH + } +} +``` + +### Critical Test Areas +- Test both Archive and Reader APIs when format supports both +- Test async operations with cancellation tokens +- Test stream disposal behavior (`LeaveStreamOpen`) +- Test with multiple target frameworks if behavior differs (net10.0 vs net48) +- Edge cases: empty archives, large files, encrypted archives, multi-volume + +## Format-Specific Knowledge + +### Tar Considerations +- **Tar requires file size in header** - If stream is non-seekable and no size provided, TarWriter throws +- Often combined with compression: `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.tar.lz` +- Long filenames handled via GNU longlink extension + +### Zip Considerations +- Supports Zip64 for large files (seekable streams only) +- Encryption: PKWare and WinZip AES supported (except encrypted LZMA) +- Compression methods: DEFLATE (default), Deflate64 (read-only), BZip2, LZMA, PPMd, Shrink, Reduce, Implode +- Multi-volume Zip requires `ZipArchive` (Reader can't seek across volumes) +- `ZipReader` processes LocalEntry headers and intentionally skips DirectoryEntry headers (they're redundant in streaming mode) + +### Rar Considerations +- Read-only format (proprietary) +- RAR5 decryption supported but CRC check incomplete +- SOLID archives require sequential extraction for performance + +### 7Zip Limitations +- No Reader/Writer API support (format doesn't support streaming) +- Archive API only - requires seekable stream + +## Project 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) +``` + +## 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 + +## Performance Considerations +- Use Reader/Writer APIs for large files to avoid loading entire file in memory +- Leverage async I/O for better scalability +- For solid archives (Rar, 7Zip), sequential extraction is significantly faster +- Consider compression level trade-offs when writing (speed vs size) + +## References +- [FORMATS.md](../FORMATS.md) - Complete format support matrix +- [USAGE.md](../USAGE.md) - API usage examples +- [AGENTS.md](../AGENTS.md) - Detailed coding conventions From 9c1c6fff9f17f853e6ce8e97417085273a7fffa8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:49:36 +0000 Subject: [PATCH 16/25] Initial plan From 6a37c550859437519b20d8e49732f6bca891f28a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:52:22 +0000 Subject: [PATCH 17/25] Consolidate agent instructions into AGENTS.md Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .github/copilot-instructions.md | 204 -------------------------------- AGENTS.md | 39 ++++++ 2 files changed, 39 insertions(+), 204 deletions(-) delete mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 24178b0e..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,204 +0,0 @@ -# SharpCompress AI Agent Instructions - -## Project Overview -SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard) for .NET Framework 4.8, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios. - -## Architecture & Design Patterns - -### Three-Tier API Design -SharpCompress has three distinct API patterns for different use cases: - -1. **Archive API** (`IArchive`) - Random access on seekable streams - - Use for: File-based archives where you can seek backward/forward - - Example: `ZipArchive.Open()`, `TarArchive.Open()`, `RarArchive.Open()` - - Located in: `src/SharpCompress/Archives/` - -2. **Reader API** (`IReader`) - Forward-only on non-seekable streams - - Use for: Streaming scenarios (network, pipes) where seeking isn't possible - - Example: `ZipReader.Open()`, `TarReader.Open()`, `ReaderFactory.Open()` - - Located in: `src/SharpCompress/Readers/` - -3. **Writer API** (`IWriter`) - Forward-only writing - - Use for: Creating archives in streaming fashion - - Example: `ZipWriter`, `TarWriter`, `WriterFactory.Open()` - - Located in: `src/SharpCompress/Writers/` - -**Important:** 7Zip only supports Archive API due to format design limitations. - -### 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/` - -### Stream Disposal Rules (Changed in v0.21) -**Critical:** SharpCompress closes wrapped streams by default to align with .NET Framework expectations. - -- Always use `ReaderOptions` or `WriterOptions` with `LeaveStreamOpen = true` to prevent disposal -- Example: `new ReaderOptions { LeaveStreamOpen = true }` -- When working with compression streams directly, use `NonDisposingStream` wrapper (if it exists) -- Always wrap operations in `using` blocks for proper disposal - -## Development Workflow - -### Building & Testing -```bash -# Build entire solution -dotnet build SharpCompress.sln - -# Build specific framework (library targets: net48, net481, netstandard2.0, net6.0, net8.0) -dotnet build src/SharpCompress/SharpCompress.csproj -f net8.0 - -# Run tests (targets: net10.0, net48) -dotnet test tests/SharpCompress.Test/SharpCompress.Test.csproj -f net10.0 - -# Custom build script (Bullseye-based) -dotnet run --project build/build.csproj -- test -``` - -### Code Formatting (REQUIRED) -```bash -# Restore CSharpier tool -dotnet tool restore - -# Format code (MUST run before committing) -dotnet csharpier . - -# Check formatting -dotnet csharpier check . -``` -**Never commit without running `dotnet csharpier .` from project root.** - -### VS Code Tasks -- Build: `Ctrl+Shift+B` (Cmd+Shift+B on Mac) -- Test: Use "test" task or F5 to debug tests -- Format: "format" task runs CSharpier - -### Debugging Features -When building with `DEBUG_STREAMS` constant (enabled for net10.0 Debug builds): -- Stream operations emit debug information -- Helps trace stream lifecycle and disposal issues -- See `#if DEBUG_STREAMS` blocks in stream classes - -## Code Conventions - -### Nullable Reference Types -- **All variables are non-nullable by default** -- Check for `null` only at entry points (public APIs) -- Always use `is null` or `is not null` (never `== null` or `!= null`) -- Trust C# null annotations - don't add redundant null checks -- Extension method: `value.NotNull(nameof(value))` validates parameters - -### Async/Await Patterns -- **All I/O operations support async/await** with `CancellationToken` -- Naming: Async methods end with `Async` suffix -- Key async methods: - - `WriteEntryToAsync(stream, cancellationToken)` - - `WriteAllToDirectoryAsync(path, options, cancellationToken)` - - `OpenEntryStreamAsync(cancellationToken)` - - `MoveToNextEntryAsync(cancellationToken)` -- Always provide `CancellationToken` parameter in new async methods - -### C# Style -- Use latest C# features (currently C# 14) -- File-scoped namespaces required (`namespace SharpCompress.Archives;`) -- `var` for all local variables unless type clarity is critical -- Expression-bodied members preferred for simple operations -- Private fields use `_camelCase` prefix (enforced by .editorconfig) -- Constants use `CONSTANT_CASE` (all caps with underscores) - -## Testing Patterns - -### 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 -- **Never emit "Arrange", "Act", "Assert" comments** - code should be self-documenting -- Match naming style of nearby test files - -### Common Test Patterns -```csharp -public class MyFormatTests : TestBase -{ - [Fact] - public void ExtractTest() - { - var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "test.zip"); - using (var archive = ZipArchive.Open(testArchive)) - using (var reader = archive.ExtractAllEntries()) - { - reader.WriteAllToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }); - } - VerifyFiles(); // Compares against ORIGINAL_FILES_PATH - } -} -``` - -### Critical Test Areas -- Test both Archive and Reader APIs when format supports both -- Test async operations with cancellation tokens -- Test stream disposal behavior (`LeaveStreamOpen`) -- Test with multiple target frameworks if behavior differs (net10.0 vs net48) -- Edge cases: empty archives, large files, encrypted archives, multi-volume - -## Format-Specific Knowledge - -### Tar Considerations -- **Tar requires file size in header** - If stream is non-seekable and no size provided, TarWriter throws -- Often combined with compression: `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.tar.lz` -- Long filenames handled via GNU longlink extension - -### Zip Considerations -- Supports Zip64 for large files (seekable streams only) -- Encryption: PKWare and WinZip AES supported (except encrypted LZMA) -- Compression methods: DEFLATE (default), Deflate64 (read-only), BZip2, LZMA, PPMd, Shrink, Reduce, Implode -- Multi-volume Zip requires `ZipArchive` (Reader can't seek across volumes) -- `ZipReader` processes LocalEntry headers and intentionally skips DirectoryEntry headers (they're redundant in streaming mode) - -### Rar Considerations -- Read-only format (proprietary) -- RAR5 decryption supported but CRC check incomplete -- SOLID archives require sequential extraction for performance - -### 7Zip Limitations -- No Reader/Writer API support (format doesn't support streaming) -- Archive API only - requires seekable stream - -## Project 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) -``` - -## 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 - -## Performance Considerations -- Use Reader/Writer APIs for large files to avoid loading entire file in memory -- Leverage async I/O for better scalability -- For solid archives (Rar, 7Zip), sequential extraction is significantly faster -- Consider compression level trade-offs when writing (speed vs size) - -## References -- [FORMATS.md](../FORMATS.md) - Complete format support matrix -- [USAGE.md](../USAGE.md) - API usage examples -- [AGENTS.md](../AGENTS.md) - Detailed coding conventions diff --git a/AGENTS.md b/AGENTS.md index 8275b651..4467fd55 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 From 99a6c4de886593077159219289a94bf18306435a Mon Sep 17 00:00:00 2001 From: HeroponRikIBestest Date: Tue, 2 Dec 2025 09:47:06 -0500 Subject: [PATCH 18/25] Add archive-level IsEncrypted flag --- src/SharpCompress/Archives/AbstractArchive.cs | 5 +++++ src/SharpCompress/Archives/Rar/RarArchive.cs | 5 +++++ src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 712db4de..01ed5b90 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -161,6 +161,11 @@ public abstract class AbstractArchive : IArchive, IArchiveExtra /// public virtual bool IsSolid => false; + /// + /// Archive is ENCRYPTED (this means the Archive has password-protected files). + /// + public virtual bool IsEncrypted => false; + /// /// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive. /// diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index b3689a9f..7e0b2877 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -84,6 +84,11 @@ public class RarArchive : AbstractArchive public override bool IsSolid => Volumes.First().IsSolidArchive; + public override bool IsEncrypted => + Entries + .Where(x => !x.IsDirectory) + .Any(file => file.IsEncrypted); + public virtual int MinVersion => Volumes.First().MinVersion; public virtual int MaxVersion => Volumes.First().MaxVersion; diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index ea763409..ec2af951 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -205,6 +205,11 @@ public class SevenZipArchive : AbstractArchive x.FilePart.Folder) .Any(folder => folder.Count() > 1); + public override bool IsEncrypted => + Entries + .Where(x => !x.IsDirectory) + .Any(file => file.IsEncrypted); + public override long TotalSize => _database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0; From e0660e777534b94afc89d59fde934c1cc90ae887 Mon Sep 17 00:00:00 2001 From: HeroponRikIBestest Date: Tue, 2 Dec 2025 09:55:24 -0500 Subject: [PATCH 19/25] Add tests --- tests/SharpCompress.Test/Rar/RarArchiveTests.cs | 9 +++++++++ .../SevenZip/SevenZipArchiveTests.cs | 9 +++++++++ .../Archives/7Zip.encryptedFiles.7z | Bin 0 -> 59042 bytes 3 files changed, 18 insertions(+) create mode 100644 tests/TestArchives/Archives/7Zip.encryptedFiles.7z diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 1aca13dd..991f18e5 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -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); + } } diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index 82919586..af21ff94 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -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() { diff --git a/tests/TestArchives/Archives/7Zip.encryptedFiles.7z b/tests/TestArchives/Archives/7Zip.encryptedFiles.7z new file mode 100644 index 0000000000000000000000000000000000000000..4c15bb2cf8e5407ddc147ffe1c4776c9895253e9 GIT binary patch literal 59042 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 X8Chc)1Q-~28jZLZFXjgMGcW)E;1-=d literal 0 HcmV?d00001 From 1b1df86a114e57067150aab9931ad25255cd082e Mon Sep 17 00:00:00 2001 From: HeroponRikIBestest Date: Tue, 2 Dec 2025 10:02:49 -0500 Subject: [PATCH 20/25] Improve logic --- src/SharpCompress/Archives/Rar/RarArchive.cs | 4 ++-- src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index 7e0b2877..c72fe5fa 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -86,8 +86,8 @@ public class RarArchive : AbstractArchive public override bool IsEncrypted => Entries - .Where(x => !x.IsDirectory) - .Any(file => file.IsEncrypted); + .First(x => !x.IsDirectory) + .IsEncrypted; public virtual int MinVersion => Volumes.First().MinVersion; public virtual int MaxVersion => Volumes.First().MaxVersion; diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index ec2af951..2c8986b4 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -207,8 +207,8 @@ public class SevenZipArchive : AbstractArchive Entries - .Where(x => !x.IsDirectory) - .Any(file => file.IsEncrypted); + .First(x => !x.IsDirectory) + .IsEncrypted; public override long TotalSize => _database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0; From 41c3cc1a18cde27b2744ac6e9724543b52a1bbf6 Mon Sep 17 00:00:00 2001 From: HeroponRikIBestest Date: Wed, 3 Dec 2025 12:05:16 -0500 Subject: [PATCH 21/25] Csharpier --- src/SharpCompress/Archives/Rar/RarArchive.cs | 5 +---- src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index c72fe5fa..9acfdccc 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -84,10 +84,7 @@ public class RarArchive : AbstractArchive public override bool IsSolid => Volumes.First().IsSolidArchive; - public override bool IsEncrypted => - Entries - .First(x => !x.IsDirectory) - .IsEncrypted; + public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted; public virtual int MinVersion => Volumes.First().MinVersion; public virtual int MaxVersion => Volumes.First().MaxVersion; diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 2c8986b4..27d47d23 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -205,10 +205,7 @@ public class SevenZipArchive : AbstractArchive x.FilePart.Folder) .Any(folder => folder.Count() > 1); - public override bool IsEncrypted => - Entries - .First(x => !x.IsDirectory) - .IsEncrypted; + public override bool IsEncrypted => Entries.First(x => !x.IsDirectory).IsEncrypted; public override long TotalSize => _database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0; From 5161f4df339c4b08f95fb6af40f88ae9df9dcd99 Mon Sep 17 00:00:00 2001 From: drone1400 <> Date: Sat, 6 Dec 2025 17:33:01 +0200 Subject: [PATCH 22/25] Add alternative option for writing TAR archives with USTAR header format - TarWriterOptions now has a property that allows the user to select writing the TAR using the USTAR header format - if unspecified, will default to the original modern GNU TAR header format - default behavior is unchanged --- .../Common/Tar/Headers/TarHeader.cs | 119 +++++++++++++++++- .../Tar/Headers/TarHeaderWriteFormat.cs | 7 ++ src/SharpCompress/Writers/Tar/TarWriter.cs | 4 +- .../Writers/Tar/TarWriterOptions.cs | 11 +- 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 src/SharpCompress/Common/Tar/Headers/TarHeaderWriteFormat.cs diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 06a1f10a..562e3b42 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -1,5 +1,6 @@ using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; using System.Text; @@ -9,8 +10,13 @@ internal sealed class TarHeader { internal static readonly DateTime EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public TarHeader(ArchiveEncoding archiveEncoding) => ArchiveEncoding = archiveEncoding; + public TarHeader(ArchiveEncoding archiveEncoding, TarHeaderWriteFormat writeFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK) + { + ArchiveEncoding = archiveEncoding; + WriteFormat = writeFormat; + } + internal TarHeaderWriteFormat WriteFormat { get; set; } internal string? Name { get; set; } internal string? LinkName { get; set; } @@ -30,6 +36,108 @@ internal sealed class TarHeader private const int MAX_LONG_NAME_SIZE = 32768; internal void Write(Stream output) + { + switch (WriteFormat) + { + case TarHeaderWriteFormat.GNU_TAR_LONG_LINK: + WriteGnuTarLongLink(output); + break; + case TarHeaderWriteFormat.USTAR: + WriteUstar(output); + break; + default: + throw new Exception("This should be impossible..."); + } + } + + internal void WriteUstar(Stream output) + { + var buffer = new byte[BLOCK_SIZE]; + + WriteOctalBytes(511, buffer, 100, 8); // file mode + WriteOctalBytes(0, buffer, 108, 8); // owner ID + WriteOctalBytes(0, buffer, 116, 8); // group ID + + //ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257); + var nameByteCount = ArchiveEncoding + .GetEncoding() + .GetByteCount(Name.NotNull("Name is null")); + + if (nameByteCount > 100) + { + // if name is longer, try to split it into name and namePrefix + + string fullName = Name.NotNull("Name is null"); + + // find all directory separators + List dirSeps = new List(); + for (int i = 0; i < fullName.Length; i++) + { + if (fullName[i] == Path.DirectorySeparatorChar) + { + dirSeps.Add(i); + } + } + + // find the right place to split the name + int splitIndex = -1; + for (int i = 0; i < dirSeps.Count; i++) + { + int count = ArchiveEncoding.GetEncoding().GetByteCount(fullName.Substring(0, dirSeps[i])); + if (count < 155) + { + splitIndex = dirSeps[i]; + } + else + { + break; + } + } + + if (splitIndex == -1) + { + throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Directory separator not found! Try using GNU Tar format instead!"); + } + + string namePrefix = fullName.Substring(0, splitIndex); + string name = fullName.Substring(splitIndex + 1); + + if (this.ArchiveEncoding.GetEncoding().GetByteCount(namePrefix) >= 155) + throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!"); + + if (this.ArchiveEncoding.GetEncoding().GetByteCount(name) >= 100) + throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!"); + + + // write name prefix + WriteStringBytes(ArchiveEncoding.Encode(namePrefix), buffer, 345, 100); + // write partial name + WriteStringBytes(ArchiveEncoding.Encode(name), buffer, 100); + } + else + { + WriteStringBytes(ArchiveEncoding.Encode(Name.NotNull("Name is null")), buffer, 100); + } + + WriteOctalBytes(Size, buffer, 124, 12); + var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds; + WriteOctalBytes(time, buffer, 136, 12); + buffer[156] = (byte)EntryType; + + // write ustar magic field + WriteStringBytes(Encoding.ASCII.GetBytes("ustar"), buffer, 257, 6 ); + // write ustar version "00" + buffer[263] = 0x30; + buffer[264] = 0x30; + + + var crc = RecalculateChecksum(buffer); + WriteOctalBytes(crc, buffer, 148, 8); + + output.Write(buffer, 0, buffer.Length); + } + + internal void WriteGnuTarLongLink(Stream output) { var buffer = new byte[BLOCK_SIZE]; @@ -85,7 +193,7 @@ internal sealed class TarHeader 0, 100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1) ); - Write(output); + WriteGnuTarLongLink(output); } } @@ -241,6 +349,13 @@ internal sealed class TarHeader buffer.Slice(i, length - i).Clear(); } + private static void WriteStringBytes(ReadOnlySpan name, Span buffer, int offset, int length) + { + name.CopyTo(buffer.Slice(offset)); + var i = Math.Min(length, name.Length); + buffer.Slice(offset+i, length - i).Clear(); + } + private static void WriteStringBytes(string name, byte[] buffer, int offset, int length) { int i; diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeaderWriteFormat.cs b/src/SharpCompress/Common/Tar/Headers/TarHeaderWriteFormat.cs new file mode 100644 index 00000000..3a3a434a --- /dev/null +++ b/src/SharpCompress/Common/Tar/Headers/TarHeaderWriteFormat.cs @@ -0,0 +1,7 @@ +namespace SharpCompress.Common.Tar.Headers; + +public enum TarHeaderWriteFormat +{ + GNU_TAR_LONG_LINK, + USTAR, +} diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index 96346e2b..77ae6fbd 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -15,11 +15,13 @@ namespace SharpCompress.Writers.Tar; public class TarWriter : AbstractWriter { private readonly bool finalizeArchiveOnClose; + private TarHeaderWriteFormat headerFormat; public TarWriter(Stream destination, TarWriterOptions options) : base(ArchiveType.Tar, options) { finalizeArchiveOnClose = options.FinalizeArchiveOnClose; + headerFormat = options.HeaderFormat; if (!destination.CanWrite) { @@ -121,7 +123,7 @@ public class TarWriter : AbstractWriter var realSize = size ?? source.Length; - var header = new TarHeader(WriterOptions.ArchiveEncoding); + var header = new TarHeader(WriterOptions.ArchiveEncoding, headerFormat); header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH; header.Name = NormalizeFilename(filename); diff --git a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs index 8f2e866d..64bc8599 100755 --- a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs +++ b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs @@ -1,4 +1,5 @@ using SharpCompress.Common; +using SharpCompress.Common.Tar.Headers; namespace SharpCompress.Writers.Tar; @@ -9,8 +10,14 @@ public class TarWriterOptions : WriterOptions /// public bool FinalizeArchiveOnClose { get; } - public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose) - : base(compressionType) => FinalizeArchiveOnClose = finalizeArchiveOnClose; + public TarHeaderWriteFormat HeaderFormat { get; } + + public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose, TarHeaderWriteFormat headerFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK) + : base(compressionType) + { + FinalizeArchiveOnClose = finalizeArchiveOnClose; + HeaderFormat = headerFormat; + } internal TarWriterOptions(WriterOptions options) : this(options.CompressionType, true) => ArchiveEncoding = options.ArchiveEncoding; From 40b1aadeb268c543f2bcca88612c4c5552c8a5fd Mon Sep 17 00:00:00 2001 From: drone1400 <> Date: Mon, 8 Dec 2025 17:49:58 +0200 Subject: [PATCH 23/25] Reformatted modified files with csharpier --- .../Common/Tar/Headers/TarHeader.cs | 34 +++++++++++++------ .../Writers/Tar/TarWriterOptions.cs | 6 +++- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 562e3b42..e6b7c265 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -10,7 +10,10 @@ internal sealed class TarHeader { internal static readonly DateTime EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public TarHeader(ArchiveEncoding archiveEncoding, TarHeaderWriteFormat writeFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK) + public TarHeader( + ArchiveEncoding archiveEncoding, + TarHeaderWriteFormat writeFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK + ) { ArchiveEncoding = archiveEncoding; WriteFormat = writeFormat; @@ -83,7 +86,9 @@ internal sealed class TarHeader int splitIndex = -1; for (int i = 0; i < dirSeps.Count; i++) { - int count = ArchiveEncoding.GetEncoding().GetByteCount(fullName.Substring(0, dirSeps[i])); + int count = ArchiveEncoding + .GetEncoding() + .GetByteCount(fullName.Substring(0, dirSeps[i])); if (count < 155) { splitIndex = dirSeps[i]; @@ -96,18 +101,23 @@ internal sealed class TarHeader if (splitIndex == -1) { - throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Directory separator not found! Try using GNU Tar format instead!"); + throw new Exception( + $"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Directory separator not found! Try using GNU Tar format instead!" + ); } string namePrefix = fullName.Substring(0, splitIndex); string name = fullName.Substring(splitIndex + 1); if (this.ArchiveEncoding.GetEncoding().GetByteCount(namePrefix) >= 155) - throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!"); + throw new Exception( + $"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!" + ); if (this.ArchiveEncoding.GetEncoding().GetByteCount(name) >= 100) - throw new Exception($"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!"); - + throw new Exception( + $"Tar header USTAR format can not fit file name \"{fullName}\" of length {nameByteCount}! Try using GNU Tar format instead!" + ); // write name prefix WriteStringBytes(ArchiveEncoding.Encode(namePrefix), buffer, 345, 100); @@ -125,12 +135,11 @@ internal sealed class TarHeader buffer[156] = (byte)EntryType; // write ustar magic field - WriteStringBytes(Encoding.ASCII.GetBytes("ustar"), buffer, 257, 6 ); + WriteStringBytes(Encoding.ASCII.GetBytes("ustar"), buffer, 257, 6); // write ustar version "00" buffer[263] = 0x30; buffer[264] = 0x30; - var crc = RecalculateChecksum(buffer); WriteOctalBytes(crc, buffer, 148, 8); @@ -349,11 +358,16 @@ internal sealed class TarHeader buffer.Slice(i, length - i).Clear(); } - private static void WriteStringBytes(ReadOnlySpan name, Span buffer, int offset, int length) + private static void WriteStringBytes( + ReadOnlySpan name, + Span buffer, + int offset, + int length + ) { name.CopyTo(buffer.Slice(offset)); var i = Math.Min(length, name.Length); - buffer.Slice(offset+i, length - i).Clear(); + buffer.Slice(offset + i, length - i).Clear(); } private static void WriteStringBytes(string name, byte[] buffer, int offset, int length) diff --git a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs index 64bc8599..a8c3a29e 100755 --- a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs +++ b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs @@ -12,7 +12,11 @@ public class TarWriterOptions : WriterOptions public TarHeaderWriteFormat HeaderFormat { get; } - public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose, TarHeaderWriteFormat headerFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK) + public TarWriterOptions( + CompressionType compressionType, + bool finalizeArchiveOnClose, + TarHeaderWriteFormat headerFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK + ) : base(compressionType) { FinalizeArchiveOnClose = finalizeArchiveOnClose; From 5f52fc2176128d1246652aaab0232832272c12ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:02:14 +0000 Subject: [PATCH 24/25] Bump actions/upload-artifact from 5 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 3f6bef75..2ce8f4dd 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -19,7 +19,7 @@ jobs: with: dotnet-version: 10.0.x - run: dotnet run --project build/build.csproj - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: ${{ matrix.os }}-sharpcompress.nupkg path: artifacts/* From 28c93d6841bef743072aa60243a6f0ea64e9fc2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:42:59 +0000 Subject: [PATCH 25/25] Bump csharpier from 1.2.1 to 1.2.3 --- updated-dependencies: - dependency-name: csharpier dependency-version: 1.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 03df2323..05325293 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.2.1", + "version": "1.2.3", "commands": [ "csharpier" ],