Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 9f82daa

Browse files
author
Cesar Blum Silveira
committed
Reject POST and PUT requests with no Content-Length or Transfer-Encoding (#1130).
1 parent b3aca04 commit 9f82daa

File tree

6 files changed

+62
-19
lines changed

6 files changed

+62
-19
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ internal static BadHttpRequestException GetException(RequestRejectionReason reas
102102
case RequestRejectionReason.FinalTransferCodingNotChunked:
103103
ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{value}\"", 400);
104104
break;
105+
case RequestRejectionReason.LengthRequired:
106+
ex = new BadHttpRequestException($"{value} request contains no length indication (no Content-Length or Transfer-Encoding header)", 411);
107+
break;
105108
default:
106109
ex = new BadHttpRequestException("Bad request.", 400);
107110
break;

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Numerics;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
1011
using Microsoft.Extensions.Internal;
1112
using Microsoft.Extensions.Primitives;
@@ -266,11 +267,11 @@ public static MessageBody For(
266267
return new ForChunkedEncoding(keepAlive, headers, context);
267268
}
268269

269-
var unparsedContentLength = headers.HeaderContentLength.ToString();
270-
if (unparsedContentLength.Length > 0)
270+
var unparsedContentLength = headers.HeaderContentLength;
271+
if (unparsedContentLength.Count > 0)
271272
{
272273
long contentLength;
273-
if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0)
274+
if (!long.TryParse(unparsedContentLength.ToString(), out contentLength) || contentLength < 0)
274275
{
275276
context.RejectRequest(RequestRejectionReason.InvalidContentLength, unparsedContentLength);
276277
}
@@ -280,6 +281,13 @@ public static MessageBody For(
280281
}
281282
}
282283

284+
// If we got here, request contains no Content-Length or Transfer-Encoding header.
285+
// Reject with 411 Length Required.
286+
if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
287+
{
288+
context.RejectRequest(RequestRejectionReason.LengthRequired, context.Method);
289+
}
290+
283291
return new ForContentLength(keepAlive, 0, context);
284292
}
285293

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/RequestRejectionReason.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public enum RequestRejectionReason
2727
MissingCRInHeaderLine,
2828
TooManyHeaders,
2929
RequestTimeout,
30-
FinalTransferCodingNotChunked
30+
FinalTransferCodingNotChunked,
31+
LengthRequired
3132
}
3233
}

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestLineSizeTests.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
1111
public class MaxRequestLineSizeTests
1212
{
1313
[Theory]
14-
[InlineData("GET / HTTP/1.1\r\n", 16)]
15-
[InlineData("GET / HTTP/1.1\r\n", 17)]
16-
[InlineData("GET / HTTP/1.1\r\n", 137)]
17-
[InlineData("POST /abc/de HTTP/1.1\r\n", 23)]
18-
[InlineData("POST /abc/de HTTP/1.1\r\n", 24)]
19-
[InlineData("POST /abc/de HTTP/1.1\r\n", 287)]
20-
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 28)]
21-
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 29)]
22-
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 589)]
23-
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 40)]
24-
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 41)]
25-
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 1027)]
26-
public async Task ServerAcceptsRequestLineWithinLimit(string requestLine, int limit)
14+
[InlineData("GET / HTTP/1.1\r\n\r\n", 16)]
15+
[InlineData("GET / HTTP/1.1\r\n\r\n", 17)]
16+
[InlineData("GET / HTTP/1.1\r\n\r\n", 137)]
17+
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 23)]
18+
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 24)]
19+
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 287)]
20+
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 28)]
21+
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 29)]
22+
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 589)]
23+
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 40)]
24+
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 41)]
25+
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 1027)]
26+
public async Task ServerAcceptsRequestLineWithinLimit(string request, int limit)
2727
{
2828
var maxRequestLineSize = limit;
2929

3030
using (var server = CreateServer(limit))
3131
{
3232
using (var connection = new TestConnection(server.Port))
3333
{
34-
await connection.SendEnd($"{requestLine}\r\n");
34+
await connection.SendEnd(request);
3535
await connection.ReceiveEnd(
3636
"HTTP/1.1 200 OK",
3737
$"Date: {server.Context.DateHeaderValue}",

test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,21 @@ public async Task BadRequestIfPathContainsNullCharacters(string path)
174174
}
175175
}
176176

177+
[Theory]
178+
[InlineData("POST")]
179+
[InlineData("PUT")]
180+
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
181+
{
182+
using (var server = new TestServer(context => { return Task.FromResult(0); }))
183+
{
184+
using (var connection = server.CreateConnection())
185+
{
186+
await connection.SendEnd($"{method} / HTTP/1.1\r\n\r\n");
187+
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
188+
}
189+
}
190+
}
191+
177192
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
178193
{
179194
await connection.ReceiveForcedEnd(

test/Microsoft.AspNetCore.Server.KestrelTests/MessageBodyTests.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,29 @@ public void ForThrowsWhenFinalTransferCodingIsNotChunked()
196196
using (var input = new TestInput())
197197
{
198198
var ex = Assert.Throws<BadHttpRequestException>(() =>
199-
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
199+
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
200200

201201
Assert.Equal(400, ex.StatusCode);
202202
Assert.Equal("Final transfer coding is not \"chunked\": \"chunked, not-chunked\"", ex.Message);
203203
}
204204
}
205205

206+
[Theory]
207+
[InlineData("POST")]
208+
[InlineData("PUT")]
209+
public void ForThrowsWhenMethodRequiresLengthButNoContentLengthOrTransferEncodingIsSet(string method)
210+
{
211+
using (var input = new TestInput())
212+
{
213+
input.FrameContext.Method = method;
214+
var ex = Assert.Throws<BadHttpRequestException>(() =>
215+
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext));
216+
217+
Assert.Equal(411, ex.StatusCode);
218+
Assert.Equal($"{method} request contains no length indication (no Content-Length or Transfer-Encoding header)", ex.Message);
219+
}
220+
}
221+
206222
public static IEnumerable<object[]> StreamData => new[]
207223
{
208224
new object[] { new ThrowOnWriteSynchronousStream() },

0 commit comments

Comments
 (0)