Skip to content

Commit b9fa0c7

Browse files
authored
Allow/ignore upgrades with bodies #17081 (#28907)
1 parent 4aa76a1 commit b9fa0c7

File tree

5 files changed

+61
-34
lines changed

5 files changed

+61
-34
lines changed

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
@@ -127,13 +127,13 @@ public static MessageBody For(
127127
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
128128
}
129129

130-
if (upgrade)
130+
// Ignore upgrades if the request has a body. Technically it's possible to support, but we'd have to add a lot
131+
// more logic to allow reading/draining the normal body before the connection could be fully upgraded.
132+
// See https://tools.ietf.org/html/rfc7230#section-6.7, https://tools.ietf.org/html/rfc7540#section-3.2
133+
if (upgrade
134+
&& headers.ContentLength.GetValueOrDefault() == 0
135+
&& headers.HeaderTransferEncoding.Count == 0)
131136
{
132-
if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0))
133-
{
134-
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
135-
}
136-
137137
context.OnTrailersComplete(); // No trailers for these.
138138
return new Http1UpgradeMessageBody(context);
139139
}

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 @@ internal enum RequestRejectionReason
3232
MissingHostHeader,
3333
MultipleHostHeaders,
3434
InvalidHostHeader,
35-
UpgradeRequestCannotHavePayload,
3635
RequestBodyExceedsContentLength
3736
}
3837
}

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

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
using System;
55
using System.IO;
6-
using System.IO.Pipelines;
76
using System.Threading.Tasks;
8-
using Microsoft.AspNetCore.Connections;
97
using Microsoft.AspNetCore.Connections.Features;
8+
using Microsoft.AspNetCore.Http;
109
using Microsoft.AspNetCore.Http.Features;
1110
using Microsoft.AspNetCore.Server.Kestrel.Core;
1211
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
1312
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
1413
using Microsoft.AspNetCore.Server.Kestrel.Tests;
1514
using Microsoft.AspNetCore.Testing;
1615
using Microsoft.Extensions.Logging.Testing;
16+
using Microsoft.Net.Http.Headers;
1717
using Xunit;
1818

1919
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
@@ -154,9 +154,24 @@ await connection.Receive("HTTP/1.1 101 Switching Protocols",
154154
}
155155

156156
[Fact]
157-
public async Task RejectsRequestWithContentLengthAndUpgrade()
157+
public async Task AcceptsRequestWithContentLengthAndUpgrade()
158158
{
159-
await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
159+
await using (var server = new TestServer(async context =>
160+
{
161+
var feature = context.Features.Get<IHttpUpgradeFeature>();
162+
163+
if (HttpMethods.IsPost(context.Request.Method))
164+
{
165+
Assert.False(feature.IsUpgradableRequest);
166+
Assert.Equal(1, context.Request.ContentLength);
167+
Assert.Equal(1, await context.Request.Body.ReadAsync(new byte[10], 0, 10));
168+
}
169+
else
170+
{
171+
Assert.True(feature.IsUpgradableRequest);
172+
}
173+
},
174+
new TestServiceContext(LoggerFactory)))
160175
{
161176
using (var connection = server.CreateConnection())
162177
{
@@ -165,23 +180,28 @@ await connection.Send("POST / HTTP/1.1",
165180
"Content-Length: 1",
166181
"Connection: Upgrade",
167182
"",
168-
"");
183+
"A");
169184

170-
await connection.ReceiveEnd(
171-
"HTTP/1.1 400 Bad Request",
172-
"Connection: close",
173-
$"Date: {server.Context.DateHeaderValue}",
174-
"Content-Length: 0",
175-
"",
176-
"");
185+
await connection.Receive("HTTP/1.1 200 OK");
177186
}
178187
}
179188
}
180189

181190
[Fact]
182191
public async Task AcceptsRequestWithNoContentLengthAndUpgrade()
183192
{
184-
await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
193+
await using (var server = new TestServer(async context =>
194+
{
195+
var feature = context.Features.Get<IHttpUpgradeFeature>();
196+
Assert.True(feature.IsUpgradableRequest);
197+
198+
if (HttpMethods.IsPost(context.Request.Method))
199+
{
200+
Assert.Equal(0, context.Request.ContentLength);
201+
}
202+
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[10], 0, 10));
203+
},
204+
new TestServiceContext(LoggerFactory)))
185205
{
186206
using (var connection = server.CreateConnection())
187207
{
@@ -203,9 +223,26 @@ await connection.Send("POST / HTTP/1.1",
203223
}
204224

205225
[Fact]
206-
public async Task RejectsRequestWithChunkedEncodingAndUpgrade()
226+
public async Task AcceptsRequestWithChunkedEncodingAndUpgrade()
207227
{
208-
await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
228+
await using (var server = new TestServer(async context =>
229+
{
230+
var feature = context.Features.Get<IHttpUpgradeFeature>();
231+
232+
Assert.Null(context.Request.ContentLength);
233+
234+
if (HttpMethods.IsPost(context.Request.Method))
235+
{
236+
Assert.False(feature.IsUpgradableRequest);
237+
Assert.Equal("chunked", context.Request.Headers[HeaderNames.TransferEncoding]);
238+
Assert.Equal(11, await context.Request.Body.ReadAsync(new byte[12], 0, 12));
239+
}
240+
else
241+
{
242+
Assert.True(feature.IsUpgradableRequest);
243+
}
244+
},
245+
new TestServiceContext(LoggerFactory)))
209246
{
210247
using (var connection = server.CreateConnection())
211248
{
@@ -214,14 +251,11 @@ await connection.Send("POST / HTTP/1.1",
214251
"Transfer-Encoding: chunked",
215252
"Connection: Upgrade",
216253
"",
217-
"");
218-
await connection.ReceiveEnd(
219-
"HTTP/1.1 400 Bad Request",
220-
"Connection: close",
221-
$"Date: {server.Context.DateHeaderValue}",
222-
"Content-Length: 0",
254+
"B", "Hello World",
255+
"0",
223256
"",
224257
"");
258+
await connection.Receive("HTTP/1.1 200 OK");
225259
}
226260
}
227261
}

0 commit comments

Comments
 (0)