From ab968f7e940bc2a998ccd3154e028b64d908ee1f Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 5 Mar 2019 16:22:38 -0800 Subject: [PATCH 1/2] Support conditional compression #6925 --- ...AspNetCore.Http.Features.netstandard2.0.cs | 10 ++ .../Http.Features/src/HttpsCompressionMode.cs | 28 ++++ .../src/IHttpsCompressionFeature.cs | 16 +++ .../src/BodyWrapperStream.cs | 4 +- .../src/ResponseCompressionMiddleware.cs | 3 + .../src/ResponseCompressionOptions.cs | 8 +- .../src/ResponseCompressionProvider.cs | 17 ++- .../test/ResponseCompressionMiddlewareTest.cs | 122 +++++++++++++++++- ...ft.AspNetCore.StaticFiles.netcoreapp3.0.cs | 1 + .../samples/StaticFileSample/Startup.cs | 3 + .../StaticFileSample/StaticFileSample.csproj | 3 +- .../StaticFiles/src/StaticFileContext.cs | 12 ++ .../StaticFiles/src/StaticFileOptions.cs | 9 ++ .../test/UnitTests/StaticFileContextTest.cs | 59 ++++++++- 14 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 src/Http/Http.Features/src/HttpsCompressionMode.cs create mode 100644 src/Http/Http.Features/src/IHttpsCompressionFeature.cs diff --git a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs index 8cfeb642560b..4c2ccde3ccc2 100644 --- a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs +++ b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs @@ -131,6 +131,12 @@ public partial struct FeatureReference public T Fetch(Microsoft.AspNetCore.Http.Features.IFeatureCollection features) { throw null; } public T Update(Microsoft.AspNetCore.Http.Features.IFeatureCollection features, T feature) { throw null; } } + public enum HttpsCompressionMode + { + Compress = 2, + Default = 0, + DoNotCompress = 1, + } public partial interface IFeatureCollection : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { bool IsReadOnly { get; } @@ -207,6 +213,10 @@ public partial interface IHttpResponseTrailersFeature { Microsoft.AspNetCore.Http.IHeaderDictionary Trailers { get; set; } } + public partial interface IHttpsCompressionFeature + { + Microsoft.AspNetCore.Http.Features.HttpsCompressionMode Mode { get; set; } + } public partial interface IHttpSendFileFeature { System.Threading.Tasks.Task SendFileAsync(string path, long offset, long? count, System.Threading.CancellationToken cancellation); diff --git a/src/Http/Http.Features/src/HttpsCompressionMode.cs b/src/Http/Http.Features/src/HttpsCompressionMode.cs new file mode 100644 index 000000000000..13cfd3895f4f --- /dev/null +++ b/src/Http/Http.Features/src/HttpsCompressionMode.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// Use to dynamically control response compression for HTTPS requests. + /// + public enum HttpsCompressionMode + { + /// + /// No value has been specified, use the configured defaults. + /// + Default, + + /// + /// Opts out of compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content + /// may expose security problems. + /// + DoNotCompress, + + /// + /// Opts into compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content + /// may expose security problems. + /// + Compress, + } +} diff --git a/src/Http/Http.Features/src/IHttpsCompressionFeature.cs b/src/Http/Http.Features/src/IHttpsCompressionFeature.cs new file mode 100644 index 000000000000..04b05bfaf584 --- /dev/null +++ b/src/Http/Http.Features/src/IHttpsCompressionFeature.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// Configures response compression behavior for HTTPS on a per-request basis. + /// + public interface IHttpsCompressionFeature + { + /// + /// The to use. + /// + HttpsCompressionMode Mode { get; set; } + } +} diff --git a/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs b/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs index 2b756bf04151..9dfe8616dbd9 100644 --- a/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs +++ b/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.ResponseCompression /// /// Stream wrapper that create specific compression stream only if necessary. /// - internal class BodyWrapperStream : Stream, IHttpBufferingFeature, IHttpSendFileFeature, IHttpResponseStartFeature + internal class BodyWrapperStream : Stream, IHttpBufferingFeature, IHttpSendFileFeature, IHttpResponseStartFeature, IHttpsCompressionFeature { private readonly HttpContext _context; private readonly Stream _bodyOriginalStream; @@ -46,6 +46,8 @@ internal ValueTask FinishCompressionAsync() return _compressionStream?.DisposeAsync() ?? new ValueTask(); } + HttpsCompressionMode IHttpsCompressionFeature.Mode { get; set; } + public override bool CanRead => false; public override bool CanSeek => false; diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs index 5c8b6447545a..c6962ef91976 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs @@ -55,11 +55,13 @@ public async Task Invoke(HttpContext context) var originalBufferFeature = context.Features.Get(); var originalSendFileFeature = context.Features.Get(); var originalStartFeature = context.Features.Get(); + var originalCompressionFeature = context.Features.Get(); var bodyWrapperStream = new BodyWrapperStream(context, bodyStream, _provider, originalBufferFeature, originalSendFileFeature, originalStartFeature); context.Response.Body = bodyWrapperStream; context.Features.Set(bodyWrapperStream); + context.Features.Set(bodyWrapperStream); if (originalSendFileFeature != null) { context.Features.Set(bodyWrapperStream); @@ -79,6 +81,7 @@ public async Task Invoke(HttpContext context) { context.Response.Body = bodyStream; context.Features.Set(originalBufferFeature); + context.Features.Set(originalCompressionFeature); if (originalSendFileFeature != null) { context.Features.Set(originalSendFileFeature); diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionOptions.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionOptions.cs index 45168d04d50d..f8d768d80735 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionOptions.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionOptions.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.ResponseCompression { @@ -22,8 +23,11 @@ public class ResponseCompressionOptions /// /// Indicates if responses over HTTPS connections should be compressed. The default is 'false'. - /// Enabling compression on HTTPS connections may expose security problems. + /// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems. /// + /// + /// This can be overridden per request using . + /// public bool EnableForHttps { get; set; } = false; /// diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs index 8b2c2222e932..d42085698c5a 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.ResponseCompression.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -170,6 +171,16 @@ public virtual ICompressionProvider GetCompressionProvider(HttpContext context) /// public virtual bool ShouldCompressResponse(HttpContext context) { + var httpsMode = context.Features.Get()?.Mode ?? HttpsCompressionMode.Default; + + if (context.Request.IsHttps + && (httpsMode == HttpsCompressionMode.DoNotCompress + || !(_enableForHttps || httpsMode == HttpsCompressionMode.Compress))) + { + _logger.NoCompressionForHttps(); + return false; + } + if (context.Response.Headers.ContainsKey(HeaderNames.ContentRange)) { _logger.NoCompressionDueToHeader(HeaderNames.ContentRange); @@ -215,12 +226,6 @@ public virtual bool ShouldCompressResponse(HttpContext context) /// public bool CheckRequestAcceptsCompression(HttpContext context) { - if (context.Request.IsHttps && !_enableForHttps) - { - _logger.NoCompressionForHttps(); - return false; - } - if (string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding])) { _logger.NoAcceptEncoding(); diff --git a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs index aef87c545055..e7cd281b6335 100644 --- a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs +++ b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs @@ -453,7 +453,127 @@ public async Task Request_Https_CompressedIfEnabled(bool enableHttps, int expect } else { - AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); + AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); + } + } + + [Theory] + [InlineData(false, 100)] + [InlineData(true, 30)] + public async Task Request_Https_CompressedIfOverriden(bool overrideHttps, int expectedLength) + { + var sink = new TestSink( + TestSink.EnableWithTypeName, + TestSink.EnableWithTypeName); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.AddSingleton(loggerFactory); + services.AddResponseCompression(options => + { + options.EnableForHttps = false; + options.MimeTypes = new[] { TextPlain }; + }); + }) + .Configure(app => + { + app.UseResponseCompression(); + app.Run(context => + { + if (overrideHttps) + { + var feature = context.Features.Get(); + feature.Mode = HttpsCompressionMode.Compress; + } + context.Response.ContentType = TextPlain; + return context.Response.WriteAsync(new string('a', 100)); + }); + }); + + var server = new TestServer(builder) + { + BaseAddress = new Uri("https://localhost/") + }; + + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, ""); + request.Headers.AcceptEncoding.ParseAdd("gzip"); + + var response = await client.SendAsync(request); + + Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length); + + var logMessages = sink.Writes.ToList(); + if (overrideHttps) + { + AssertCompressedWithLog(logMessages, "gzip"); + } + else + { + AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); + } + } + + [Theory] + [InlineData(true, 100)] + [InlineData(false, 30)] + public async Task Request_Https_NotCompressedIfOverriden(bool overrideHttps, int expectedLength) + { + var sink = new TestSink( + TestSink.EnableWithTypeName, + TestSink.EnableWithTypeName); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.AddSingleton(loggerFactory); + services.AddResponseCompression(options => + { + options.EnableForHttps = true; + options.MimeTypes = new[] { TextPlain }; + }); + }) + .Configure(app => + { + app.UseResponseCompression(); + app.Run(context => + { + if (overrideHttps) + { + var feature = context.Features.Get(); + feature.Mode = HttpsCompressionMode.DoNotCompress; + } + context.Response.ContentType = TextPlain; + return context.Response.WriteAsync(new string('a', 100)); + }); + }); + + var server = new TestServer(builder) + { + BaseAddress = new Uri("https://localhost/") + }; + + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, ""); + request.Headers.AcceptEncoding.ParseAdd("gzip"); + + var response = await client.SendAsync(request); + + Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length); + + var logMessages = sink.Writes.ToList(); + if (overrideHttps) + { + AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); + } + else + { + AssertCompressedWithLog(logMessages, "gzip"); } } diff --git a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs index 06fc6c792cd5..76cc1985ba84 100644 --- a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs +++ b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs @@ -55,6 +55,7 @@ public StaticFileOptions() : base (default(Microsoft.AspNetCore.StaticFiles.Infr public StaticFileOptions(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions sharedOptions) : base (default(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions)) { } public Microsoft.AspNetCore.StaticFiles.IContentTypeProvider ContentTypeProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string DefaultContentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.Features.HttpsCompressionMode HttpsCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Action OnPrepareResponse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool ServeUnknownFileTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } diff --git a/src/Middleware/StaticFiles/samples/StaticFileSample/Startup.cs b/src/Middleware/StaticFiles/samples/StaticFileSample/Startup.cs index 861b7c396e09..cd504755daf2 100644 --- a/src/Middleware/StaticFiles/samples/StaticFileSample/Startup.cs +++ b/src/Middleware/StaticFiles/samples/StaticFileSample/Startup.cs @@ -12,12 +12,15 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); + services.AddResponseCompression(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment host) { Console.WriteLine("webroot: " + host.WebRootPath); + app.UseResponseCompression(); + app.UseFileServer(new FileServerOptions { EnableDirectoryBrowsing = true diff --git a/src/Middleware/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj b/src/Middleware/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj index 830cfd2a917c..96301766f247 100644 --- a/src/Middleware/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj +++ b/src/Middleware/StaticFiles/samples/StaticFileSample/StaticFileSample.csproj @@ -1,10 +1,11 @@ - + netcoreapp3.0 + diff --git a/src/Middleware/StaticFiles/src/StaticFileContext.cs b/src/Middleware/StaticFiles/src/StaticFileContext.cs index e38b600f7ba6..c21e3df7fe2b 100644 --- a/src/Middleware/StaticFiles/src/StaticFileContext.cs +++ b/src/Middleware/StaticFiles/src/StaticFileContext.cs @@ -322,6 +322,7 @@ public Task SendStatusAsync(int statusCode) public async Task SendAsync() { + SetCompressionMode(); ApplyResponseHeaders(Constants.Status200Ok); string physicalPath = _fileInfo.PhysicalPath; var sendFile = _context.Features.Get(); @@ -366,6 +367,7 @@ internal async Task SendRangeAsync() _responseHeaders.ContentRange = ComputeContentRange(_range, out var start, out var length); _response.ContentLength = length; + SetCompressionMode(); ApplyResponseHeaders(Constants.Status206PartialContent); string physicalPath = _fileInfo.PhysicalPath; @@ -404,5 +406,15 @@ private ContentRangeHeaderValue ComputeContentRange(RangeItemHeaderValue range, length = end - start + 1; return new ContentRangeHeaderValue(start, end, _length); } + + // Only called when we expect to serve the body. + private void SetCompressionMode() + { + var responseCompressionFeature = _context.Features.Get(); + if (responseCompressionFeature != null) + { + responseCompressionFeature.Mode = _options.HttpsCompression; + } + } } } diff --git a/src/Middleware/StaticFiles/src/StaticFileOptions.cs b/src/Middleware/StaticFiles/src/StaticFileOptions.cs index 01cef16b686a..978cfce65184 100644 --- a/src/Middleware/StaticFiles/src/StaticFileOptions.cs +++ b/src/Middleware/StaticFiles/src/StaticFileOptions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles.Infrastructure; @@ -46,6 +47,14 @@ public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions) /// public bool ServeUnknownFileTypes { get; set; } + /// + /// Indicates if files should be compressed for HTTPS requests. The default value is . + /// + /// + /// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems. + /// + public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress; + /// /// Called after the status code and headers have been set, but before the body has been written. /// This can be used to add or change the response headers. diff --git a/src/Middleware/StaticFiles/test/UnitTests/StaticFileContextTest.cs b/src/Middleware/StaticFiles/test/UnitTests/StaticFileContextTest.cs index f4fca87a3647..d61609657a26 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/StaticFileContextTest.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/StaticFileContextTest.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; @@ -54,6 +56,54 @@ public void LookupFileInfo_ReturnsTrue_IfFileExists() Assert.True(result); } + [Fact] + public async Task EnablesHttpsCompression_IfMatched() + { + var options = new StaticFileOptions(); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile("/foo.txt", new TestFileInfo + { + LastModified = new DateTimeOffset(2014, 1, 2, 3, 4, 5, TimeSpan.Zero) + }); + var pathString = new PathString("/test"); + var httpContext = new DefaultHttpContext(); + var httpsCompressionFeature = new TestHttpsCompressionFeature(); + httpContext.Features.Set(httpsCompressionFeature); + httpContext.Request.Path = new PathString("/test/foo.txt"); + var context = new StaticFileContext(httpContext, options, pathString, NullLogger.Instance, fileProvider, new FileExtensionContentTypeProvider()); + + context.ValidatePath(); + var result = context.LookupFileInfo(); + Assert.True(result); + + await context.SendAsync(); + + Assert.Equal(HttpsCompressionMode.Compress, httpsCompressionFeature.Mode); + } + + [Fact] + public void SkipsHttpsCompression_IfNotMatched() + { + var options = new StaticFileOptions(); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile("/foo.txt", new TestFileInfo + { + LastModified = new DateTimeOffset(2014, 1, 2, 3, 4, 5, TimeSpan.Zero) + }); + var pathString = new PathString("/test"); + var httpContext = new DefaultHttpContext(); + var httpsCompressionFeature = new TestHttpsCompressionFeature(); + httpContext.Features.Set(httpsCompressionFeature); + httpContext.Request.Path = new PathString("/test/bar.txt"); + var context = new StaticFileContext(httpContext, options, pathString, NullLogger.Instance, fileProvider, new FileExtensionContentTypeProvider()); + + context.ValidatePath(); + var result = context.LookupFileInfo(); + Assert.False(result); + + Assert.Equal(HttpsCompressionMode.Default, httpsCompressionFeature.Mode); + } + private sealed class TestFileProvider : IFileProvider { private readonly Dictionary _files = new Dictionary(StringComparer.Ordinal); @@ -162,8 +212,13 @@ public bool IsDirectory public Stream CreateReadStream() { - throw new NotImplementedException(); + return new MemoryStream(); } } + + private class TestHttpsCompressionFeature : IHttpsCompressionFeature + { + public HttpsCompressionMode Mode { get; set; } + } } -} \ No newline at end of file +} From 8f234848d85900fb40edc3ed7743b0ff8476b78e Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 7 Mar 2019 09:19:47 -0800 Subject: [PATCH 2/2] PR feedback --- .../Http.Features/src/HttpsCompressionMode.cs | 2 +- .../src/BodyWrapperStream.cs | 2 +- .../src/ResponseCompressionProvider.cs | 1 + .../test/ResponseCompressionMiddlewareTest.cs | 32 ++++++++----------- .../StaticFiles/src/StaticFileOptions.cs | 3 +- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Http/Http.Features/src/HttpsCompressionMode.cs b/src/Http/Http.Features/src/HttpsCompressionMode.cs index 13cfd3895f4f..0ed2bcc2d686 100644 --- a/src/Http/Http.Features/src/HttpsCompressionMode.cs +++ b/src/Http/Http.Features/src/HttpsCompressionMode.cs @@ -11,7 +11,7 @@ public enum HttpsCompressionMode /// /// No value has been specified, use the configured defaults. /// - Default, + Default = 0, /// /// Opts out of compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content diff --git a/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs b/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs index 9dfe8616dbd9..0a89bd83d0f3 100644 --- a/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs +++ b/src/Middleware/ResponseCompression/src/BodyWrapperStream.cs @@ -46,7 +46,7 @@ internal ValueTask FinishCompressionAsync() return _compressionStream?.DisposeAsync() ?? new ValueTask(); } - HttpsCompressionMode IHttpsCompressionFeature.Mode { get; set; } + HttpsCompressionMode IHttpsCompressionFeature.Mode { get; set; } = HttpsCompressionMode.Default; public override bool CanRead => false; diff --git a/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs b/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs index d42085698c5a..a0e00b077a6b 100644 --- a/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs +++ b/src/Middleware/ResponseCompression/src/ResponseCompressionProvider.cs @@ -173,6 +173,7 @@ public virtual bool ShouldCompressResponse(HttpContext context) { var httpsMode = context.Features.Get()?.Mode ?? HttpsCompressionMode.Default; + // Check if the app has opted into or out of compression over HTTPS if (context.Request.IsHttps && (httpsMode == HttpsCompressionMode.DoNotCompress || !(_enableForHttps || httpsMode == HttpsCompressionMode.Compress))) diff --git a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs index e7cd281b6335..9255adcf2985 100644 --- a/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs +++ b/src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs @@ -458,9 +458,10 @@ public async Task Request_Https_CompressedIfEnabled(bool enableHttps, int expect } [Theory] - [InlineData(false, 100)] - [InlineData(true, 30)] - public async Task Request_Https_CompressedIfOverriden(bool overrideHttps, int expectedLength) + [InlineData(HttpsCompressionMode.Default, 100)] + [InlineData(HttpsCompressionMode.DoNotCompress, 100)] + [InlineData(HttpsCompressionMode.Compress, 30)] + public async Task Request_Https_CompressedIfOptIn(HttpsCompressionMode mode, int expectedLength) { var sink = new TestSink( TestSink.EnableWithTypeName, @@ -482,11 +483,8 @@ public async Task Request_Https_CompressedIfOverriden(bool overrideHttps, int ex app.UseResponseCompression(); app.Run(context => { - if (overrideHttps) - { - var feature = context.Features.Get(); - feature.Mode = HttpsCompressionMode.Compress; - } + var feature = context.Features.Get(); + feature.Mode = mode; context.Response.ContentType = TextPlain; return context.Response.WriteAsync(new string('a', 100)); }); @@ -507,7 +505,7 @@ public async Task Request_Https_CompressedIfOverriden(bool overrideHttps, int ex Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length); var logMessages = sink.Writes.ToList(); - if (overrideHttps) + if (mode == HttpsCompressionMode.Compress) { AssertCompressedWithLog(logMessages, "gzip"); } @@ -518,9 +516,10 @@ public async Task Request_Https_CompressedIfOverriden(bool overrideHttps, int ex } [Theory] - [InlineData(true, 100)] - [InlineData(false, 30)] - public async Task Request_Https_NotCompressedIfOverriden(bool overrideHttps, int expectedLength) + [InlineData(HttpsCompressionMode.Default, 30)] + [InlineData(HttpsCompressionMode.Compress, 30)] + [InlineData(HttpsCompressionMode.DoNotCompress, 100)] + public async Task Request_Https_NotCompressedIfOptOut(HttpsCompressionMode mode, int expectedLength) { var sink = new TestSink( TestSink.EnableWithTypeName, @@ -542,11 +541,8 @@ public async Task Request_Https_NotCompressedIfOverriden(bool overrideHttps, int app.UseResponseCompression(); app.Run(context => { - if (overrideHttps) - { - var feature = context.Features.Get(); - feature.Mode = HttpsCompressionMode.DoNotCompress; - } + var feature = context.Features.Get(); + feature.Mode = mode; context.Response.ContentType = TextPlain; return context.Response.WriteAsync(new string('a', 100)); }); @@ -567,7 +563,7 @@ public async Task Request_Https_NotCompressedIfOverriden(bool overrideHttps, int Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length); var logMessages = sink.Writes.ToList(); - if (overrideHttps) + if (mode == HttpsCompressionMode.DoNotCompress) { AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps."); } diff --git a/src/Middleware/StaticFiles/src/StaticFileOptions.cs b/src/Middleware/StaticFiles/src/StaticFileOptions.cs index 978cfce65184..6a1710d698b4 100644 --- a/src/Middleware/StaticFiles/src/StaticFileOptions.cs +++ b/src/Middleware/StaticFiles/src/StaticFileOptions.cs @@ -48,7 +48,8 @@ public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions) public bool ServeUnknownFileTypes { get; set; } /// - /// Indicates if files should be compressed for HTTPS requests. The default value is . + /// Indicates if files should be compressed for HTTPS requests when the Response Compression middleware is available. + /// The default value is . /// /// /// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems.