diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index e6b511d83..b4956592e 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -32,11 +32,56 @@ protected override IEnumerable LoadEntries( IEnumerable 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 @@ IEnumerable volumes } } - return entries; + foreach (var entry in entries) + { + yield return entry; + } } private void LoadFactory(Stream stream) @@ -71,6 +119,27 @@ private void LoadFactory(Stream stream) } } + private async Task LoadFactoryAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + if (_database is null) + { + stream.Position = 0; + var reader = new ArchiveReader(); + await reader.OpenAsync( + stream, + lookForHeader: ReaderOptions.LookForHeader, + cancellationToken + ); + _database = await reader.ReadDatabaseAsync( + new PasswordProvider(ReaderOptions.Password), + cancellationToken + ); + } + } + protected override IReader CreateReaderForSolidExtraction() => new SevenZipReader(ReaderOptions, this); diff --git a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs index 288a72989..28762ace3 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs @@ -5,6 +5,8 @@ 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 @@ public void Open(Stream stream, bool lookForHeader) _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 @@ public ArchiveDatabase ReadDatabase(IPasswordProvider pass) 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 aad19fc29..623b94e3c 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -27,8 +27,7 @@ public override bool IsArchive( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize - ) => - ArjHeader.IsArchive(stream); + ) => ArjHeader.IsArchive(stream); public override ValueTask IsArchiveAsync( Stream stream, diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 413253332..032c15c49 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", diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index ec5a69350..d60c7a74a 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -8,14 +8,17 @@ namespace SharpCompress.Test.SevenZip; -#if !NETFRAMEWORK public class SevenZipArchiveAsyncTests : ArchiveTests { [Fact] 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) ); @@ -30,9 +33,21 @@ public async Task SevenZipArchive_LZMA_AsyncStreamExtraction() 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(); @@ -42,7 +57,11 @@ public async Task SevenZipArchive_LZMA_AsyncStreamExtraction() 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) ); @@ -57,19 +76,35 @@ public async Task SevenZipArchive_LZMA2_AsyncStreamExtraction() 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(); } - //[Fact] + [Fact] 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) ); @@ -84,19 +119,35 @@ public async Task SevenZipArchive_Solid_AsyncStreamExtraction() 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(); } - //[Fact] + [Fact] 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) ); @@ -111,19 +162,35 @@ public async Task SevenZipArchive_BZip2_AsyncStreamExtraction() 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(); } - //[Fact] + [Fact] 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) ); @@ -138,12 +205,23 @@ public async Task SevenZipArchive_PPMd_AsyncStreamExtraction() 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(); } } -#endif