From e1f132b45b065f91e9bdf882f34b84d68a457117 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 21 Oct 2025 09:21:46 +0100 Subject: [PATCH 1/9] Only allow extract all on archives that are solid (some rars and 7zip only) --- src/SharpCompress/Archives/AbstractArchive.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index bf7fb9ed..262b4db4 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -144,6 +144,10 @@ public abstract class AbstractArchive : IArchive, IArchiveExtra /// public IReader ExtractAllEntries() { + if (IsSolid == false) + { + throw new InvalidOperationException("ExtractAllEntries can only be used on solid archives."); + } ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); return CreateReaderForSolidExtraction(); } From 5a5a55e556566c2f5045a63ba43fa57d0b205c87 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 21 Oct 2025 09:22:35 +0100 Subject: [PATCH 2/9] fmt --- src/SharpCompress/Archives/AbstractArchive.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 262b4db4..03b0d5ce 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -146,7 +146,9 @@ public abstract class AbstractArchive : IArchive, IArchiveExtra { if (IsSolid == false) { - throw new InvalidOperationException("ExtractAllEntries can only be used on solid archives."); + throw new InvalidOperationException( + "ExtractAllEntries can only be used on solid archives." + ); } ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); return CreateReaderForSolidExtraction(); From f1a80da34b6b58fc818828de581a8ca4598917eb Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 21 Oct 2025 09:56:29 +0100 Subject: [PATCH 3/9] fix tests that use extract all wrongly --- .../Archives/IArchiveExtensions.cs | 6 ++--- src/SharpCompress/Readers/Rar/RarReader.cs | 23 +++++++++++++++++++ src/SharpCompress/Readers/ReaderFactory.cs | 12 ++++++++++ tests/SharpCompress.Test/ArchiveTests.cs | 15 ++++-------- .../SharpCompress.Test/Rar/RarReaderTests.cs | 12 +++++++--- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 12 ++++------ .../SharpCompress.Test/Zip/ZipReaderTests.cs | 3 +-- 7 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index 337e4ccc..d9701da3 100644 --- a/src/SharpCompress/Archives/IArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveExtensions.cs @@ -45,12 +45,10 @@ public static class IArchiveExtensions var seenDirectories = new HashSet(); // Extract - var entries = archive.ExtractAllEntries(); - while (entries.MoveToNextEntry()) + foreach (var entry in archive.Entries) { cancellationToken.ThrowIfCancellationRequested(); - var entry = entries.Entry; if (entry.IsDirectory) { var dirPath = Path.Combine(destination, entry.Key.NotNull("Entry Key is null")); @@ -77,7 +75,7 @@ public static class IArchiveExtensions // Write file using var fs = File.OpenWrite(path); - entries.WriteEntryTo(fs); + entry.WriteTo(fs); // Update progress bytesRead += entry.Size; diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index aa73e30f..2a56ee35 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -40,6 +40,29 @@ public abstract class RarReader : AbstractReader public override RarVolume? Volume => volume; + public static RarReader Open(string filePath, ReaderOptions? options = null) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), options); + } + + public static RarReader Open(FileInfo fileInfo, ReaderOptions? options = null) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + return Open(fileInfo.OpenRead(), options); + } + + public static RarReader Open(IEnumerable filePaths, ReaderOptions? options = null) + { + return Open(filePaths.Select(x => new FileInfo(x)), options); + } + + public static RarReader Open(IEnumerable fileInfos, ReaderOptions? options = null) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + return Open(fileInfos.Select(x => x.OpenRead()), options); + } + /// /// Opens a RarReader for Non-seeking usage with a single volume /// diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index fb133d98..9830c8fb 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -9,6 +9,18 @@ namespace SharpCompress.Readers; public static class ReaderFactory { + public static IReader Open(string filePath, ReaderOptions? options = null) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), options); + } + + public static IReader Open(FileInfo fileInfo, ReaderOptions? options = null) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + return Open(fileInfo.OpenRead(), options); + } + /// /// Opens a Reader for Non-seeking usage /// diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 5a3d36ca..9c483c60 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -255,16 +255,10 @@ public class ArchiveTests : ReaderTests protected void ArchiveExtractToDirectory( string testArchive, ReaderOptions? readerOptions = null - ) => ArchiveExtractToDirectory(ArchiveFactory.AutoFactory, testArchive, readerOptions); - - protected void ArchiveExtractToDirectory( - IArchiveFactory archiveFactory, - string testArchive, - ReaderOptions? readerOptions = null ) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = archiveFactory.Open(new FileInfo(testArchive), readerOptions)) + using (var archive = ArchiveFactory.Open(new FileInfo(testArchive), readerOptions)) { archive.ExtractToDirectory(SCRATCH_FILES_PATH); } @@ -342,13 +336,12 @@ public class ArchiveTests : ReaderTests { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var archive = ArchiveFactory.Open(testArchive); - using var reader = archive.ExtractAllEntries(); - while (reader.MoveToNextEntry()) + foreach (var entry in archive.Entries) { - if (!reader.Entry.IsDirectory) + if (!entry.IsDirectory) { var memory = new MemoryStream(); - reader.WriteEntryTo(memory); + entry.WriteTo(memory); memory.Position = 0; diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index 2158adf7..d7c02d2d 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -407,10 +407,16 @@ public class RarReaderTests : ReaderTests Path.Combine("exe", "test.exe"), } ); - using var archive = RarArchive.Open( - Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part01.rar") + using var reader = RarReader.Open( + [ + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part01.rar"), + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part02.rar"), + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part03.rar"), + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part04.rar"), + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part05.rar"), + Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part06.rar"), + ] ); - using var reader = archive.ExtractAllEntries(); while (reader.MoveToNextEntry()) { Assert.Equal(expectedOrder.Pop(), reader.Entry.Key); diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index e09df7e9..53849e46 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -734,8 +734,7 @@ public class ZipArchiveTests : ArchiveTests { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - var archive = ArchiveFactory.Open(stream); - var reader = archive.ExtractAllEntries(); + var reader = ReaderFactory.Open(stream); var entries = 0; while (reader.MoveToNextEntry()) { @@ -763,8 +762,7 @@ public class ZipArchiveTests : ArchiveTests }; var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - var archive = ArchiveFactory.Open(stream); - var reader = archive.ExtractAllEntries(); + var reader = ReaderFactory.Open(stream); var x = 0; while (reader.MoveToNextEntry()) { @@ -781,7 +779,7 @@ public class ZipArchiveTests : ArchiveTests var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.UnicodePathExtra.zip"); using (var stream = File.OpenRead(zipPath)) { - var archive = ArchiveFactory.Open( + var reader = ReaderFactory.Open( stream, new ReaderOptions { @@ -791,13 +789,12 @@ public class ZipArchiveTests : ArchiveTests }, } ); - var reader = archive.ExtractAllEntries(); reader.MoveToNextEntry(); Assert.Equal("궖귛궖귙귪궖귗귪궖귙_wav.frq", reader.Entry.Key); } using (var stream = File.OpenRead(zipPath)) { - var archive = ArchiveFactory.Open( + var reader = ReaderFactory.Open( stream, new ReaderOptions { @@ -807,7 +804,6 @@ public class ZipArchiveTests : ArchiveTests }, } ); - var reader = archive.ExtractAllEntries(); reader.MoveToNextEntry(); Assert.Equal("きょきゅんきゃんきゅ_wav.frq", reader.Entry.Key); } diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 835521e0..118e788d 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -383,11 +383,10 @@ public class ZipReaderTests : ReaderTests [Fact] public void Zip_Uncompressed_Encrypted_Read() { - using var archive = ArchiveFactory.Open( + using var reader = ReaderFactory.Open( Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.encrypted.zip"), new ReaderOptions { Password = "test" } ); - using var reader = archive.ExtractAllEntries(); reader.MoveToNextEntry(); Assert.Equal("first.txt", reader.Entry.Key); Assert.Equal(199, reader.Entry.Size); From 84e03b1b277e548ba12a350850785ec1a6d1a1ed Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 21 Oct 2025 10:28:58 +0100 Subject: [PATCH 4/9] Allow 7zip files of all sizes? --- src/SharpCompress/Archives/AbstractArchive.cs | 4 ++-- tests/SharpCompress.Test/Zip/ZipReaderTests.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 03b0d5ce..eb385a47 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -144,10 +144,10 @@ public abstract class AbstractArchive : IArchive, IArchiveExtra /// public IReader ExtractAllEntries() { - if (IsSolid == false) + if (IsSolid == false && Type != ArchiveType.SevenZip) { throw new InvalidOperationException( - "ExtractAllEntries can only be used on solid archives." + "ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)." ); } ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 118e788d..c48d98f4 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -362,6 +362,8 @@ public class ZipReaderTests : ReaderTests while (reader.MoveToNextEntry()) { } } + //this test uses a large 7zip file containing a zip file inside it to test zip64 support + // we probably shouldn't be allowing ExtractAllEntries here but it works for now. [Fact] public void Zip_Uncompressed_64bit() { From e96090769886e559872af4dd9cdbb843c9809348 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 21 Oct 2025 13:55:56 +0100 Subject: [PATCH 5/9] Update src/SharpCompress/Archives/AbstractArchive.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/SharpCompress/Archives/AbstractArchive.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index eb385a47..712db4de 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -144,7 +144,7 @@ public abstract class AbstractArchive : IArchive, IArchiveExtra /// public IReader ExtractAllEntries() { - if (IsSolid == false && Type != ArchiveType.SevenZip) + if (!IsSolid && Type != ArchiveType.SevenZip) { throw new InvalidOperationException( "ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)." From 6de738ff17da4d44c0d7ba32039dfa1f4d5d0622 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Oct 2025 08:32:19 +0100 Subject: [PATCH 6/9] reduce dynamic stackallocs in unpackv1 --- .../Compressors/Rar/UnpackV1/Unpack.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index c4e6d108..3afa4caa 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -1028,7 +1028,7 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable vmCode.Add((byte)(GetBits() >> 8)); AddBits(8); } - return (AddVMCode(FirstByte, vmCode, Length)); + return AddVMCode(FirstByte, vmCode); } private bool ReadVMCodePPM() @@ -1073,10 +1073,10 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable } vmCode.Add((byte)Ch); // VMCode[I]=Ch; } - return (AddVMCode(FirstByte, vmCode, Length)); + return AddVMCode(FirstByte, vmCode); } - private bool AddVMCode(int firstByte, List vmCode, int length) + private bool AddVMCode(int firstByte, List vmCode) { var Inp = new BitInput(); Inp.InitBitInput(); @@ -1199,19 +1199,28 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable { return (false); } - Span VMCode = stackalloc byte[VMCodeSize]; - for (var I = 0; I < VMCodeSize; I++) - { - if (Inp.Overflow(3)) - { - return (false); - } - VMCode[I] = (byte)(Inp.GetBits() >> 8); - Inp.AddBits(8); - } - // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); - rarVM.prepare(VMCode, VMCodeSize, Filter.Program); + var VMCode = ArrayPool.Shared.Rent(VMCodeSize); + try + { + for (var I = 0; I < VMCodeSize; I++) + { + if (Inp.Overflow(3)) + { + return (false); + } + + VMCode[I] = (byte)(Inp.GetBits() >> 8); + Inp.AddBits(8); + } + + // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); + rarVM.prepare(VMCode, VMCodeSize, Filter.Program); + } + finally + { + ArrayPool.Shared.Return(VMCode); + } } StackFilter.Program.AltCommands = Filter.Program.Commands; // StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0]; StackFilter.Program.CommandCount = Filter.Program.CommandCount; From 01021e102b9bd12e13355e66f9f912cbc871315c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Oct 2025 08:36:03 +0100 Subject: [PATCH 7/9] remove some extra stackallocs --- src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 3afa4caa..4ebfbac2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -205,9 +205,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable private void Unpack29(bool solid) { - Span DDecode = stackalloc int[PackDef.DC]; - Span DBits = stackalloc byte[PackDef.DC]; - int Bits; if (DDecode[1] == 0) From 36c06c4089c8df94d40566ae866476bd8159ccc0 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Oct 2025 09:32:19 +0100 Subject: [PATCH 8/9] ugh, this is used because it shadows a field --- src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 4ebfbac2..3afa4caa 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -205,6 +205,9 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable private void Unpack29(bool solid) { + Span DDecode = stackalloc int[PackDef.DC]; + Span DBits = stackalloc byte[PackDef.DC]; + int Bits; if (DDecode[1] == 0) From 74e27c028eadc73222f863c6cf1d72fb55af1b58 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Oct 2025 10:10:07 +0100 Subject: [PATCH 9/9] fix the span length --- src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs | 2 +- src/SharpCompress/Compressors/Rar/VM/RarVM.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 3afa4caa..47904984 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -1215,7 +1215,7 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable } // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); - rarVM.prepare(VMCode, VMCodeSize, Filter.Program); + rarVM.prepare(VMCode.AsSpan(0, VMCodeSize), Filter.Program); } finally { diff --git a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs index f1ef3834..ce267bad 100644 --- a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs +++ b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs @@ -776,9 +776,10 @@ internal sealed class RarVM : BitInput } } - public void prepare(ReadOnlySpan code, int codeSize, VMPreparedProgram prg) + public void prepare(ReadOnlySpan code, VMPreparedProgram prg) { InitBitInput(); + var codeSize = code.Length; var cpLength = Math.Min(MAX_SIZE, codeSize); // memcpy(inBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); @@ -795,7 +796,7 @@ internal sealed class RarVM : BitInput prg.CommandCount = 0; if (xorSum == code[0]) { - var filterType = IsStandardFilter(code, codeSize); + var filterType = IsStandardFilter(code); if (filterType != VMStandardFilters.VMSF_NONE) { var curCmd = new VMPreparedCommand(); @@ -1105,7 +1106,7 @@ internal sealed class RarVM : BitInput } } - private VMStandardFilters IsStandardFilter(ReadOnlySpan code, int codeSize) + private VMStandardFilters IsStandardFilter(ReadOnlySpan code) { VMStandardFilterSignature[] stdList = {