using System; using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Archives; using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Zip; using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test.Zip; public class ZipReaderAsyncTests : ReaderTests { public ZipReaderAsyncTests() => UseExtensionInsteadOfNameToVerify = true; [Fact] public async ValueTask Issue_269_Double_Skip_Async() { var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip"); using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var count = 0; while (await reader.MoveToNextEntryAsync()) { count++; if (!reader.Entry.IsDirectory) { if (count % 2 != 0) { await reader.WriteEntryToAsync(Stream.Null); } } } } [Fact] public async ValueTask Zip_Zip64_Streamed_Read_Async() => await ReadAsync("Zip.zip64.zip", CompressionType.Deflate); [Fact] public async ValueTask Zip_ZipX_Streamed_Read_Async() => await ReadAsync("Zip.zipx", CompressionType.LZMA); [Fact] public async ValueTask Zip_BZip2_Streamed_Read_Async() => await ReadAsync("Zip.bzip2.dd.zip", CompressionType.BZip2); [Fact] public async ValueTask Zip_BZip2_Read_Async() => await ReadAsync("Zip.bzip2.zip", CompressionType.BZip2); [Fact] public async ValueTask Zip_Deflate_Streamed2_Read_Async() => await ReadAsync("Zip.deflate.dd-.zip", CompressionType.Deflate); [Fact] public async ValueTask Zip_Deflate_Streamed_Read_Async() => await ReadAsync("Zip.deflate.dd.zip", CompressionType.Deflate); [Fact] public async ValueTask Zip_Deflate_Streamed_Skip_Async() { using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var x = 0; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { x++; if (x % 2 == 0) { await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } } [Fact] public async ValueTask Zip_Deflate_Streamed2_Skip_Async() { using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd-.zip")) ); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var x = 0; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { x++; if (x % 2 == 0) { await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } } [Fact] public async ValueTask Zip_Deflate_Read_Async() => await ReadAsync("Zip.deflate.zip", CompressionType.Deflate); [Fact] public async ValueTask Zip_Deflate64_Read_Async() => await ReadAsync("Zip.deflate64.zip", CompressionType.Deflate64); [Fact] public async ValueTask Zip_LZMA_Streamed_Read_Async() => await ReadAsync("Zip.lzma.dd.zip", CompressionType.LZMA); [Fact] public async ValueTask Zip_LZMA_Read_Async() => await ReadAsync("Zip.lzma.zip", CompressionType.LZMA); [Fact] public async ValueTask Zip_PPMd_Streamed_Read_Async() => await ReadAsync("Zip.ppmd.dd.zip", CompressionType.PPMd); [Fact] public async ValueTask Zip_PPMd_Read_Async() => await ReadAsync("Zip.ppmd.zip", CompressionType.PPMd); [Fact] public async ValueTask Zip_None_Read_Async() => await ReadAsync("Zip.none.zip", CompressionType.None); [Fact] public async ValueTask Zip_Deflate_NoEmptyDirs_Read_Async() => await ReadAsync("Zip.deflate.noEmptyDirs.zip", CompressionType.Deflate); [Fact] public async ValueTask Zip_BZip2_PkwareEncryption_Read_Async() { using ( Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip")) ) using ( IReader baseReader = ZipReader.OpenReader( stream, new ReaderOptions { Password = "test" } ) ) { IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } VerifyFiles(); } [Fact] public async ValueTask Zip_Reader_Disposal_Test_Async() { using var stream = new TestStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); await using ( var reader = await ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), new ReaderOptions().WithLeaveStreamOpen(false) ) ) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } Assert.True(stream.IsDisposed); } [Fact] public async ValueTask Zip_Reader_Disposal_Test2_Async() { using var stream = new TestStream( new AsyncOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ) ); await using var reader = await ReaderFactory.OpenAsyncReader(stream); while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } Assert.False(stream.IsDisposed); } [Fact] public async ValueTask Zip_LZMA_WinzipAES_Read_Async() => await Assert.ThrowsAsync(async () => { using ( Stream stream = new AsyncOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip")) ) ) using ( IReader baseReader = ZipReader.OpenReader( stream, new ReaderOptions { Password = "test" } ) ) { IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.LZMA, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } VerifyFiles(); }); [Fact] public async ValueTask Zip_Deflate_WinzipAES_Read_Async() { using ( Stream stream = new AsyncOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip")) ) ) await using ( var reader = await ReaderFactory.OpenAsyncReader( stream, new ReaderOptions { Password = "test" } ) ) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Deflate, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } VerifyFiles(); } [Fact] public async ValueTask Zip_Deflate_ZipCrypto_Read_Async() { var count = 0; using ( Stream stream = new AsyncOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip")) ) ) await using ( var reader = await ReaderFactory.OpenAsyncReader( stream, new ReaderOptions { Password = "test" } ) ) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.None, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); count++; } } } Assert.Equal(8, count); } [Fact] public async ValueTask EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_Deflate_Async() { // Since version 0.41.0: EntryStream.DisposeAsync() should not throw NotSupportedException // when FlushAsync() fails on non-seekable streams (Deflate compression) var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"); using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); // This should not throw, even if internal FlushAsync() fails while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { #if LEGACY_DOTNET using var entryStream = await reader.OpenEntryStreamAsync(); #else await using var entryStream = await reader.OpenEntryStreamAsync(); #endif // Read some data var buffer = new byte[1024]; await entryStream.ReadAsync(buffer, 0, buffer.Length); // DisposeAsync should not throw NotSupportedException } } } [Fact] public async ValueTask EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA_Async() { // Since version 0.41.0: EntryStream.DisposeAsync() should not throw NotSupportedException // when FlushAsync() fails on non-seekable streams (LZMA compression) var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip"); using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); // This should not throw, even if internal FlushAsync() fails while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { #if LEGACY_DOTNET using var entryStream = await reader.OpenEntryStreamAsync(); #else await using var entryStream = await reader.OpenEntryStreamAsync(); #endif // Read some data var buffer = new byte[1024]; await entryStream.ReadAsync(buffer, 0, buffer.Length); // DisposeAsync should not throw NotSupportedException } } } [Fact] public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_Deflate_Async() { // Regression test: since 0.41.0, archive iteration would silently break // when the input stream throws NotSupportedException in Flush(). // Only the first entry would be returned, then iteration would stop without exception. var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"); using var fileStream = File.OpenRead(path); using Stream stream = new ThrowOnFlushStream(fileStream); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var count = 0; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { count++; } } // Should iterate through all entries, not just the first one Assert.True(count > 1, $"Expected more than 1 entry, but got {count}"); } [Fact] public async ValueTask Archive_Iteration_DoesNotBreak_WhenFlushThrows_LZMA_Async() { // Regression test: since 0.41.0, archive iteration would silently break // when the input stream throws NotSupportedException in Flush(). // Only the first entry would be returned, then iteration would stop without exception. var path = Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip"); using var fileStream = File.OpenRead(path); using Stream stream = new ThrowOnFlushStream(fileStream); await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var count = 0; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { count++; } } // Should iterate through all entries, not just the first one Assert.True(count > 1, $"Expected more than 1 entry, but got {count}"); } }