More specific response status codes for errors (#653).

- 414 when request line exceeds size limit
- 431 when request headers exceed size or count limits
- 505 when request line contains unsupported HTTP version
This commit is contained in:
Cesar Blum Silveira 2016-09-21 11:08:47 -07:00
parent 63509b9e10
commit 49ff98f8cb
9 changed files with 294 additions and 152 deletions

View File

@ -8,100 +8,102 @@ namespace Microsoft.AspNetCore.Server.Kestrel
{
public sealed class BadHttpRequestException : IOException
{
private BadHttpRequestException(string message)
private BadHttpRequestException(string message, int statusCode)
: base(message)
{
StatusCode = statusCode;
}
internal int StatusCode { get; }
internal static BadHttpRequestException GetException(RequestRejectionReason reason)
{
BadHttpRequestException ex;
switch (reason)
{
case RequestRejectionReason.MissingMethod:
ex = new BadHttpRequestException("Missing method.");
ex = new BadHttpRequestException("Missing method.", 400);
break;
case RequestRejectionReason.InvalidMethod:
ex = new BadHttpRequestException("Invalid method.");
ex = new BadHttpRequestException("Invalid method.", 400);
break;
case RequestRejectionReason.MissingRequestTarget:
ex = new BadHttpRequestException("Missing request target.");
ex = new BadHttpRequestException("Missing request target.", 400);
break;
case RequestRejectionReason.MissingHTTPVersion:
ex = new BadHttpRequestException("Missing HTTP version.");
ex = new BadHttpRequestException("Missing HTTP version.", 400);
break;
case RequestRejectionReason.UnrecognizedHTTPVersion:
ex = new BadHttpRequestException("Unrecognized HTTP version.");
ex = new BadHttpRequestException("Unrecognized HTTP version.", 505);
break;
case RequestRejectionReason.MissingLFInRequestLine:
ex = new BadHttpRequestException("Missing LF in request line.");
ex = new BadHttpRequestException("Missing LF in request line.", 400);
break;
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.");
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", 400);
break;
case RequestRejectionReason.HeaderLineMustNotStartWithWhitespace:
ex = new BadHttpRequestException("Header line must not start with whitespace.");
ex = new BadHttpRequestException("Header line must not start with whitespace.", 400);
break;
case RequestRejectionReason.NoColonCharacterFoundInHeaderLine:
ex = new BadHttpRequestException("No ':' character found in header line.");
ex = new BadHttpRequestException("No ':' character found in header line.", 400);
break;
case RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName:
ex = new BadHttpRequestException("Whitespace is not allowed in header name.");
ex = new BadHttpRequestException("Whitespace is not allowed in header name.", 400);
break;
case RequestRejectionReason.HeaderValueMustNotContainCR:
ex = new BadHttpRequestException("Header value must not contain CR characters.");
ex = new BadHttpRequestException("Header value must not contain CR characters.", 400);
break;
case RequestRejectionReason.HeaderValueLineFoldingNotSupported:
ex = new BadHttpRequestException("Header value line folding not supported.");
ex = new BadHttpRequestException("Header value line folding not supported.", 400);
break;
case RequestRejectionReason.MalformedRequestInvalidHeaders:
ex = new BadHttpRequestException("Malformed request: invalid headers.");
ex = new BadHttpRequestException("Malformed request: invalid headers.", 400);
break;
case RequestRejectionReason.UnexpectedEndOfRequestContent:
ex = new BadHttpRequestException("Unexpected end of request content.");
ex = new BadHttpRequestException("Unexpected end of request content.", 400);
break;
case RequestRejectionReason.BadChunkSuffix:
ex = new BadHttpRequestException("Bad chunk suffix.");
ex = new BadHttpRequestException("Bad chunk suffix.", 400);
break;
case RequestRejectionReason.BadChunkSizeData:
ex = new BadHttpRequestException("Bad chunk size data.");
ex = new BadHttpRequestException("Bad chunk size data.", 400);
break;
case RequestRejectionReason.ChunkedRequestIncomplete:
ex = new BadHttpRequestException("Chunked request incomplete.");
ex = new BadHttpRequestException("Chunked request incomplete.", 400);
break;
case RequestRejectionReason.PathContainsNullCharacters:
ex = new BadHttpRequestException("The path contains null characters.");
ex = new BadHttpRequestException("The path contains null characters.", 400);
break;
case RequestRejectionReason.InvalidCharactersInHeaderName:
ex = new BadHttpRequestException("Invalid characters in header name.");
ex = new BadHttpRequestException("Invalid characters in header name.", 400);
break;
case RequestRejectionReason.NonAsciiOrNullCharactersInInputString:
ex = new BadHttpRequestException("The input string contains non-ASCII or null characters.");
ex = new BadHttpRequestException("The input string contains non-ASCII or null characters.", 400);
break;
case RequestRejectionReason.RequestLineTooLong:
ex = new BadHttpRequestException("Request line too long.");
ex = new BadHttpRequestException("Request line too long.", 414);
break;
case RequestRejectionReason.MissingSpaceAfterMethod:
ex = new BadHttpRequestException("No space character found after method in request line.");
ex = new BadHttpRequestException("No space character found after method in request line.", 400);
break;
case RequestRejectionReason.MissingSpaceAfterTarget:
ex = new BadHttpRequestException("No space character found after target in request line.");
ex = new BadHttpRequestException("No space character found after target in request line.", 400);
break;
case RequestRejectionReason.MissingCrAfterVersion:
ex = new BadHttpRequestException("Missing CR in request line.");
ex = new BadHttpRequestException("Missing CR in request line.", 400);
break;
case RequestRejectionReason.HeadersExceedMaxTotalSize:
ex = new BadHttpRequestException("Request headers too long.");
ex = new BadHttpRequestException("Request headers too long.", 431);
break;
case RequestRejectionReason.MissingCRInHeaderLine:
ex = new BadHttpRequestException("No CR character found in header line.");
ex = new BadHttpRequestException("No CR character found in header line.", 400);
break;
case RequestRejectionReason.TooManyHeaders:
ex = new BadHttpRequestException("Request contains too many headers.");
ex = new BadHttpRequestException("Request contains too many headers.", 431);
break;
default:
ex = new BadHttpRequestException("Bad request.");
ex = new BadHttpRequestException("Bad request.", 400);
break;
}
return ex;
@ -113,13 +115,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel
switch (reason)
{
case RequestRejectionReason.MalformedRequestLineStatus:
ex = new BadHttpRequestException($"Invalid request line: {value}");
ex = new BadHttpRequestException($"Invalid request line: {value}", 400);
break;
case RequestRejectionReason.InvalidContentLength:
ex = new BadHttpRequestException($"Invalid content length: {value}");
ex = new BadHttpRequestException($"Invalid content length: {value}", 400);
break;
default:
ex = new BadHttpRequestException("Bad request.");
ex = new BadHttpRequestException("Bad request.", 400);
break;
}
return ex;

View File

@ -640,13 +640,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return TaskCache.CompletedTask;
}
if (_requestRejected)
{
// 400 Bad Request
StatusCode = 400;
_keepAlive = false;
}
else
// If the request was rejected, StatusCode has already been set by SetBadRequestState
if (!_requestRejected)
{
// 500 Internal Server Error
StatusCode = 500;
@ -1249,6 +1244,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void SetBadRequestState(BadHttpRequestException ex)
{
StatusCode = ex.StatusCode;
_keepAlive = false;
_requestProcessingStopping = true;
_requestRejected = true;
Log.ConnectionBadRequest(ConnectionId, ex);

View File

@ -52,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private static readonly byte[] _bytesStatus423 = Encoding.ASCII.GetBytes("423 Locked");
private static readonly byte[] _bytesStatus424 = Encoding.ASCII.GetBytes("424 Failed Dependency");
private static readonly byte[] _bytesStatus426 = Encoding.ASCII.GetBytes("426 Upgrade Required");
private static readonly byte[] _bytesStatus431 = Encoding.ASCII.GetBytes("431 Request Header Fields Too Large");
private static readonly byte[] _bytesStatus451 = Encoding.ASCII.GetBytes("451 Unavailable For Legal Reasons");
private static readonly byte[] _bytesStatus500 = Encoding.ASCII.GetBytes("500 Internal Server Error");
private static readonly byte[] _bytesStatus501 = Encoding.ASCII.GetBytes("501 Not Implemented");
@ -157,6 +158,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return _bytesStatus424;
case 426:
return _bytesStatus426;
case 431:
return _bytesStatus431;
case 451:
return _bytesStatus451;
case 500:

View File

@ -1,10 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Testing;
using Xunit;
@ -30,17 +27,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var maxRequestLineSize = limit;
using (var host = BuildWebHost(options =>
using (var server = CreateServer(limit))
{
options.Limits.MaxRequestLineSize = maxRequestLineSize;
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendEnd($"{requestLine}\r\n");
await connection.Receive($"HTTP/1.1 200 OK\r\n");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
}
}
@ -52,33 +53,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n")]
public async Task ServerRejectsRequestLineExceedingLimit(string requestLine)
{
using (var host = BuildWebHost(options =>
using (var server = CreateServer(requestLine.Length - 1))
{
options.Limits.MaxRequestLineSize = requestLine.Length - 1; // stop short of the '\n'
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendAllTryEnd($"{requestLine}\r\n");
await connection.Receive($"HTTP/1.1 400 Bad Request\r\n");
await connection.Receive(
"HTTP/1.1 414 URI Too Long",
"Connection: close",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
private IWebHost BuildWebHost(Action<KestrelServerOptions> options)
private TestServer CreateServer(int maxRequestLineSize)
{
var host = new WebHostBuilder()
.UseKestrel(options)
.UseUrls("http://127.0.0.1:0/")
.Configure(app => app.Run(async context =>
return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext
{
ServerOptions = new KestrelServerOptions
{
await context.Response.WriteAsync("hello, world");
}))
.Build();
return host;
AddServerHeader = false,
Limits =
{
MaxRequestLineSize = maxRequestLineSize
}
}
});
}
}
}

View File

@ -1,12 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Testing;
using Xunit;
@ -28,17 +24,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var headers = MakeHeaders(headerCount);
using (var host = BuildWebHost(options =>
using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length + extraLimit))
{
options.Limits.MaxRequestHeadersTotalSize = headers.Length + extraLimit;
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendEnd($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.Receive($"HTTP/1.1 200 OK\r\n");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
}
}
@ -56,17 +56,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var headers = MakeHeaders(headerCount);
using (var host = BuildWebHost(options =>
using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount))
{
options.Limits.MaxRequestHeaderCount = maxHeaderCount;
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendEnd($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.Receive($"HTTP/1.1 200 OK\r\n");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
}
}
@ -78,17 +82,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var headers = MakeHeaders(headerCount);
using (var host = BuildWebHost(options =>
using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length - 1))
{
options.Limits.MaxRequestHeadersTotalSize = headers.Length - 1;
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendAllTryEnd($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.Receive($"HTTP/1.1 400 Bad Request\r\n");
await connection.ReceiveForcedEnd(
"HTTP/1.1 431 Request Header Fields Too Large",
"Connection: close",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
@ -101,17 +106,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var headers = MakeHeaders(headerCount);
using (var host = BuildWebHost(options =>
using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount))
{
options.Limits.MaxRequestHeaderCount = maxHeaderCount;
}))
{
host.Start();
using (var connection = new TestConnection(host.GetPort()))
using (var connection = new TestConnection(server.Port))
{
await connection.SendAllTryEnd($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.Receive($"HTTP/1.1 400 Bad Request\r\n");
await connection.ReceiveForcedEnd(
"HTTP/1.1 431 Request Header Fields Too Large",
"Connection: close",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
@ -123,18 +129,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
.Select(i => $"Header-{i}: value{i}\r\n"));
}
private static IWebHost BuildWebHost(Action<KestrelServerOptions> options)
private TestServer CreateServer(int? maxRequestHeaderCount = null, int? maxRequestHeadersTotalSize = null)
{
var host = new WebHostBuilder()
.UseKestrel(options)
.UseUrls("http://127.0.0.1:0/")
.Configure(app => app.Run(async context =>
{
await context.Response.WriteAsync("hello, world");
}))
.Build();
var options = new KestrelServerOptions { AddServerHeader = false };
return host;
if (maxRequestHeaderCount.HasValue)
{
options.Limits.MaxRequestHeaderCount = maxRequestHeaderCount.Value;
}
if (maxRequestHeadersTotalSize.HasValue)
{
options.Limits.MaxRequestHeadersTotalSize = maxRequestHeadersTotalSize.Value;
}
return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext
{
ServerOptions = options
});
}
}
}

View File

@ -19,13 +19,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("GET \r\n")]
[InlineData("GET /\r\n")]
[InlineData("GET / \r\n")]
[InlineData("GET / H\r\n")]
[InlineData("GET / HT\r\n")]
[InlineData("GET / HTT\r\n")]
[InlineData("GET / HTTP\r\n")]
[InlineData("GET / HTTP/\r\n")]
[InlineData("GET / HTTP/1\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
// Missing method
[InlineData(" \r\n")]
// Missing second space
@ -37,18 +30,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("GET / \r\n")]
// Missing CR
[InlineData("GET / \n")]
// Unrecognized HTTP version
[InlineData("GET / http/1.0\r\n")]
[InlineData("GET / http/1.1\r\n")]
[InlineData("GET / HTTP/1.1 \r\n")]
[InlineData("GET / HTTP/1.1a\r\n")]
[InlineData("GET / HTTP/1.0\n\r\n")]
[InlineData("GET / HTTP/1.2\r\n")]
[InlineData("GET / HTTP/3.0\r\n")]
[InlineData("GET / H\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
[InlineData("GET / hello\r\n")]
[InlineData("GET / 8charact\r\n")]
// Missing LF after CR
[InlineData("GET / HTTP/1.0\rA\n")]
// Bad HTTP Methods (invalid according to RFC)
@ -78,7 +59,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var connection = server.CreateConnection())
{
await connection.SendAllTryEnd(request);
await ReceiveBadRequestResponse(connection, server.Context.DateHeaderValue);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("GET / H\r\n")]
[InlineData("GET / HT\r\n")]
[InlineData("GET / HTT\r\n")]
[InlineData("GET / HTTP\r\n")]
[InlineData("GET / HTTP/\r\n")]
[InlineData("GET / HTTP/1\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
[InlineData("GET / http/1.0\r\n")]
[InlineData("GET / http/1.1\r\n")]
[InlineData("GET / HTTP/1.1 \r\n")]
[InlineData("GET / HTTP/1.1a\r\n")]
[InlineData("GET / HTTP/1.2\r\n")]
[InlineData("GET / HTTP/3.0\r\n")]
[InlineData("GET / H\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
[InlineData("GET / hello\r\n")]
[InlineData("GET / 8charact\r\n")]
public async Task TestInvalidRequestLinesWithUnsupportedVersion(string request)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAllTryEnd(request);
await ReceiveBadRequestResponse(connection, "505 HTTP Version Not Supported", server.Context.DateHeaderValue);
}
}
}
@ -114,7 +125,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var connection = server.CreateConnection())
{
await connection.SendAllTryEnd($"GET / HTTP/1.1\r\n{rawHeaders}");
await ReceiveBadRequestResponse(connection, server.Context.DateHeaderValue);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
@ -131,7 +142,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"H\u00eb\u00e4d\u00ebr: value",
"",
"");
await ReceiveBadRequestResponse(connection, server.Context.DateHeaderValue);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
@ -158,15 +169,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var connection = server.CreateConnection())
{
await connection.SendAllTryEnd($"GET {path} HTTP/1.1\r\n");
await ReceiveBadRequestResponse(connection, server.Context.DateHeaderValue);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedDateHeaderValue)
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
{
await connection.ReceiveForcedEnd(
"HTTP/1.1 400 Bad Request",
$"HTTP/1.1 {expectedResponseStatusCode}",
"Connection: close",
$"Date: {expectedDateHeaderValue}",
"Content-Length: 0",

View File

@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"",
"");
await connection.ReceiveForcedEnd(
"HTTP/1.1 400 Bad Request",
"HTTP/1.1 431 Request Header Fields Too Large",
"Connection: close",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 0",
@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"",
"");
await connection.ReceiveForcedEnd(
"HTTP/1.1 400 Bad Request",
"HTTP/1.1 431 Request Header Fields Too Large",
"Connection: close",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 0",

View File

@ -1,3 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
@ -243,8 +246,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
const string key = "\u00141ód\017c";
var encoding = Encoding.GetEncoding("iso-8859-1");
Assert.Throws<BadHttpRequestException>(
var exception = Assert.Throws<BadHttpRequestException>(
() => headers.Append(encoding.GetBytes(key), 0, encoding.GetByteCount(key), key));
Assert.Equal(400, exception.StatusCode);
}
}
}

View File

@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: line1\r\n\t\tline2\r\n\r\n")]
[InlineData("Header: line1\r\n \t\t line2\r\n\r\n")]
[InlineData("Header: line1\r\n \t \t line2\r\n\r\n")]
public void ThrowsOnHeaderValueWithLineFolding(string rawHeaders)
public void TakeMessageHeadersThrowsOnHeaderValueWithLineFolding(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -210,11 +210,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Header value line folding not supported.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
[Fact]
public void ThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
public void TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -241,6 +242,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Header value line folding not supported.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
@ -250,7 +252,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: v\ralue2\r\n")]
public void ThrowsOnHeaderValueContainingCR(string rawHeaders)
public void TakeMessageHeadersThrowsOnHeaderValueContainingCR(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -273,6 +275,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Header value must not contain CR characters.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
@ -280,7 +283,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1 value1\r\n\r\n")]
[InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")]
public void ThrowsOnHeaderLineMissingColon(string rawHeaders)
public void TakeMessageHeadersThrowsOnHeaderLineMissingColon(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -303,6 +306,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("No ':' character found in header line.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
@ -311,7 +315,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("\tHeader: value\r\n\r\n")]
[InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")]
public void ThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
public void TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -334,6 +338,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Header line must not start with whitespace.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
@ -346,7 +351,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")]
public void ThrowsOnWhitespaceInHeaderName(string rawHeaders)
public void TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -369,6 +374,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
@ -376,7 +382,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r\r")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r ")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r \n")]
public void ThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders)
public void TakeMessageHeadersThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -399,11 +405,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Headers corrupted, invalid header sequence.", exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
[Fact]
public void ThrowsWhenHeadersExceedTotalSizeLimit()
public void TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -432,11 +439,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Request headers too long.", exception.Message);
Assert.Equal(431, exception.StatusCode);
}
}
[Fact]
public void ThrowsWhenHeadersExceedCountLimit()
public void TakeMessageHeadersThrowsWhenHeadersExceedCountLimit()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
@ -465,6 +473,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
Assert.Equal("Request contains too many headers.", exception.Message);
Assert.Equal(431, exception.StatusCode);
}
}
@ -842,6 +851,107 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
}
[Fact]
public void TakeStartLineThrowsWhenTooLong()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionContext = new ConnectionContext()
{
ConnectionControl = Mock.Of<IConnectionControl>(),
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions()
{
Limits =
{
MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length
}
},
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
Assert.Equal("Request line too long.", exception.Message);
Assert.Equal(414, exception.StatusCode);
}
}
[Theory]
[InlineData("GET/HTTP/1.1\r\n", "No space character found after method in request line.")]
[InlineData(" / HTTP/1.1\r\n", "Missing method.")]
[InlineData("GET? / HTTP/1.1\r\n", "Invalid method.")]
[InlineData("GET /HTTP/1.1\r\n", "No space character found after target in request line.")]
[InlineData("GET /a?b=cHTTP/1.1\r\n", "No space character found after target in request line.")]
[InlineData("GET /a%20bHTTP/1.1\r\n", "No space character found after target in request line.")]
[InlineData("GET /a%20b?c=dHTTP/1.1\r\n", "No space character found after target in request line.")]
[InlineData("GET HTTP/1.1\r\n", "Missing request target.")]
[InlineData("GET / HTTP/1.1\n", "Missing CR in request line.")]
[InlineData("GET / \r\n", "Missing HTTP version.")]
[InlineData("GET / HTTP/1.1\ra\n", "Missing LF in request line.")]
public void TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionContext = new ConnectionContext()
{
ConnectionControl = Mock.Of<IConnectionControl>(),
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
Assert.Equal(expectedExceptionMessage, exception.Message);
Assert.Equal(400, exception.StatusCode);
}
}
[Fact]
public void TakeStartLineThrowsOnUnsupportedHttpVersion()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionContext = new ConnectionContext()
{
ConnectionControl = Mock.Of<IConnectionControl>(),
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n");
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
Assert.Equal("Unrecognized HTTP version.", exception.Message);
Assert.Equal(505, exception.StatusCode);
}
}
[Fact]
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
{