Skip to content

Commit 86249d5

Browse files
authored
[2.1] Allow/ignore upgrades with bodies (#28908)
* Allow/ignore upgrades with bodies #17081 * Update patchconfig.props
1 parent 0b598a9 commit 86249d5

File tree

6 files changed

+50
-33
lines changed

6 files changed

+50
-33
lines changed

eng/PatchConfig.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,9 @@ Later on, this will be checked using this condition:
8383
Microsoft.AspNetCore.DataProtection.AzureStorage;
8484
</PackagesInPatch>
8585
</PropertyGroup>
86+
<PropertyGroup Condition=" '$(VersionPrefix)' == '2.1.25' ">
87+
<PackagesInPatch>
88+
Microsoft.AspNetCore.Server.Kestrel.Core;
89+
</PackagesInPatch>
90+
</PropertyGroup>
8691
</Project>

src/Servers/Kestrel/Core/src/BadHttpRequestException.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ internal static BadHttpRequestException GetException(RequestRejectionReason reas
111111
case RequestRejectionReason.InvalidHostHeader:
112112
ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidHostHeader, StatusCodes.Status400BadRequest, reason);
113113
break;
114-
case RequestRejectionReason.UpgradeRequestCannotHavePayload:
115-
ex = new BadHttpRequestException(CoreStrings.BadRequest_UpgradeRequestCannotHavePayload, StatusCodes.Status400BadRequest, reason);
116-
break;
117114
default:
118115
ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
119116
break;

src/Servers/Kestrel/Core/src/CoreStrings.resx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,6 @@
198198
<data name="BadRequest_UnrecognizedHTTPVersion" xml:space="preserve">
199199
<value>Unrecognized HTTP version: '{detail}'</value>
200200
</data>
201-
<data name="BadRequest_UpgradeRequestCannotHavePayload" xml:space="preserve">
202-
<value>Requests with 'Connection: Upgrade' cannot have content in the request body.</value>
203-
</data>
204201
<data name="FallbackToIPv4Any" xml:space="preserve">
205202
<value>Failed to bind to http://[::]:{port} (IPv6Any). Attempting to bind to http://0.0.0.0:{port} instead.</value>
206203
</data>

src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,13 @@ public static MessageBody For(
271271
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
272272
}
273273

274-
if (upgrade)
274+
// Ignore upgrades if the request has a body. Technically it's possible to support, but we'd have to add a lot
275+
// more logic to allow reading/draining the normal body before the connection could be fully upgraded.
276+
// See https://tools.ietf.org/html/rfc7230#section-6.7, https://tools.ietf.org/html/rfc7540#section-3.2
277+
if (upgrade
278+
&& headers.ContentLength.GetValueOrDefault() == 0
279+
&& headers.HeaderTransferEncoding.Count == 0)
275280
{
276-
if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0))
277-
{
278-
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
279-
}
280-
281281
return new ForUpgrade(context);
282282
}
283283

src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public enum RequestRejectionReason
3232
MissingHostHeader,
3333
MultipleHostHeaders,
3434
InvalidHostHeader,
35-
UpgradeRequestCannotHavePayload,
3635
RequestBodyExceedsContentLength
3736
}
3837
}

src/Servers/Kestrel/test/FunctionalTests/UpgradeTests.cs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55
using System.IO;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Http;
78
using Microsoft.AspNetCore.Http.Features;
89
using Microsoft.AspNetCore.Server.Kestrel.Core;
910
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1011
using Microsoft.AspNetCore.Server.Kestrel.Tests;
1112
using Microsoft.AspNetCore.Testing;
1213
using Microsoft.Extensions.Logging.Testing;
14+
using Microsoft.Net.Http.Headers;
1315
using Xunit;
1416

1517
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
@@ -146,32 +148,43 @@ await connection.Receive("HTTP/1.1 101 Switching Protocols",
146148
}
147149

148150
[Fact]
149-
public async Task RejectsRequestWithContentLengthAndUpgrade()
151+
public async Task AcceptsRequestWithContentLengthAndUpgrade()
150152
{
151-
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
153+
using (var server = new TestServer(async context =>
154+
{
155+
var feature = context.Features.Get<IHttpUpgradeFeature>();
156+
Assert.False(feature.IsUpgradableRequest);
157+
Assert.Equal(1, context.Request.ContentLength);
158+
Assert.Equal(1, await context.Request.Body.ReadAsync(new byte[10], 0, 10));
159+
}, new TestServiceContext(LoggerFactory)))
152160
using (var connection = server.CreateConnection())
153161
{
154162
await connection.Send("POST / HTTP/1.1",
155163
"Host:",
156164
"Content-Length: 1",
157165
"Connection: Upgrade",
158166
"",
159-
"");
167+
"A");
160168

161-
await connection.ReceiveForcedEnd(
162-
"HTTP/1.1 400 Bad Request",
163-
"Connection: close",
164-
$"Date: {server.Context.DateHeaderValue}",
165-
"Content-Length: 0",
166-
"",
167-
"");
169+
await connection.Receive("HTTP/1.1 200 OK");
168170
}
169171
}
170172

171173
[Fact]
172174
public async Task AcceptsRequestWithNoContentLengthAndUpgrade()
173175
{
174-
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
176+
using (var server = new TestServer(async context =>
177+
{
178+
var feature = context.Features.Get<IHttpUpgradeFeature>();
179+
Assert.True(feature.IsUpgradableRequest);
180+
181+
if (HttpMethods.IsPost(context.Request.Method))
182+
{
183+
Assert.Equal(0, context.Request.ContentLength);
184+
}
185+
186+
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[10], 0, 10));
187+
}, new TestServiceContext(LoggerFactory)))
175188
{
176189
using (var connection = server.CreateConnection())
177190
{
@@ -193,24 +206,30 @@ await connection.Send("POST / HTTP/1.1",
193206
}
194207

195208
[Fact]
196-
public async Task RejectsRequestWithChunkedEncodingAndUpgrade()
209+
public async Task AcceptsRequestWithChunkedEncodingAndUpgrade()
197210
{
198-
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
211+
using (var server = new TestServer(async context =>
212+
{
213+
var feature = context.Features.Get<IHttpUpgradeFeature>();
214+
Assert.Null(context.Request.ContentLength);
215+
Assert.False(feature.IsUpgradableRequest);
216+
Assert.True(HttpMethods.IsPost(context.Request.Method));
217+
Assert.False(feature.IsUpgradableRequest);
218+
Assert.Equal("chunked", context.Request.Headers[HeaderNames.TransferEncoding]);
219+
Assert.Equal(11, await context.Request.Body.ReadAsync(new byte[12], 0, 12));
220+
}, new TestServiceContext(LoggerFactory)))
199221
using (var connection = server.CreateConnection())
200222
{
201223
await connection.Send("POST / HTTP/1.1",
202224
"Host:",
203225
"Transfer-Encoding: chunked",
204226
"Connection: Upgrade",
205227
"",
206-
"");
207-
await connection.ReceiveForcedEnd(
208-
"HTTP/1.1 400 Bad Request",
209-
"Connection: close",
210-
$"Date: {server.Context.DateHeaderValue}",
211-
"Content-Length: 0",
228+
"B", "Hello World",
229+
"0",
212230
"",
213231
"");
232+
await connection.Receive("HTTP/1.1 200 OK");
214233
}
215234
}
216235

0 commit comments

Comments
 (0)