未验证 提交 d3beb601 编写于 作者: S Stephen Toub 提交者: GitHub

Implement ZLibStream and fix SocketsHttpHandler deflate support (#42717)

* Implement ZLibStream and fix SocketsHttpHandler deflate support

- Implements ZLibStream, exposes it in the ref, and add tests
- Fixes SocketsHttpHandler to use ZLibStream instead of DeflateStream

* Add comment about deflate content encoding

* Apply suggestions from code review
Co-authored-by: NCarlos Sanchez <1175054+carlossanlop@users.noreply.github.com>

* Fix netfx build
Co-authored-by: NCarlos Sanchez <1175054+carlossanlop@users.noreply.github.com>
上级 c5b6881e
......@@ -1254,6 +1254,59 @@ public async Task Parallel_CompressDecompressMultipleStreamsConcurrently()
Assert.Equal(sourceData, decompressedStream.ToArray());
})));
}
[Fact]
public void Precancellation()
{
var ms = new MemoryStream();
using (Stream compressor = CreateStream(ms, CompressionMode.Compress, leaveOpen: true))
{
Assert.True(compressor.WriteAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
Assert.True(compressor.FlushAsync(new CancellationToken(true)).IsCanceled);
}
using (Stream decompressor = CreateStream(ms, CompressionMode.Decompress, leaveOpen: true))
{
Assert.True(decompressor.ReadAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task DisposeAsync_Flushes(bool leaveOpen)
{
var ms = new MemoryStream();
var cs = CreateStream(ms, CompressionMode.Compress, leaveOpen);
cs.WriteByte(1);
await cs.FlushAsync();
long pos = ms.Position;
cs.WriteByte(1);
Assert.Equal(pos, ms.Position);
await cs.DisposeAsync();
Assert.InRange(ms.ToArray().Length, pos + 1, int.MaxValue);
if (leaveOpen)
{
Assert.InRange(ms.Position, pos + 1, int.MaxValue);
}
else
{
Assert.Throws<ObjectDisposedException>(() => ms.Position);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task DisposeAsync_MultipleCallsAllowed(bool leaveOpen)
{
using (var cs = CreateStream(new MemoryStream(), CompressionMode.Compress, leaveOpen))
{
await cs.DisposeAsync();
await cs.DisposeAsync();
}
}
}
internal sealed class BadWrappedStream : MemoryStream
......
......@@ -33,46 +33,55 @@ public static IEnumerable<object[]> RemoteServersAndCompressionUris()
foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
{
yield return new object[] { remoteServer, remoteServer.GZipUri };
yield return new object[] { remoteServer, remoteServer.DeflateUri };
// Remote deflate endpoint isn't correctly following the deflate protocol.
//yield return new object[] { remoteServer, remoteServer.DeflateUri };
}
}
public static IEnumerable<object[]> DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData()
[Theory]
[InlineData("gzip", false)]
[InlineData("gzip", true)]
[InlineData("deflate", false)]
[InlineData("deflate", true)]
[InlineData("br", false)]
[InlineData("br", true)]
public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned(string encodingName, bool all)
{
foreach (bool specifyAllMethods in new[] { false, true })
Func<Stream, Stream> compress;
DecompressionMethods methods;
switch (encodingName)
{
yield return new object[]
{
"deflate",
new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.Deflate : _all
};
yield return new object[]
{
"gzip",
new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.GZip : _all
};
case "gzip":
compress = s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true);
methods = all ? DecompressionMethods.GZip : _all;
break;
#if !NETFRAMEWORK
yield return new object[]
{
"br",
new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.Brotli : _all
};
case "br":
if (IsWinHttpHandler)
{
// Brotli only supported on SocketsHttpHandler.
return;
}
compress = s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true);
methods = all ? DecompressionMethods.Brotli : _all;
break;
case "deflate":
// WinHttpHandler continues to use DeflateStream as it doesn't have a newer build than netstandard2.0
// and doesn't have access to ZLibStream.
compress = IsWinHttpHandler ?
new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)) :
new Func<Stream, Stream>(s => new ZLibStream(s, CompressionLevel.Optimal, leaveOpen: true));
methods = all ? DecompressionMethods.Deflate : _all;
break;
#endif
}
}
[Theory]
[MemberData(nameof(DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData))]
public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned(
string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
{
// Brotli only supported on SocketsHttpHandler.
if (IsWinHttpHandler && encodingName == "br")
{
return;
default:
Assert.Contains(encodingName, new[] { "br", "deflate", "gzip" });
return;
}
var expectedContent = new byte[12345];
......@@ -104,15 +113,15 @@ public static IEnumerable<object[]> DecompressedResponse_MethodNotSpecified_Orig
{
yield return new object[]
{
"deflate",
new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
"gzip",
new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
DecompressionMethods.None
};
#if !NETFRAMEWORK
yield return new object[]
{
"gzip",
new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
"deflate",
new Func<Stream, Stream>(s => new ZLibStream(s, CompressionLevel.Optimal, leaveOpen: true)),
DecompressionMethods.Brotli
};
yield return new object[]
......@@ -186,6 +195,26 @@ public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(Configu
}
}
// The remote server endpoint was written to use DeflateStream, which isn't actually a correct
// implementation of the deflate protocol (the deflate protocol requires the zlib wrapper around
// deflate). Until we can get that updated (and deal with previous releases still testing it
// via a DeflateStream-based implementation), we utilize httpbin.org to help validate behavior.
[OuterLoop("Uses external servers")]
[Theory]
[InlineData("http://httpbin.org/deflate", "\"deflated\": true")]
[InlineData("https://httpbin.org/deflate", "\"deflated\": true")]
[InlineData("http://httpbin.org/gzip", "\"gzipped\": true")]
[InlineData("https://httpbin.org/gzip", "\"gzipped\": true")]
public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(string uri, string expectedContent)
{
HttpClientHandler handler = CreateHttpClientHandler();
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (HttpClient client = CreateHttpClient(handler))
{
Assert.Contains(expectedContent, await client.GetStringAsync(uri));
}
}
[OuterLoop("Uses external server")]
[Theory, MemberData(nameof(RemoteServersAndCompressionUris))]
public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Configuration.Http.RemoteServer remoteServer, Uri uri)
......
......@@ -23,42 +23,6 @@ public class BrotliStreamUnitTests : CompressionStreamUnitTestBase
protected override string CompressedTestFile(string uncompressedPath) => Path.Combine("BrotliTestData", Path.GetFileName(uncompressedPath) + ".br");
[Fact]
public void Precancellation()
{
var ms = new MemoryStream();
using (Stream compressor = new BrotliStream(ms, CompressionMode.Compress, leaveOpen: true))
{
Assert.True(compressor.WriteAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
Assert.True(compressor.FlushAsync(new CancellationToken(true)).IsCanceled);
}
using (Stream decompressor = CreateStream(ms, CompressionMode.Decompress, leaveOpen: true))
{
Assert.True(decompressor.ReadAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task DisposeAsync_Flushes(bool leaveOpen)
{
var ms = new MemoryStream();
var bs = new BrotliStream(ms, CompressionMode.Compress, leaveOpen);
bs.WriteByte(1);
Assert.Equal(0, ms.Position);
await bs.DisposeAsync();
Assert.InRange(ms.ToArray().Length, 1, int.MaxValue);
if (leaveOpen)
{
Assert.InRange(ms.Position, 1, int.MaxValue);
}
else
{
Assert.Throws<ObjectDisposedException>(() => ms.Position);
}
}
[Fact]
[OuterLoop("Test takes ~6 seconds to run")]
public override void FlushAsync_DuringWriteAsync() { base.FlushAsync_DuringWriteAsync(); }
......
......@@ -121,4 +121,39 @@ public enum ZipArchiveMode
Create = 1,
Update = 2,
}
public sealed partial class ZLibStream : System.IO.Stream
{
public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel) { }
public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel, bool leaveOpen) { }
public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionMode mode) { }
public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionMode mode, bool leaveOpen) { }
public System.IO.Stream BaseStream { get { throw null; } }
public override bool CanRead { get { throw null; } }
public override bool CanSeek { get { throw null; } }
public override bool CanWrite { get { throw null; } }
public override long Length { get { throw null; } }
public override long Position { get { throw null; } set { } }
public override System.IAsyncResult BeginRead(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
public override System.IAsyncResult BeginWrite(byte[] array, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
public override void CopyTo(System.IO.Stream destination, int bufferSize) { }
public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override void Dispose(bool disposing) { }
public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
public override int EndRead(System.IAsyncResult asyncResult) { throw null; }
public override void EndWrite(System.IAsyncResult asyncResult) { }
public override void Flush() { }
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
public override int Read(byte[] array, int offset, int count) { throw null; }
public override int Read(System.Span<byte> buffer) { throw null; }
public override System.Threading.Tasks.Task<int> ReadAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override int ReadByte() { throw null; }
public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
public override void SetLength(long value) { }
public override void Write(byte[] array, int offset, int count) { }
public override void Write(System.ReadOnlySpan<byte> buffer) { }
public override void WriteByte(byte value) { }
public override System.Threading.Tasks.Task WriteAsync(byte[] array, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
}
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser</TargetFrameworks>
......@@ -33,6 +33,7 @@
<Compile Include="System\IO\Compression\Crc32Helper.ZLib.cs" />
<Compile Include="System\IO\Compression\GZipStream.cs" />
<Compile Include="System\IO\Compression\PositionPreservingWriteOnlyStreamWrapper.cs" />
<Compile Include="System\IO\Compression\ZLibStream.cs" />
<Compile Include="$(CommonPath)System\IO\StreamHelpers.CopyValidation.cs"
Link="Common\System\IO\StreamHelpers.CopyValidation.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
......
......@@ -162,7 +162,7 @@ private void EnsureNotDisposed()
private static void ThrowStreamClosedException()
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
throw new ObjectDisposedException(nameof(DeflateStream), SR.ObjectDisposed_StreamClosed);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) =>
......
......@@ -320,7 +320,7 @@ private void EnsureNotDisposed()
private static void ThrowStreamClosedException()
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
throw new ObjectDisposedException(nameof(DeflateStream), SR.ObjectDisposed_StreamClosed);
}
private void EnsureDecompressionMode()
......
......@@ -114,6 +114,14 @@ public enum CompressionMethod : int
public const int Deflate_DefaultWindowBits = -15; // Legal values are 8..15 and -8..-15. 15 is the window size,
// negative val causes deflate to produce raw deflate data (no zlib header).
/// <summary>
/// <p><strong>From the ZLib manual:</strong></p>
/// <p>ZLib's <code>windowBits</code> parameter is the base two logarithm of the window size (the size of the history buffer).
/// It should be in the range 8..15 for this version of the library. Larger values of this parameter result in better compression
/// at the expense of memory usage. The default value is 15 if deflateInit is used instead.<br /></p>
/// </summary>
public const int ZLib_DefaultWindowBits = 15;
/// <summary>
/// <p>Zlib's <code>windowBits</code> parameter is the base two logarithm of the window size (the size of the history buffer).
/// For GZip header encoding, <code>windowBits</code> should be equal to a value between 8..15 (to specify Window Size) added to
......
......@@ -234,7 +234,7 @@ private void CheckDeflateStream()
private static void ThrowStreamClosedException()
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
throw new ObjectDisposedException(nameof(GZipStream), SR.ObjectDisposed_StreamClosed);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO.Compression
{
/// <summary>Provides methods and properties used to compress and decompress streams by using the zlib data format specification.</summary>
public sealed class ZLibStream : Stream
{
/// <summary>The underlying deflate stream.</summary>
private DeflateStream _deflateStream;
/// <summary>Initializes a new instance of the <see cref="ZLibStream"/> class by using the specified stream and compression mode.</summary>
/// <param name="stream">The stream to which compressed data is written or from which decompressed data is read.</param>
/// <param name="mode">One of the enumeration values that indicates whether to compress or decompress the stream.</param>
public ZLibStream(Stream stream, CompressionMode mode) : this(stream, mode, leaveOpen: false)
{
}
/// <summary>Initializes a new instance of the <see cref="ZLibStream"/> class by using the specified stream, compression mode, and whether to leave the <paramref name="stream"/> open.</summary>
/// <param name="stream">The stream to which compressed data is written or from which decompressed data is read.</param>
/// <param name="mode">One of the enumeration values that indicates whether to compress or decompress the stream.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream object open after disposing the <see cref="ZLibStream"/> object; otherwise, <see langword="false" />.</param>
public ZLibStream(Stream stream, CompressionMode mode, bool leaveOpen)
{
_deflateStream = new DeflateStream(stream, mode, leaveOpen, ZLibNative.ZLib_DefaultWindowBits);
}
/// <summary>Initializes a new instance of the <see cref="ZLibStream"/> class by using the specified stream and compression level.</summary>
/// <param name="stream">The stream to which compressed data is written.</param>
/// <param name="compressionLevel">One of the enumeration values that indicates whether to emphasize speed or compression efficiency when compressing the stream.</param>
public ZLibStream(Stream stream, CompressionLevel compressionLevel) : this(stream, compressionLevel, leaveOpen: false)
{
}
/// <summary>Initializes a new instance of the <see cref="ZLibStream"/> class by using the specified stream, compression level, and whether to leave the <paramref name="stream"/> open.</summary>
/// <param name="stream">The stream to which compressed data is written.</param>
/// <param name="compressionLevel">One of the enumeration values that indicates whether to emphasize speed or compression efficiency when compressing the stream.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream object open after disposing the <see cref="ZLibStream"/> object; otherwise, <see langword="false" />.</param>
public ZLibStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen)
{
_deflateStream = new DeflateStream(stream, compressionLevel, leaveOpen, ZLibNative.ZLib_DefaultWindowBits);
}
/// <summary>Gets a value indicating whether the stream supports reading.</summary>
public override bool CanRead => _deflateStream?.CanRead ?? false;
/// <summary>Gets a value indicating whether the stream supports writing.</summary>
public override bool CanWrite => _deflateStream?.CanWrite ?? false;
/// <summary>Gets a value indicating whether the stream supports seeking.</summary>
public override bool CanSeek => false;
/// <summary>This property is not supported and always throws a <see cref="NotSupportedException"/>.</summary>
public override long Length => throw new NotSupportedException();
/// <summary>This property is not supported and always throws a <see cref="NotSupportedException"/>.</summary>
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
/// <summary>Flushes the internal buffers.</summary>
public override void Flush()
{
ThrowIfClosed();
_deflateStream.Flush();
}
/// <summary>Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests.</summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous flush operation.</returns>
public override Task FlushAsync(CancellationToken cancellationToken)
{
ThrowIfClosed();
return _deflateStream.FlushAsync(cancellationToken);
}
/// <summary>This method is not supported and always throws a <see cref="NotSupportedException"/>.</summary>
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <summary>This method is not supported and always throws a <see cref="NotSupportedException"/>.</summary>
public override void SetLength(long value) => throw new NotSupportedException();
/// <summary>Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.</summary>
/// <returns>The unsigned byte cast to an <see cref="int" />, or -1 if at the end of the stream.</returns>
public override int ReadByte()
{
ThrowIfClosed();
return _deflateStream.ReadByte();
}
/// <summary>Begins an asynchronous read operation.</summary>
/// <param name="array">The byte array to read the data into.</param>
/// <param name="offset">The byte offset in array at which to begin reading data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="asyncCallback">An optional asynchronous callback, to be called when the read operation is complete.</param>
/// <param name="asyncState">A user-provided object that distinguishes this particular asynchronous read request from other requests.</param>
/// <returns>An object that represents the asynchronous read operation, which could still be pending.</returns>
public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState)
{
ThrowIfClosed();
return _deflateStream.BeginRead(array, offset, count, asyncCallback, asyncState);
}
/// <summary>Waits for the pending asynchronous read to complete.</summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <returns>The number of bytes that were read into the byte array.</returns>
public override int EndRead(IAsyncResult asyncResult) =>
_deflateStream.EndRead(asyncResult);
/// <summary>Reads a number of decompressed bytes into the specified byte array.</summary>
/// <param name="array">The byte array to read the data into.</param>
/// <param name="offset">The byte offset in array at which to begin reading data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>The number of bytes that were read into the byte array.</returns>
public override int Read(byte[] array, int offset, int count)
{
ThrowIfClosed();
return _deflateStream.Read(array, offset, count);
}
/// <summary>Reads a number of decompressed bytes into the specified byte span.</summary>
/// <param name="buffer">The span to read the data into.</param>
/// <returns>The number of bytes that were read into the byte span.</returns>
public override int Read(Span<byte> buffer)
{
ThrowIfClosed();
return _deflateStream.ReadCore(buffer);
}
/// <summary>Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests.</summary>
/// <param name="array">The byte array to read the data into.</param>
/// <param name="offset">The byte offset in array at which to begin reading data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous completion of the operation.</returns>
public override Task<int> ReadAsync(byte[] array, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfClosed();
return _deflateStream.ReadAsync(array, offset, count, cancellationToken);
}
/// <summary>Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests.</summary>
/// <param name="buffer">The byte span to read the data into.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous completion of the operation.</returns>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfClosed();
return _deflateStream.ReadAsyncMemory(buffer, cancellationToken);
}
/// <summary>Begins an asynchronous write operation.</summary>
/// <param name="array">The buffer to write data from.</param>
/// <param name="offset">The byte offset in buffer to begin writing from.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="asyncCallback">An optional asynchronous callback, to be called when the write operation is complete.</param>
/// <param name="asyncState">A user-provided object that distinguishes this particular asynchronous write request from other requests.</param>
/// <returns>An object that represents the asynchronous write operation, which could still be pending.</returns>
public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback? asyncCallback, object? asyncState)
{
ThrowIfClosed();
return _deflateStream.BeginWrite(array, offset, count, asyncCallback, asyncState);
}
/// <summary>Ends an asynchronous write operation.</summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
public override void EndWrite(IAsyncResult asyncResult) =>
_deflateStream.EndWrite(asyncResult);
/// <summary>Writes compressed bytes to the underlying stream from the specified byte array.</summary>
/// <param name="array">The buffer to write data from.</param>
/// <param name="offset">The byte offset in buffer to begin writing from.</param>
/// <param name="count">The maximum number of bytes to write.</param>
public override void Write(byte[] array, int offset, int count)
{
ThrowIfClosed();
_deflateStream.Write(array, offset, count);
}
/// <summary>Writes compressed bytes to the underlying stream from the specified byte span.</summary>
/// <param name="buffer">The buffer to write data from.</param>
public override void Write(ReadOnlySpan<byte> buffer)
{
ThrowIfClosed();
_deflateStream.WriteCore(buffer);
}
/// <summary>Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.</summary>
/// <param name="array">The buffer to write data from.</param>
/// <param name="offset">The byte offset in buffer to begin writing from.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous completion of the operation.</returns>
public override Task WriteAsync(byte[] array, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfClosed();
return _deflateStream.WriteAsync(array, offset, count, cancellationToken);
}
/// <summary>Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.</summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous completion of the operation.</returns>
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ThrowIfClosed();
return _deflateStream.WriteAsyncMemory(buffer, cancellationToken);
}
/// <summary>Writes a byte to the current position in the stream and advances the position within the stream by one byte.</summary>
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value)
{
ThrowIfClosed();
_deflateStream.WriteByte(value);
}
/// <summary>Reads the bytes from the current stream and writes them to another stream, using the specified buffer size.</summary>
/// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
/// <param name="bufferSize">The size of the buffer. This value must be greater than zero.</param>
public override void CopyTo(Stream destination, int bufferSize)
{
ThrowIfClosed();
_deflateStream.CopyTo(destination, bufferSize);
}
/// <summary>Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.</summary>
/// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous copy operation.</returns>
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
ThrowIfClosed();
return _deflateStream.CopyToAsync(destination, bufferSize, cancellationToken);
}
/// <summary>Releases all resources used by the <see cref="Stream"/>.</summary>
/// <param name="disposing">Whether this method is being called from Dispose.</param>
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
_deflateStream?.Dispose();
}
_deflateStream = null!;
}
finally
{
base.Dispose(disposing);
}
}
/// <summary>Asynchronously releases all resources used by the <see cref="Stream"/>.</summary>
/// <returns>A task that represents the completion of the disposal operation.</returns>
public override ValueTask DisposeAsync()
{
DeflateStream? ds = _deflateStream;
if (ds is not null)
{
_deflateStream = null!;
return ds.DisposeAsync();
}
return default;
}
/// <summary>Gets a reference to the underlying stream.</summary>
public Stream BaseStream => _deflateStream?.BaseStream!;
/// <summary>Throws an <see cref="ObjectDisposedException"/> if the stream is closed.</summary>
private void ThrowIfClosed()
{
if (_deflateStream is null)
{
ThrowClosedException();
}
}
/// <summary>Throws an <see cref="ObjectDisposedException"/>.</summary>
[DoesNotReturn]
private static void ThrowClosedException() =>
throw new ObjectDisposedException(nameof(ZLibStream), SR.ObjectDisposed_StreamClosed);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
......@@ -16,34 +18,27 @@ public class DeflateStreamUnitTests : CompressionStreamUnitTestBase
public override Stream BaseStream(Stream stream) => ((DeflateStream)stream).BaseStream;
protected override string CompressedTestFile(string uncompressedPath) => Path.Combine("DeflateTestData", Path.GetFileName(uncompressedPath));
/// <summary>
/// Test to pass gzipstream data to a deflatestream
/// </summary>
[Theory]
[MemberData(nameof(UncompressedTestFiles))]
public async Task DecompressFailsWithRealGzStream(string uncompressedPath)
public static IEnumerable<object[]> DecompressFailsWithWrapperStream_MemberData()
{
string fileName = Path.Combine("GZipTestData", Path.GetFileName(uncompressedPath) + ".gz");
var baseStream = await LocalMemoryStream.readAppFileAsync(fileName);
var zip = CreateStream(baseStream, CompressionMode.Decompress);
int _bufferSize = 2048;
var bytes = new byte[_bufferSize];
Assert.Throws<InvalidDataException>(() => { zip.Read(bytes, 0, _bufferSize); });
zip.Dispose();
foreach (object[] testFile in UncompressedTestFiles())
{
yield return new object[] { testFile[0], "GZipTestData", ".gz" };
yield return new object[] { testFile[0], "ZLibTestData", ".z" };
}
}
[Fact]
public void Precancellation()
/// <summary>Test to pass GZipStream data and ZLibStream data to a DeflateStream</summary>
[Theory]
[MemberData(nameof(DecompressFailsWithWrapperStream_MemberData))]
public async Task DecompressFailsWithWrapperStream(string uncompressedPath, string newDirectory, string newSuffix)
{
var ms = new MemoryStream();
using (Stream compressor = new DeflateStream(ms, CompressionMode.Compress, leaveOpen: true))
string fileName = Path.Combine(newDirectory, Path.GetFileName(uncompressedPath) + newSuffix);
using (LocalMemoryStream baseStream = await LocalMemoryStream.readAppFileAsync(fileName))
using (Stream cs = CreateStream(baseStream, CompressionMode.Decompress))
{
Assert.True(compressor.WriteAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
Assert.True(compressor.FlushAsync(new CancellationToken(true)).IsCanceled);
}
using (Stream decompressor = CreateStream(ms, CompressionMode.Decompress, leaveOpen: true))
{
Assert.True(decompressor.ReadAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
int _bufferSize = 2048;
var bytes = new byte[_bufferSize];
Assert.Throws<InvalidDataException>(() => { cs.Read(bytes, 0, _bufferSize); });
}
}
......@@ -76,51 +71,9 @@ public void DerivedStream_ReadWriteSpan_UsesReadWriteArray()
}
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task DisposeAsync_Flushes(bool derived, bool leaveOpen)
{
var ms = new MemoryStream();
var ds = derived ?
new DerivedDeflateStream(ms, CompressionMode.Compress, leaveOpen) :
new DeflateStream(ms, CompressionMode.Compress, leaveOpen);
ds.WriteByte(1);
Assert.Equal(0, ms.Position);
await ds.DisposeAsync();
Assert.InRange(ms.ToArray().Length, 1, int.MaxValue);
if (leaveOpen)
{
Assert.InRange(ms.Position, 1, int.MaxValue);
}
else
{
Assert.Throws<ObjectDisposedException>(() => ms.Position);
}
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task DisposeAsync_MultipleCallsAllowed(bool derived, bool leaveOpen)
{
using (var ds = derived ?
new DerivedDeflateStream(new MemoryStream(), CompressionMode.Compress, leaveOpen) :
new DeflateStream(new MemoryStream(), CompressionMode.Compress, leaveOpen))
{
await ds.DisposeAsync();
await ds.DisposeAsync();
}
}
private sealed class DerivedDeflateStream : DeflateStream
{
public bool ReadArrayInvoked = false, WriteArrayInvoked = false;
internal DerivedDeflateStream(Stream stream, CompressionMode mode) : base(stream, mode) { }
internal DerivedDeflateStream(Stream stream, CompressionMode mode, bool leaveOpen) : base(stream, mode, leaveOpen) { }
public override int Read(byte[] array, int offset, int count)
......
......@@ -17,21 +17,6 @@ public class GzipStreamUnitTests : CompressionStreamUnitTestBase
public override Stream BaseStream(Stream stream) => ((GZipStream)stream).BaseStream;
protected override string CompressedTestFile(string uncompressedPath) => Path.Combine("GZipTestData", Path.GetFileName(uncompressedPath) + ".gz");
[Fact]
public void Precancellation()
{
var ms = new MemoryStream();
using (Stream compressor = new GZipStream(ms, CompressionMode.Compress, leaveOpen: true))
{
Assert.True(compressor.WriteAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
Assert.True(compressor.FlushAsync(new CancellationToken(true)).IsCanceled);
}
using (Stream decompressor = CreateStream(ms, CompressionMode.Decompress, leaveOpen: true))
{
Assert.True(decompressor.ReadAsync(new byte[1], 0, 1, new CancellationToken(true)).IsCanceled);
}
}
[Fact]
public void ConcatenatedGzipStreams()
{
......@@ -63,7 +48,8 @@ public void ConcatenatedGzipStreams()
/// that bypasses buffering.
/// </summary>
private class DerivedMemoryStream : MemoryStream
{ }
{
}
[Fact]
public async Task ConcatenatedEmptyGzipStreams()
......@@ -295,52 +281,6 @@ public void DerivedStream_ReadWriteSpan_UsesReadWriteArray()
}
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task DisposeAsync_Flushes(bool derived, bool leaveOpen)
{
var ms = new MemoryStream();
var gs = derived ?
new DerivedGZipStream(ms, CompressionMode.Compress, leaveOpen) :
new GZipStream(ms, CompressionMode.Compress, leaveOpen);
gs.WriteByte(1);
await gs.FlushAsync();
long pos = ms.Position;
gs.WriteByte(1);
Assert.Equal(pos, ms.Position);
await gs.DisposeAsync();
Assert.InRange(ms.ToArray().Length, pos + 1, int.MaxValue);
if (leaveOpen)
{
Assert.InRange(ms.Position, pos + 1, int.MaxValue);
}
else
{
Assert.Throws<ObjectDisposedException>(() => ms.Position);
}
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task DisposeAsync_MultipleCallsAllowed(bool derived, bool leaveOpen)
{
using (var gs = derived ?
new DerivedGZipStream(new MemoryStream(), CompressionMode.Compress, leaveOpen) :
new GZipStream(new MemoryStream(), CompressionMode.Compress, leaveOpen))
{
await gs.DisposeAsync();
await gs.DisposeAsync();
}
}
private sealed class DerivedGZipStream : GZipStream
{
public bool ReadArrayInvoked = false, WriteArrayInvoked = false;
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace System.IO.Compression
{
public class ZLibStreamUnitTests : CompressionStreamUnitTestBase
{
public override Stream CreateStream(Stream stream, CompressionMode mode) => new ZLibStream(stream, mode);
public override Stream CreateStream(Stream stream, CompressionMode mode, bool leaveOpen) => new ZLibStream(stream, mode, leaveOpen);
public override Stream CreateStream(Stream stream, CompressionLevel level) => new ZLibStream(stream, level);
public override Stream CreateStream(Stream stream, CompressionLevel level, bool leaveOpen) => new ZLibStream(stream, level, leaveOpen);
public override Stream BaseStream(Stream stream) => ((ZLibStream)stream).BaseStream;
protected override string CompressedTestFile(string uncompressedPath) => Path.Combine("ZLibTestData", Path.GetFileName(uncompressedPath) + ".z");
}
}
......@@ -3,6 +3,7 @@
<TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="CompressionStreamUnitTests.ZLib.cs" />
<Compile Include="CompressionStreamUnitTests.Deflate.cs" />
<Compile Include="CompressionStreamUnitTests.Gzip.cs" />
<Compile Include="Utilities\StripHeaderAndFooter.cs" />
......@@ -14,26 +15,16 @@
<Compile Include="ZipArchive\zip_netcoreappTests.cs" />
<Compile Include="ZipArchive\zip_ReadTests.cs" />
<Compile Include="ZipArchive\zip_UpdateTests.cs" />
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs"
Link="Common\System\IO\PathFeatures.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CRC.cs"
Link="Common\System\IO\Compression\CRC.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CompressionStreamTestBase.cs"
Link="Common\System\IO\Compression\CompressionStreamTestBase.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CompressionStreamUnitTestBase.cs"
Link="Common\System\IO\Compression\CompressionStreamUnitTestBase.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\FileData.cs"
Link="Common\System\IO\Compression\FileData.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\LocalMemoryStream.cs"
Link="Common\System\IO\Compression\LocalMemoryStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs"
Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs"
Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs"
Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CRC.cs" Link="Common\System\IO\Compression\CRC.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CompressionStreamTestBase.cs" Link="Common\System\IO\Compression\CompressionStreamTestBase.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\CompressionStreamUnitTestBase.cs" Link="Common\System\IO\Compression\CompressionStreamUnitTestBase.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\FileData.cs" Link="Common\System\IO\Compression\FileData.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\LocalMemoryStream.cs" Link="Common\System\IO\Compression\LocalMemoryStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\StreamHelpers.cs" Link="Common\System\IO\Compression\StreamHelpers.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\Compression\ZipTestHelper.cs" Link="Common\System\IO\Compression\ZipTestHelper.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Compression.TestData" Version="$(SystemIOCompressionTestDataVersion)" />
......
......@@ -158,18 +158,12 @@ public static byte[] CompressBytes(byte[] bytes, bool useGZip)
{
using (var memoryStream = new MemoryStream())
{
Stream compressedStream = null;
if (useGZip)
using (Stream compressedStream = useGZip ?
new GZipStream(memoryStream, CompressionMode.Compress) :
new DeflateStream(memoryStream, CompressionMode.Compress))
{
compressedStream = new GZipStream(memoryStream, CompressionMode.Compress);
compressedStream.Write(bytes, 0, bytes.Length);
}
else
{
compressedStream = new DeflateStream(memoryStream, CompressionMode.Compress);
}
compressedStream.Write(bytes, 0, bytes.Length);
compressedStream.Dispose();
return memoryStream.ToArray();
}
......
......@@ -211,7 +211,12 @@ public DeflateDecompressedContent(HttpContent originalContent)
{ }
protected override Stream GetDecompressedStream(Stream originalStream) =>
new DeflateStream(originalStream, CompressionMode.Decompress);
// As described in RFC 2616, the deflate content-coding is actually
// the "zlib" format (RFC 1950) in combination with the "deflate"
// compression algrithm (RFC 1951). So while potentially
// counterintuitive based on naming, this needs to use ZLibStream
// rather than DeflateStream.
new ZLibStream(originalStream, CompressionMode.Decompress);
}
private sealed class BrotliDecompressedContent : DecompressedContent
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册