diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs index 8312e88be..e31bd4777 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs @@ -617,6 +617,21 @@ private void VerifyAndUpdateWrite(int count) _responseBytesWritten += count; } + protected void VerifyResponseContentLength() + { + var responseHeaders = FrameResponseHeaders; + + if (!HttpMethods.IsHead(Method) && + !responseHeaders.HasTransferEncoding && + responseHeaders.HeaderContentLengthValue.HasValue && + _responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value) + { + _keepAlive = false; + ReportApplicationError(new InvalidOperationException( + $"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value}).")); + } + } + private void WriteChunked(ArraySegment data) { SocketOutput.Write(data, chunk: true); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs index 5fc2a56d2..f2b0dac36 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http @@ -92,16 +93,7 @@ public override async Task RequestProcessingAsync() try { await _application.ProcessRequestAsync(context).ConfigureAwait(false); - - var responseHeaders = FrameResponseHeaders; - if (!responseHeaders.HasTransferEncoding && - responseHeaders.HasContentLength && - _responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value) - { - _keepAlive = false; - ReportApplicationError(new InvalidOperationException( - $"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value}).")); - } + VerifyResponseContentLength(); } catch (Exception ex) { diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs index 57e753d07..719b61cab 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs @@ -607,6 +607,62 @@ await connection.Receive( Assert.Equal(0, testLogger.ApplicationErrorsLogged); } + [Fact] + public async Task HeadResponseCanContainContentLengthHeader() + { + var testLogger = new TestApplicationErrorLogger(); + var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) }; + + using (var server = new TestServer(httpContext => + { + httpContext.Response.ContentLength = 42; + return TaskCache.CompletedTask; + }, serviceContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "HEAD / HTTP/1.1", + "", + ""); + await connection.ReceiveEnd( + $"HTTP/1.1 200 OK", + $"Date: {server.Context.DateHeaderValue}", + "Content-Length: 42", + "", + ""); + } + } + } + + [Fact] + public async Task HeadResponseCanContainContentLengthHeaderButBodyNotWritten() + { + var testLogger = new TestApplicationErrorLogger(); + var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) }; + + using (var server = new TestServer(async httpContext => + { + httpContext.Response.ContentLength = 12; + await httpContext.Response.WriteAsync("hello, world"); + }, serviceContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "HEAD / HTTP/1.1", + "", + ""); + await connection.ReceiveEnd( + $"HTTP/1.1 200 OK", + $"Date: {server.Context.DateHeaderValue}", + "Content-Length: 12", + "", + ""); + } + } + } + public static TheoryData NullHeaderData { get