From 06713c641ec7d12359ee8d6dfa89f8b84368fb6a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 28 Oct 2025 11:26:31 +0000 Subject: [PATCH] async deflate 64 --- .../Compressors/Deflate64/Deflate64Stream.cs | 102 ++++++++++++++++++ .../SharpCompress.Test/Zip/Zip64AsyncTests.cs | 86 ++++++++------- 2 files changed, 150 insertions(+), 38 deletions(-) diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs index 3de390c6..3ba9e618 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -6,6 +6,8 @@ using System; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Common.Zip; using SharpCompress.IO; @@ -143,6 +145,106 @@ public sealed class Deflate64Stream : Stream, IStreamStack return count - remainingCount; } + public override async Task ReadAsync( + byte[] array, + int offset, + int count, + CancellationToken cancellationToken + ) + { + ValidateParameters(array, offset, count); + EnsureNotDisposed(); + + int bytesRead; + var currentOffset = offset; + var remainingCount = count; + + while (true) + { + bytesRead = _inflater.Inflate(array, currentOffset, remainingCount); + currentOffset += bytesRead; + remainingCount -= bytesRead; + + if (remainingCount == 0) + { + break; + } + + if (_inflater.Finished()) + { + // if we finished decompressing, we can't have anything left in the outputwindow. + Debug.Assert( + _inflater.AvailableOutput == 0, + "We should have copied all stuff out!" + ); + break; + } + + var bytes = await _stream + .ReadAsync(_buffer, 0, _buffer.Length, cancellationToken) + .ConfigureAwait(false); + if (bytes <= 0) + { + break; + } + else if (bytes > _buffer.Length) + { + // The stream is either malicious or poorly implemented and returned a number of + // bytes larger than the buffer supplied to it. + throw new InvalidFormatException("Deflate64: invalid data"); + } + + _inflater.SetInput(_buffer, 0, bytes); + } + + return count - remainingCount; + } + +#if !NETFRAMEWORK && !NETSTANDARD2_0 + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + EnsureNotDisposed(); + + // InflaterManaged doesn't have a Span-based Inflate method, so we need to work with arrays + // For large buffers, we could rent from ArrayPool, but for simplicity we'll use the buffer's array if available + if ( + System.Runtime.InteropServices.MemoryMarshal.TryGetArray( + buffer, + out var arraySegment + ) + ) + { + // Fast path: the Memory is backed by an array + return await ReadAsync( + arraySegment.Array!, + arraySegment.Offset, + arraySegment.Count, + cancellationToken + ) + .ConfigureAwait(false); + } + else + { + // Slow path: rent a temporary array + var tempBuffer = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); + try + { + var bytesRead = await ReadAsync(tempBuffer, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false); + tempBuffer.AsMemory(0, bytesRead).CopyTo(buffer); + return bytesRead; + } + finally + { + System.Buffers.ArrayPool.Shared.Return(tempBuffer); + } + } + } +#endif + private void ValidateParameters(byte[] array, int offset, int count) { if (array is null) diff --git a/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs b/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs index ee392c5d..223ad969 100644 --- a/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs @@ -100,51 +100,61 @@ public class Zip64AsyncTests : WriterTests { filename = Path.Combine(SCRATCH2_FILES_PATH, filename); - if (File.Exists(filename)) + try { - File.Delete(filename); - } + if (File.Exists(filename)) + { + File.Delete(filename); + } - if (!File.Exists(filename)) - { - await CreateZipArchiveAsync( - filename, - files, - filesize, - writeChunkSize, - setZip64, - forwardOnly - ); - } + if (!File.Exists(filename)) + { + await CreateZipArchiveAsync( + filename, + files, + filesize, + writeChunkSize, + setZip64, + forwardOnly + ); + } - var resForward = await ReadForwardOnlyAsync(filename); - if (resForward.Item1 != files) - { - throw new InvalidOperationException( - $"Incorrect number of items reported: {resForward.Item1}, should have been {files}" - ); - } + var resForward = await ReadForwardOnlyAsync(filename); + if (resForward.Item1 != files) + { + throw new InvalidOperationException( + $"Incorrect number of items reported: {resForward.Item1}, should have been {files}" + ); + } - if (resForward.Item2 != files * filesize) - { - throw new InvalidOperationException( - $"Incorrect combined size reported: {resForward.Item2}, should have been {files * filesize}" - ); - } + if (resForward.Item2 != files * filesize) + { + throw new InvalidOperationException( + $"Incorrect combined size reported: {resForward.Item2}, should have been {files * filesize}" + ); + } - var resArchive = ReadArchive(filename); - if (resArchive.Item1 != files) - { - throw new InvalidOperationException( - $"Incorrect number of items reported: {resArchive.Item1}, should have been {files}" - ); - } + var resArchive = ReadArchive(filename); + if (resArchive.Item1 != files) + { + throw new InvalidOperationException( + $"Incorrect number of items reported: {resArchive.Item1}, should have been {files}" + ); + } - if (resArchive.Item2 != files * filesize) + if (resArchive.Item2 != files * filesize) + { + throw new InvalidOperationException( + $"Incorrect number of items reported: {resArchive.Item2}, should have been {files * filesize}" + ); + } + } + finally { - throw new InvalidOperationException( - $"Incorrect number of items reported: {resArchive.Item2}, should have been {files * filesize}" - ); + if (File.Exists(filename)) + { + File.Delete(filename); + } } }