Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -672,8 +672,29 @@ protected override void Dispose(bool disposing)
// In this case, we still need to clean up internal resources, hence the inner finally blocks.
try
{
// Auto-rewind the stream if we're in decompression mode and the stream supports seeking
if (disposing && _mode == CompressionMode.Decompress && _stream?.CanSeek == true && _inflater != null)
{
// Check if there are unconsumed bytes in the inflater's input buffer
int unconsumedBytes = _inflater.GetAvailableInput();
if (unconsumedBytes > 0)
{
try
{
// Rewind the stream to the exact end of the compressed data
_stream.Seek(-unconsumedBytes, SeekOrigin.Current);
}
catch
{
// If seeking fails, we don't want to throw during disposal
}
}
}

if (disposing && !_leaveOpen)
{
_stream?.Dispose();
}
}
finally
{
Expand Down Expand Up @@ -727,8 +748,29 @@ async ValueTask Core()
_stream = null!;
try
{
// Auto-rewind the stream if we're in decompression mode and the stream supports seeking
if (stream != null && _mode == CompressionMode.Decompress && stream.CanSeek && _inflater != null)
{
// Check if there are unconsumed bytes in the inflater's input buffer
int unconsumedBytes = _inflater.GetAvailableInput();
if (unconsumedBytes > 0)
{
try
{
// Rewind the stream to the exact end of the compressed data
stream.Seek(-unconsumedBytes, SeekOrigin.Current);
}
catch
{
// If seeking fails, we don't want to throw during disposal
}
}
}

if (!_leaveOpen && stream != null)
{
await stream.DisposeAsync().ConfigureAwait(false);
}
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ private unsafe bool ResetStreamForLeftoverInput()

public bool NonEmptyInput() => _nonEmptyInput;

internal int GetAvailableInput() => (int)_zlibStream.AvailIn;

public void SetInput(byte[] inputBuffer, int startIndex, int count)
{
Debug.Assert(NeedsInput(), "We have something left in previous input!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,124 @@ public void StreamTruncation_IsDetected(TestScenario testScenario)
}, testScenario.ToString()).Dispose();
}

[Fact]
public void AutomaticStreamRewinds_WhenDecompressionFinishes()
{
// Create test data: some header bytes + compressed data + some footer bytes
byte[] originalData = Encoding.UTF8.GetBytes("Hello, world! This is a test string for compression.");
byte[] headerBytes = Encoding.UTF8.GetBytes("HEADER");
byte[] footerBytes = Encoding.UTF8.GetBytes("FOOTER");

// Create compressed data
byte[] compressedData;
using (var ms = new MemoryStream())
{
using (var deflateStream = new DeflateStream(ms, CompressionMode.Compress))
{
deflateStream.Write(originalData);
}
compressedData = ms.ToArray();
}

// Create a stream with: [header][compressed data][footer]
byte[] combinedData = new byte[headerBytes.Length + compressedData.Length + footerBytes.Length];
Array.Copy(headerBytes, 0, combinedData, 0, headerBytes.Length);
Array.Copy(compressedData, 0, combinedData, headerBytes.Length, compressedData.Length);
Array.Copy(footerBytes, 0, combinedData, headerBytes.Length + compressedData.Length, footerBytes.Length);

using (var stream = new MemoryStream(combinedData))
{
// Read the header
byte[] headerBuffer = new byte[headerBytes.Length];
stream.Read(headerBuffer, 0, headerBytes.Length);
Assert.Equal(headerBytes, headerBuffer);

// Decompress the data
byte[] decompressedData;
using (var deflateStream = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true))
{
using (var outputStream = new MemoryStream())
{
deflateStream.CopyTo(outputStream);
decompressedData = outputStream.ToArray();
}
}
// DeflateStream should have automatically rewound the stream to the end of compressed data

// Verify decompressed data is correct
Assert.Equal(originalData, decompressedData);

// Read the footer - if automatic rewinding worked, this should read the footer correctly
byte[] footerBuffer = new byte[footerBytes.Length];
int bytesRead = stream.Read(footerBuffer, 0, footerBytes.Length);

Assert.Equal(footerBytes.Length, bytesRead);
Assert.Equal(footerBytes, footerBuffer);

// Verify we're at the end of the stream
Assert.Equal(combinedData.Length, stream.Position);
}
}

[Fact]
public async Task AutomaticStreamRewinds_WhenDecompressionFinishes_Async()
{
// Create test data: some header bytes + compressed data + some footer bytes
byte[] originalData = Encoding.UTF8.GetBytes("Hello, world! This is a test string for compression.");
byte[] headerBytes = Encoding.UTF8.GetBytes("HEADER");
byte[] footerBytes = Encoding.UTF8.GetBytes("FOOTER");

// Create compressed data
byte[] compressedData;
using (var ms = new MemoryStream())
{
using (var deflateStream = new DeflateStream(ms, CompressionMode.Compress))
{
await deflateStream.WriteAsync(originalData);
}
compressedData = ms.ToArray();
}

// Create a stream with: [header][compressed data][footer]
byte[] combinedData = new byte[headerBytes.Length + compressedData.Length + footerBytes.Length];
Array.Copy(headerBytes, 0, combinedData, 0, headerBytes.Length);
Array.Copy(compressedData, 0, combinedData, headerBytes.Length, compressedData.Length);
Array.Copy(footerBytes, 0, combinedData, headerBytes.Length + compressedData.Length, footerBytes.Length);

using (var stream = new MemoryStream(combinedData))
{
// Read the header
byte[] headerBuffer = new byte[headerBytes.Length];
await stream.ReadAsync(headerBuffer, 0, headerBytes.Length);
Assert.Equal(headerBytes, headerBuffer);

// Decompress the data
byte[] decompressedData;
await using (var deflateStream = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true))
{
using (var outputStream = new MemoryStream())
{
await deflateStream.CopyToAsync(outputStream);
decompressedData = outputStream.ToArray();
}
}
// DeflateStream should have automatically rewound the stream to the end of compressed data

// Verify decompressed data is correct
Assert.Equal(originalData, decompressedData);

// Read the footer - if automatic rewinding worked, this should read the footer correctly
byte[] footerBuffer = new byte[footerBytes.Length];
int bytesRead = await stream.ReadAsync(footerBuffer, 0, footerBytes.Length);

Assert.Equal(footerBytes.Length, bytesRead);
Assert.Equal(footerBytes, footerBuffer);

// Verify we're at the end of the stream
Assert.Equal(combinedData.Length, stream.Position);
}
}

private sealed class DerivedDeflateStream : DeflateStream
{
public bool ReadArrayInvoked = false, WriteArrayInvoked = false;
Expand Down
Loading