async deflate 64

This commit is contained in:
Adam Hathcock
2025-10-28 11:26:31 +00:00
parent 42f7d43139
commit 06713c641e
2 changed files with 150 additions and 38 deletions

View File

@@ -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<int> 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<int> ReadAsync(
Memory<byte> 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<byte>(
buffer,
out var arraySegment
)
)
{
// Fast path: the Memory<byte> 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<byte>.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<byte>.Shared.Return(tempBuffer);
}
}
}
#endif
private void ValidateParameters(byte[] array, int offset, int count)
{
if (array is null)

View File

@@ -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);
}
}
}