From 0b2158f74caaaadbd1a4914f6353730ad1ed4959 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:44:57 +0000 Subject: [PATCH 1/5] Initial plan From bbba2e6c7ab3b96e286dcdcef2dacd5714a0023e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:53:18 +0000 Subject: [PATCH 2/5] Initial plan for fixing SevenZipArchive_LZMA_AsyncStreamExtraction test Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/packages.lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 41325333..032c15c4 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -216,9 +216,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "ISahzLHsHY7vrwqr2p1YWZ+gsxoBRtH7gWRDK8fDUst9pp2He0GiesaqEfeX0V8QMCJM3eNEHGGpnIcPjFo2NQ==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", From 9bb670ad196acde024935e0f9fa4ec86075b5d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:28:05 +0000 Subject: [PATCH 3/5] Fix SevenZipArchive async stream handling by adding async Open and ReadDatabase methods Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .../Archives/SevenZip/SevenZipArchive.cs | 77 ++++++++- .../Common/SevenZip/ArchiveReader.cs | 146 ++++++++++++++++++ src/SharpCompress/Factories/ArjFactory.cs | 3 +- 3 files changed, 220 insertions(+), 6 deletions(-) diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index e6b511d8..b4956592 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -32,11 +32,56 @@ public partial class SevenZipArchive : AbstractArchive volumes ) { - var stream = volumes.Single().Stream; - LoadFactory(stream); + foreach (var volume in volumes) + { + LoadFactory(volume.Stream); + if (_database is null) + { + yield break; + } + var entries = new SevenZipArchiveEntry[_database._files.Count]; + for (var i = 0; i < _database._files.Count; i++) + { + var file = _database._files[i]; + entries[i] = new SevenZipArchiveEntry( + this, + new SevenZipFilePart( + volume.Stream, + _database, + i, + file, + ReaderOptions.ArchiveEncoding + ) + ); + } + foreach ( + var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder) + ) + { + var isSolid = false; + foreach (var entry in group) + { + entry.IsSolid = isSolid; + isSolid = true; + } + } + + foreach (var entry in entries) + { + yield return entry; + } + } + } + + protected override async IAsyncEnumerable LoadEntriesAsync( + IAsyncEnumerable volumes + ) + { + var stream = (await volumes.SingleAsync()).Stream; + await LoadFactoryAsync(stream); if (_database is null) { - return Enumerable.Empty(); + yield break; } var entries = new SevenZipArchiveEntry[_database._files.Count]; for (var i = 0; i < _database._files.Count; i++) @@ -57,7 +102,10 @@ public partial class SevenZipArchive : AbstractArchive new SevenZipReader(ReaderOptions, this); diff --git a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs index 288a7298..28762ace 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.IO; @@ -1270,6 +1272,46 @@ internal class ArchiveReader _stream = stream; } + public async Task OpenAsync( + Stream stream, + bool lookForHeader, + CancellationToken cancellationToken = default + ) + { + Close(); + + _streamOrigin = stream.Position; + _streamEnding = stream.Length; + + var canScan = lookForHeader ? 0x80000 - 20 : 0; + while (true) + { + // TODO: Check Signature! + _header = new byte[0x20]; + await stream.ReadExactAsync(_header, 0, 0x20, cancellationToken); + + if ( + !lookForHeader + || _header + .AsSpan(0, length: 6) + .SequenceEqual([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]) + ) + { + break; + } + + if (canScan == 0) + { + throw new InvalidFormatException("Unable to find 7z signature"); + } + + canScan--; + stream.Position = ++_streamOrigin; + } + + _stream = stream; + } + public void Close() { _stream?.Dispose(); @@ -1383,6 +1425,110 @@ internal class ArchiveReader return db; } + public async Task ReadDatabaseAsync( + IPasswordProvider pass, + CancellationToken cancellationToken = default + ) + { + var db = new ArchiveDatabase(pass); + db.Clear(); + + db._majorVersion = _header[6]; + db._minorVersion = _header[7]; + + if (db._majorVersion != 0) + { + throw new InvalidOperationException(); + } + + var crcFromArchive = DataReader.Get32(_header, 8); + var nextHeaderOffset = (long)DataReader.Get64(_header, 0xC); + var nextHeaderSize = (long)DataReader.Get64(_header, 0x14); + var nextHeaderCrc = DataReader.Get32(_header, 0x1C); + + var crc = Crc.INIT_CRC; + crc = Crc.Update(crc, nextHeaderOffset); + crc = Crc.Update(crc, nextHeaderSize); + crc = Crc.Update(crc, nextHeaderCrc); + crc = Crc.Finish(crc); + + if (crc != crcFromArchive) + { + throw new InvalidOperationException(); + } + + db._startPositionAfterHeader = _streamOrigin + 0x20; + + // empty header is ok + if (nextHeaderSize == 0) + { + db.Fill(); + return db; + } + + if (nextHeaderOffset < 0 || nextHeaderSize < 0 || nextHeaderSize > int.MaxValue) + { + throw new InvalidOperationException(); + } + + if (nextHeaderOffset > _streamEnding - db._startPositionAfterHeader) + { + throw new InvalidOperationException("nextHeaderOffset is invalid"); + } + + _stream.Seek(nextHeaderOffset, SeekOrigin.Current); + + var header = new byte[nextHeaderSize]; + await _stream.ReadExactAsync(header, 0, header.Length, cancellationToken); + + if (Crc.Finish(Crc.Update(Crc.INIT_CRC, header, 0, header.Length)) != nextHeaderCrc) + { + throw new InvalidOperationException(); + } + + using (var streamSwitch = new CStreamSwitch()) + { + streamSwitch.Set(this, header); + + var type = ReadId(); + if (type != BlockType.Header) + { + if (type != BlockType.EncodedHeader) + { + throw new InvalidOperationException(); + } + + var dataVector = ReadAndDecodePackedStreams( + db._startPositionAfterHeader, + db.PasswordProvider + ); + + // compressed header without content is odd but ok + if (dataVector.Count == 0) + { + db.Fill(); + return db; + } + + if (dataVector.Count != 1) + { + throw new InvalidOperationException(); + } + + streamSwitch.Set(this, dataVector[0]); + + if (ReadId() != BlockType.Header) + { + throw new InvalidOperationException(); + } + } + + ReadHeader(db, db.PasswordProvider); + } + db.Fill(); + return db; + } + internal class CExtractFolderInfo { internal int _fileIndex; diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs index aad19fc2..623b94e3 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -27,8 +27,7 @@ namespace SharpCompress.Factories Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize - ) => - ArjHeader.IsArchive(stream); + ) => ArjHeader.IsArchive(stream); public override ValueTask IsArchiveAsync( Stream stream, From 491beabe032222ee3eb8559ae6b9a0bf819b4d2c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 16 Jan 2026 08:35:49 +0000 Subject: [PATCH 4/5] uncomment tests --- .../SevenZip/SevenZipArchiveAsyncTests.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index ec5a6935..cd6b67f2 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -8,7 +8,6 @@ using Xunit; namespace SharpCompress.Test.SevenZip; -#if !NETFRAMEWORK public class SevenZipArchiveAsyncTests : ArchiveTests { [Fact] @@ -65,7 +64,7 @@ public class SevenZipArchiveAsyncTests : ArchiveTests VerifyFiles(); } - //[Fact] + [Fact] public async Task SevenZipArchive_Solid_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); @@ -92,7 +91,7 @@ public class SevenZipArchiveAsyncTests : ArchiveTests VerifyFiles(); } - //[Fact] + [Fact] public async Task SevenZipArchive_BZip2_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.BZip2.7z"); @@ -119,7 +118,7 @@ public class SevenZipArchiveAsyncTests : ArchiveTests VerifyFiles(); } - //[Fact] + [Fact] public async Task SevenZipArchive_PPMd_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.PPMd.7z"); @@ -146,4 +145,3 @@ public class SevenZipArchiveAsyncTests : ArchiveTests VerifyFiles(); } } -#endif From f4ce4cbad8855a482fd5d242cd90ea3ef26e4983 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 16 Jan 2026 08:43:13 +0000 Subject: [PATCH 5/5] fix tests for both frameworks --- .../SevenZip/SevenZipArchiveAsyncTests.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index cd6b67f2..d60c7a74 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -14,7 +14,11 @@ public class SevenZipArchiveAsyncTests : ArchiveTests public async Task SevenZipArchive_LZMA_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.LZMA.7z"); +#if NETFRAMEWORK using var stream = File.OpenRead(testArchive); +#else + await using var stream = File.OpenRead(testArchive); +#endif await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); @@ -29,9 +33,21 @@ public class SevenZipArchiveAsyncTests : ArchiveTests Directory.CreateDirectory(targetDir); } +#if NETFRAMEWORK using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#else + await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#endif +#if NETFRAMEWORK + using var targetStream = File.Create(targetPath); +#else await using var targetStream = File.Create(targetPath); +#endif +#if NETFRAMEWORK + await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None); +#else await sourceStream.CopyToAsync(targetStream, CancellationToken.None); +#endif } VerifyFiles(); @@ -41,7 +57,11 @@ public class SevenZipArchiveAsyncTests : ArchiveTests public async Task SevenZipArchive_LZMA2_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.LZMA2.7z"); +#if NETFRAMEWORK using var stream = File.OpenRead(testArchive); +#else + await using var stream = File.OpenRead(testArchive); +#endif await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); @@ -56,9 +76,21 @@ public class SevenZipArchiveAsyncTests : ArchiveTests Directory.CreateDirectory(targetDir); } +#if NETFRAMEWORK using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#else + await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#endif +#if NETFRAMEWORK + using var targetStream = File.Create(targetPath); +#else await using var targetStream = File.Create(targetPath); +#endif +#if NETFRAMEWORK + await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None); +#else await sourceStream.CopyToAsync(targetStream, CancellationToken.None); +#endif } VerifyFiles(); @@ -68,7 +100,11 @@ public class SevenZipArchiveAsyncTests : ArchiveTests public async Task SevenZipArchive_Solid_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); +#if NETFRAMEWORK using var stream = File.OpenRead(testArchive); +#else + await using var stream = File.OpenRead(testArchive); +#endif await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); @@ -83,9 +119,21 @@ public class SevenZipArchiveAsyncTests : ArchiveTests Directory.CreateDirectory(targetDir); } +#if NETFRAMEWORK using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#else + await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#endif +#if NETFRAMEWORK + using var targetStream = File.Create(targetPath); +#else await using var targetStream = File.Create(targetPath); +#endif +#if NETFRAMEWORK + await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None); +#else await sourceStream.CopyToAsync(targetStream, CancellationToken.None); +#endif } VerifyFiles(); @@ -95,7 +143,11 @@ public class SevenZipArchiveAsyncTests : ArchiveTests public async Task SevenZipArchive_BZip2_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.BZip2.7z"); +#if NETFRAMEWORK using var stream = File.OpenRead(testArchive); +#else + await using var stream = File.OpenRead(testArchive); +#endif await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); @@ -110,9 +162,21 @@ public class SevenZipArchiveAsyncTests : ArchiveTests Directory.CreateDirectory(targetDir); } +#if NETFRAMEWORK using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#else + await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#endif +#if NETFRAMEWORK + using var targetStream = File.Create(targetPath); +#else await using var targetStream = File.Create(targetPath); +#endif +#if NETFRAMEWORK + await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None); +#else await sourceStream.CopyToAsync(targetStream, CancellationToken.None); +#endif } VerifyFiles(); @@ -122,7 +186,11 @@ public class SevenZipArchiveAsyncTests : ArchiveTests public async Task SevenZipArchive_PPMd_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.PPMd.7z"); +#if NETFRAMEWORK using var stream = File.OpenRead(testArchive); +#else + await using var stream = File.OpenRead(testArchive); +#endif await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); @@ -137,9 +205,21 @@ public class SevenZipArchiveAsyncTests : ArchiveTests Directory.CreateDirectory(targetDir); } +#if NETFRAMEWORK using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#else + await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None); +#endif +#if NETFRAMEWORK + using var targetStream = File.Create(targetPath); +#else await using var targetStream = File.Create(targetPath); +#endif +#if NETFRAMEWORK + await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None); +#else await sourceStream.CopyToAsync(targetStream, CancellationToken.None); +#endif } VerifyFiles();