Reject POST and PUT requests with no Content-Length or Transfer-Encoding (#1130).
This commit is contained in:
parent
5b65117b64
commit
a1c598733b
|
|
@ -102,6 +102,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
case RequestRejectionReason.FinalTransferCodingNotChunked:
|
||||
ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{value}\"", 400);
|
||||
break;
|
||||
case RequestRejectionReason.LengthRequired:
|
||||
ex = new BadHttpRequestException($"{value} request contains no Content-Length or Transfer-Encoding header", 411);
|
||||
break;
|
||||
case RequestRejectionReason.LengthRequiredHttp10:
|
||||
ex = new BadHttpRequestException($"{value} request contains no Content-Length header", 400);
|
||||
break;
|
||||
default:
|
||||
ex = new BadHttpRequestException("Bad request.", 400);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
|||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -266,17 +267,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return new ForChunkedEncoding(keepAlive, headers, context);
|
||||
}
|
||||
|
||||
var unparsedContentLength = headers.HeaderContentLength.ToString();
|
||||
if (unparsedContentLength.Length > 0)
|
||||
var unparsedContentLength = headers.HeaderContentLength;
|
||||
if (unparsedContentLength.Count > 0)
|
||||
{
|
||||
long contentLength;
|
||||
if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0)
|
||||
try
|
||||
{
|
||||
var contentLength = FrameHeaders.ParseContentLength(unparsedContentLength);
|
||||
return new ForContentLength(keepAlive, contentLength, context);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
context.RejectRequest(RequestRejectionReason.InvalidContentLength, unparsedContentLength);
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
// Avoid slowing down most common case
|
||||
if (!object.ReferenceEquals(context.Method, HttpMethods.Get))
|
||||
{
|
||||
// If we got here, request contains no Content-Length or Transfer-Encoding header.
|
||||
// Reject with 411 Length Required.
|
||||
if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
|
||||
{
|
||||
return new ForContentLength(keepAlive, contentLength, context);
|
||||
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
|
||||
context.RejectRequest(requestRejectionReason, context.Method);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
MissingCRInHeaderLine,
|
||||
TooManyHeaders,
|
||||
RequestTimeout,
|
||||
FinalTransferCodingNotChunked
|
||||
FinalTransferCodingNotChunked,
|
||||
LengthRequired,
|
||||
LengthRequiredHttp10,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
public class MaxRequestLineSizeTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("GET / HTTP/1.1\r\n", 16)]
|
||||
[InlineData("GET / HTTP/1.1\r\n", 17)]
|
||||
[InlineData("GET / HTTP/1.1\r\n", 137)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\n", 23)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\n", 24)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\n", 287)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 28)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 29)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 589)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 40)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 41)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 1027)]
|
||||
public async Task ServerAcceptsRequestLineWithinLimit(string requestLine, int limit)
|
||||
[InlineData("GET / HTTP/1.1\r\n\r\n", 16)]
|
||||
[InlineData("GET / HTTP/1.1\r\n\r\n", 17)]
|
||||
[InlineData("GET / HTTP/1.1\r\n\r\n", 137)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 23)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 24)]
|
||||
[InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 287)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 28)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 29)]
|
||||
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 589)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 40)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 41)]
|
||||
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 1027)]
|
||||
public async Task ServerAcceptsRequestLineWithinLimit(string request, int limit)
|
||||
{
|
||||
var maxRequestLineSize = limit;
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await connection.SendEnd($"{requestLine}\r\n");
|
||||
await connection.SendEnd(request);
|
||||
await connection.ReceiveEnd(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
|
|
|
|||
|
|
@ -174,6 +174,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
|
||||
{
|
||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.SendEnd($"{method} / HTTP/1.1\r\n\r\n");
|
||||
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
|
||||
{
|
||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.SendEnd($"{method} / HTTP/1.0\r\n\r\n");
|
||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
|
||||
{
|
||||
await connection.ReceiveForcedEnd(
|
||||
|
|
|
|||
|
|
@ -196,13 +196,45 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
using (var input = new TestInput())
|
||||
{
|
||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
|
||||
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
|
||||
|
||||
Assert.Equal(400, ex.StatusCode);
|
||||
Assert.Equal("Final transfer coding is not \"chunked\": \"chunked, not-chunked\"", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public void ForThrowsWhenMethodRequiresLengthButNoContentLengthOrTransferEncodingIsSet(string method)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
input.FrameContext.Method = method;
|
||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext));
|
||||
|
||||
Assert.Equal(411, ex.StatusCode);
|
||||
Assert.Equal($"{method} request contains no Content-Length or Transfer-Encoding header", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public void ForThrowsWhenMethodRequiresLengthButNoContentLengthSetHttp10(string method)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
input.FrameContext.Method = method;
|
||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext));
|
||||
|
||||
Assert.Equal(400, ex.StatusCode);
|
||||
Assert.Equal($"{method} request contains no Content-Length header", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> StreamData => new[]
|
||||
{
|
||||
new object[] { new ThrowOnWriteSynchronousStream() },
|
||||
|
|
|
|||
Loading…
Reference in New Issue