diff --git a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs index e75f7e7255..3d808838b9 100644 --- a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs @@ -230,6 +230,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive; } + if (upgrade) + { + if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0)) + { + context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); + } + + return new ForUpgrade(context); + } + var transferEncoding = headers.HeaderTransferEncoding; if (transferEncoding.Count > 0) { @@ -246,11 +256,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http context.ThrowRequestRejected(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString()); } - if (upgrade) - { - context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); - } - return new ForChunkedEncoding(keepAlive, context); } @@ -262,10 +267,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; } - else if (upgrade) - { - context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); - } return new ForContentLength(keepAlive, contentLength, context); } @@ -282,11 +283,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - if (upgrade) - { - return new ForUpgrade(context); - } - return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; } diff --git a/test/Kestrel.Core.Tests/MessageBodyTests.cs b/test/Kestrel.Core.Tests/MessageBodyTests.cs index 76ecd86f7f..9fa5b6ef1a 100644 --- a/test/Kestrel.Core.Tests/MessageBodyTests.cs +++ b/test/Kestrel.Core.Tests/MessageBodyTests.cs @@ -503,6 +503,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } } + [Fact] + public async Task UpgradeConnectionAcceptsContentLengthZero() + { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // "A user agent SHOULD NOT send a Content-Length header field when the request message does not contain + // a payload body and the method semantics do not anticipate such a body." + // ==> it can actually send that header + var headerConnection = "Upgrade, Keep-Alive"; + using (var input = new TestInput()) + { + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderConnection = headerConnection, ContentLength = 0 }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); + stream.StartAcceptingReads(body); + + input.Add("Hello"); + + var buffer = new byte[1024]; + Assert.Equal(5, await stream.ReadAsync(buffer, 0, buffer.Length)); + AssertASCII("Hello", new ArraySegment(buffer, 0, 5)); + + input.Fin(); + + await body.StopAsync(); + } + } + [Fact] public async Task PumpAsyncDoesNotReturnAfterCancelingInput() {