diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index e9b27d6a..3f766cd8 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -153,12 +153,12 @@ public partial class SevenZipArchive : AbstractArchive @@ -39,7 +41,14 @@ public partial class EntryStream : Stream _isDisposed = true; if (!(_completed || _reader.Cancelled)) { - SkipEntry(); + if (_useSyncOverAsyncDispose) + { + SkipEntryAsync().GetAwaiter().GetResult(); + } + else + { + SkipEntry(); + } } //Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised diff --git a/src/SharpCompress/Common/Tar/TarFilePart.cs b/src/SharpCompress/Common/Tar/TarFilePart.cs index 06516114..15b76d14 100644 --- a/src/SharpCompress/Common/Tar/TarFilePart.cs +++ b/src/SharpCompress/Common/Tar/TarFilePart.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common.Tar.Headers; namespace SharpCompress.Common.Tar; @@ -23,10 +25,28 @@ internal sealed class TarFilePart : FilePart if (_seekableStream != null) { _seekableStream.Position = Header.DataStartPosition ?? 0; - return new TarReadOnlySubStream(_seekableStream, Header.Size); + return new TarReadOnlySubStream(_seekableStream, Header.Size, false); } return Header.PackedStream.NotNull(); } + internal override ValueTask GetCompressedStreamAsync( + CancellationToken cancellationToken = default + ) + { + if (_seekableStream != null) + { + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif + _seekableStream.Position = Header.DataStartPosition ?? 0; + return new ValueTask( + new TarReadOnlySubStream(_seekableStream, Header.Size, useSyncOverAsync) + ); + } + return new ValueTask(Header.PackedStream.NotNull()); + } + internal override Stream? GetRawStream() => null; } diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs index de6422aa..ad6a357e 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs @@ -36,7 +36,15 @@ internal static partial class TarHeaderFactory break; case StreamingMode.Streaming: { - header.PackedStream = new TarReadOnlySubStream(stream, header.Size); + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif + header.PackedStream = new TarReadOnlySubStream( + stream, + header.Size, + useSyncOverAsync + ); } break; default: diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs index c141bc8e..bcd3a5e9 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs @@ -37,7 +37,11 @@ internal static partial class TarHeaderFactory break; case StreamingMode.Streaming: { - header.PackedStream = new TarReadOnlySubStream(stream, header.Size); + header.PackedStream = new TarReadOnlySubStream( + stream, + header.Size, + false + ); } break; default: diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs index 1a9a40b9..f0140426 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -6,13 +6,15 @@ namespace SharpCompress.Common.Tar; internal class TarReadOnlySubStream : Stream { private readonly Stream _stream; + private readonly bool _useSyncOverAsyncDispose; private bool _isDisposed; private long _amountRead; - public TarReadOnlySubStream(Stream stream, long bytesToRead) + public TarReadOnlySubStream(Stream stream, long bytesToRead, bool useSyncOverAsyncDispose) { _stream = stream; + _useSyncOverAsyncDispose = useSyncOverAsyncDispose; BytesLeftToRead = bytesToRead; } @@ -35,7 +37,14 @@ internal class TarReadOnlySubStream : Stream if (bytesInLastBlock != 0) { - _stream.Skip(512 - bytesInLastBlock); + if (_useSyncOverAsyncDispose) + { + _stream.SkipAsync(512 - bytesInLastBlock).GetAwaiter().GetResult(); + } + else + { + _stream.Skip(512 - bytesInLastBlock); + } } } } diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index 641848fb..f9df099f 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Security.Cryptography; @@ -18,15 +19,18 @@ internal partial class WinzipAesCryptoStream : Stream private bool _isFinalBlock; private long _totalBytesLeftToRead; private bool _isDisposed; + private bool _useSyncOverAsyncDispose; internal WinzipAesCryptoStream( Stream stream, WinzipAesEncryptionData winzipAesEncryptionData, - long length + long length, + bool useSyncOverAsyncDispose ) { _stream = stream; _totalBytesLeftToRead = length; + _useSyncOverAsyncDispose = useSyncOverAsyncDispose; _cipher = CreateCipher(winzipAesEncryptionData); @@ -68,20 +72,33 @@ internal partial class WinzipAesCryptoStream : Stream if (disposing) { // Read out last 10 auth bytes - catch exceptions for async-only streams - try + if (_useSyncOverAsyncDispose) + { + var ten = ArrayPool.Shared.Rent(10); + try + { + _stream.ReadFullyAsync(ten, 0, 10).GetAwaiter().GetResult(); + } + finally + { + ArrayPool.Shared.Return(ten); + } + } + else { Span ten = stackalloc byte[10]; _stream.ReadFully(ten); } - catch (NotSupportedException) - { - // Stream may be async-only, auth bytes will be skipped - // This is acceptable when the entire stream has been read - } _stream.Dispose(); } } + private async Task ReadAuthBytesAsync() + { + byte[] authBytes = new byte[10]; + await _stream.ReadFullyAsync(authBytes, 0, 10).ConfigureAwait(false); + } + public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs index 98cc93c5..7e153bda 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -99,10 +99,15 @@ internal abstract partial class ZipFilePart { if (Header.WinzipAesEncryptionData != null) { + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif return new WinzipAesCryptoStream( plainStream, Header.WinzipAesEncryptionData, - Header.CompressedSize - 10 + Header.CompressedSize - 10, + useSyncOverAsync ); } return plainStream; diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index f01faf34..5ab69796 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -270,7 +270,8 @@ internal abstract partial class ZipFilePart : FilePart return new WinzipAesCryptoStream( plainStream, Header.WinzipAesEncryptionData, - Header.CompressedSize - 10 + Header.CompressedSize - 10, + false ); } return plainStream; diff --git a/src/SharpCompress/Readers/AbstractReader.Async.cs b/src/SharpCompress/Readers/AbstractReader.Async.cs index 958045e0..979f701d 100644 --- a/src/SharpCompress/Readers/AbstractReader.Async.cs +++ b/src/SharpCompress/Readers/AbstractReader.Async.cs @@ -178,7 +178,11 @@ public abstract partial class AbstractReader .Parts.First() .GetCompressedStreamAsync(cancellationToken) .ConfigureAwait(false); - return CreateEntryStream(stream); + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif + return CreateEntryStream(stream, useSyncOverAsync); } internal virtual ValueTask NextEntryForCurrentStreamAsync() => diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index e969955a..46b595b0 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -35,8 +35,6 @@ public abstract partial class AbstractReader : IReader, IAsyncR public ArchiveType ArchiveType { get; } - protected bool IsAsync => _entriesForCurrentReadStreamAsync is not null; - /// /// Current volume that the current entry resides in /// @@ -248,7 +246,7 @@ public abstract partial class AbstractReader : IReader, IAsyncR { throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } - var stream = GetEntryStream(); + var stream = GetEntryStream(false); _wroteCurrentEntry = true; return stream; } @@ -256,11 +254,11 @@ public abstract partial class AbstractReader : IReader, IAsyncR /// /// Retains a reference to the entry stream, so we can check whether it completed later. /// - protected EntryStream CreateEntryStream(Stream? decompressed) => - new(this, decompressed.NotNull()); + protected EntryStream CreateEntryStream(Stream? decompressed, bool useSyncOverAsyncDispose) => + new(this, decompressed.NotNull(), useSyncOverAsyncDispose); - protected virtual EntryStream GetEntryStream() => - CreateEntryStream(Entry.Parts.First().GetCompressedStream()); + protected virtual EntryStream GetEntryStream(bool useSyncOverAsyncDispose) => + CreateEntryStream(Entry.Parts.First().GetCompressedStream(), useSyncOverAsyncDispose); #endregion diff --git a/src/SharpCompress/Readers/Rar/RarReader.Async.cs b/src/SharpCompress/Readers/Rar/RarReader.Async.cs index 0e1943d2..9dc50f11 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.Async.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.Async.cs @@ -27,6 +27,10 @@ public abstract partial class RarReader CancellationToken cancellationToken = default ) { + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif if (Entry.IsRedir) { throw new InvalidOperationException("no stream for redirect entry"); @@ -40,7 +44,8 @@ public abstract partial class RarReader return CreateEntryStream( await RarCrcStream .CreateAsync(UnpackV1.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false) + .ConfigureAwait(false), + useSyncOverAsync ); } @@ -49,14 +54,16 @@ public abstract partial class RarReader return CreateEntryStream( await RarBLAKE2spStream .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false) + .ConfigureAwait(false), + useSyncOverAsync ); } return CreateEntryStream( await RarCrcStream .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false) + .ConfigureAwait(false), + useSyncOverAsync ); } } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index cd0aebc9..cae92ee1 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -110,7 +110,7 @@ public abstract partial class RarReader : AbstractReader CreateFilePartEnumerableForCurrentEntry() => Entry.Parts; - protected override EntryStream GetEntryStream() + protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) { if (Entry.IsRedir) { @@ -122,17 +122,24 @@ public abstract partial class RarReader : AbstractReader 5) { return CreateEntryStream( - RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream) + RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream), + useSyncOverAsyncDispose ); } - return CreateEntryStream(RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream)); + return CreateEntryStream( + RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream), + useSyncOverAsyncDispose + ); } // GetEntryStreamAsync moved to RarReader.Async.cs diff --git a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs index c93552cf..ece913bf 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using SharpCompress.Archives; using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; @@ -252,14 +253,14 @@ public class ZipReaderAsyncTests : ReaderTests File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip")) ) ) - using ( - IReader baseReader = ZipReader.OpenReader( + + await using ( + var reader = await ReaderFactory.OpenAsyncReader( stream, new ReaderOptions { Password = "test" } ) ) { - IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -284,14 +285,13 @@ public class ZipReaderAsyncTests : ReaderTests File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip")) ) ) - using ( - IReader baseReader = ZipReader.OpenReader( + await using ( + var reader = await ReaderFactory.OpenAsyncReader( stream, new ReaderOptions { Password = "test" } ) ) { - IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory)