Consolidate BadHttpRequestException messages

This commit is contained in:
Ben Adams 2016-07-19 16:51:04 +01:00
parent 27f8c8dca2
commit 0620ce5a2f
10 changed files with 189 additions and 52 deletions

View File

@ -2,15 +2,106 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO; using System.IO;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel namespace Microsoft.AspNetCore.Server.Kestrel
{ {
public sealed class BadHttpRequestException : IOException public sealed class BadHttpRequestException : IOException
{ {
internal BadHttpRequestException(string message) private BadHttpRequestException(string message)
: base(message) : base(message)
{ {
} }
internal static BadHttpRequestException GetException(RequestRejectionReasons reason)
{
BadHttpRequestException ex;
switch (reason)
{
case RequestRejectionReasons.MissingMethod:
ex = new BadHttpRequestException("Missing method.");
break;
case RequestRejectionReasons.InvalidMethod:
ex = new BadHttpRequestException("Invalid method.");
break;
case RequestRejectionReasons.MissingRequestTarget:
ex = new BadHttpRequestException("Missing request target.");
break;
case RequestRejectionReasons.MissingHTTPVersion:
ex = new BadHttpRequestException("Missing HTTP version.");
break;
case RequestRejectionReasons.UnrecognizedHTTPVersion:
ex = new BadHttpRequestException("Unrecognized HTTP version.");
break;
case RequestRejectionReasons.MissingLFInRequestLine:
ex = new BadHttpRequestException("Missing LF in request line.");
break;
case RequestRejectionReasons.HeadersCorruptedInvalidHeaderSequence:
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.");
break;
case RequestRejectionReasons.HeaderLineMustNotStartWithWhitespace:
ex = new BadHttpRequestException("Header line must not start with whitespace.");
break;
case RequestRejectionReasons.NoColonCharacterFoundInHeaderLine:
ex = new BadHttpRequestException("No ':' character found in header line.");
break;
case RequestRejectionReasons.WhitespaceIsNotAllowedInHeaderName:
ex = new BadHttpRequestException("Whitespace is not allowed in header name.");
break;
case RequestRejectionReasons.HeaderLineMustEndInCRLFOnlyCRFound:
ex = new BadHttpRequestException("Header line must end in CRLF; only CR found.");
break;
case RequestRejectionReasons.HeaderValueLineFoldingNotSupported:
ex = new BadHttpRequestException("Header value line folding not supported.");
break;
case RequestRejectionReasons.MalformedRequestInvalidHeaders:
ex = new BadHttpRequestException("Malformed request: invalid headers.");
break;
case RequestRejectionReasons.UnexpectedEndOfRequestContent:
ex = new BadHttpRequestException("Unexpected end of request content");
break;
case RequestRejectionReasons.BadChunkSuffix:
ex = new BadHttpRequestException("Bad chunk suffix");
break;
case RequestRejectionReasons.BadChunkSizeData:
ex = new BadHttpRequestException("Bad chunk size data");
break;
case RequestRejectionReasons.ChunkedRequestIncomplete:
ex = new BadHttpRequestException("Chunked request incomplete");
break;
case RequestRejectionReasons.PathContainsNullCharacters:
ex = new BadHttpRequestException("The path contains null characters.");
break;
case RequestRejectionReasons.InvalidCharactersInHeaderName:
ex = new BadHttpRequestException("Invalid characters in header name.");
break;
case RequestRejectionReasons.NonAsciiOrNullCharactersInInputString:
ex = new BadHttpRequestException("The input string contains non-ASCII or null characters.");
break;
default:
ex = new BadHttpRequestException("Bad request.");
break;
}
return ex;
}
internal static BadHttpRequestException GetException(RequestRejectionReasons reason, string value)
{
BadHttpRequestException ex;
switch (reason)
{
case RequestRejectionReasons.MalformedRequestLineStatus:
ex = new BadHttpRequestException($"Malformed request: {value}");
break;
case RequestRejectionReasons.InvalidContentLength:
ex = new BadHttpRequestException($"Invalid content length: {value}");
break;
default:
ex = new BadHttpRequestException("Bad request.");
break;
}
return ex;
}
} }
} }

View File

@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (HasResponseStarted) if (HasResponseStarted)
{ {
throw new InvalidOperationException("Status code cannot be set, response has already started."); ThrowResponseAlreadyStartedException(nameof(StatusCode));
} }
_statusCode = value; _statusCode = value;
@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (HasResponseStarted) if (HasResponseStarted)
{ {
throw new InvalidOperationException("Reason phrase cannot be set, response had already started."); ThrowResponseAlreadyStartedException(nameof(ReasonPhrase));
} }
_reasonPhrase = value; _reasonPhrase = value;
@ -569,13 +569,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (_applicationException != null) if (_applicationException != null)
{ {
throw new ObjectDisposedException( ThrowResponseAbortedException();
"The response has been aborted due to an unhandled application exception.",
_applicationException);
} }
ProduceStart(appCompleted: false); ProduceStart(appCompleted: false);
return TaskUtilities.CompletedTask; return TaskUtilities.CompletedTask;
} }
@ -585,9 +582,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (_applicationException != null) if (_applicationException != null)
{ {
throw new ObjectDisposedException( ThrowResponseAbortedException();
"The response has been aborted due to an unhandled application exception.",
_applicationException);
} }
ProduceStart(appCompleted: false); ProduceStart(appCompleted: false);
@ -819,7 +814,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (method == null) if (method == null)
{ {
RejectRequest("Missing method."); RejectRequest(RequestRejectionReasons.MissingMethod);
} }
// Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of) // Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of)
@ -828,7 +823,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (!IsValidTokenChar(method[i])) if (!IsValidTokenChar(method[i]))
{ {
RejectRequest("Invalid method."); RejectRequest(RequestRejectionReasons.InvalidMethod);
} }
} }
} }
@ -873,7 +868,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (pathBegin.Peek() == ' ') if (pathBegin.Peek() == ' ')
{ {
RejectRequest("Missing request target."); RejectRequest(RequestRejectionReasons.MissingRequestTarget);
} }
scan.Take(); scan.Take();
@ -895,11 +890,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (httpVersion == null) if (httpVersion == null)
{ {
RejectRequest("Missing HTTP version."); RejectRequest(RequestRejectionReasons.MissingHTTPVersion);
} }
else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1") else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1")
{ {
RejectRequest("Unrecognized HTTP version."); RejectRequest(RequestRejectionReasons.UnrecognizedHTTPVersion);
} }
} }
@ -911,7 +906,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (next != '\n') else if (next != '\n')
{ {
RejectRequest("Missing LF in request line."); RejectRequest(RequestRejectionReasons.MissingLFInRequestLine);
} }
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
@ -1065,11 +1060,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
// Headers don't end in CRLF line. // Headers don't end in CRLF line.
RejectRequest("Headers corrupted, invalid header sequence."); RejectRequest(RequestRejectionReasons.HeadersCorruptedInvalidHeaderSequence);
} }
else if (ch == ' ' || ch == '\t') else if (ch == ' ' || ch == '\t')
{ {
RejectRequest("Header line must not start with whitespace."); RejectRequest(RequestRejectionReasons.HeaderLineMustNotStartWithWhitespace);
} }
var beginName = scan; var beginName = scan;
@ -1082,13 +1077,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
ch = scan.Take(); ch = scan.Take();
if (ch != ':') if (ch != ':')
{ {
RejectRequest("No ':' character found in header line."); RejectRequest(RequestRejectionReasons.NoColonCharacterFoundInHeaderLine);
} }
var validateName = beginName; var validateName = beginName;
if (validateName.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorColons) != ':') if (validateName.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorColons) != ':')
{ {
RejectRequest("Whitespace is not allowed in header name."); RejectRequest(RequestRejectionReasons.WhitespaceIsNotAllowedInHeaderName);
} }
var beginValue = scan; var beginValue = scan;
@ -1128,7 +1123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (ch != '\n') else if (ch != '\n')
{ {
RejectRequest("Header line must end in CRLF; only CR found."); RejectRequest(RequestRejectionReasons.HeaderLineMustEndInCRLFOnlyCRFound);
} }
var next = scan.Peek(); var next = scan.Peek();
@ -1155,7 +1150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// explaining that obsolete line folding is unacceptable, or replace // explaining that obsolete line folding is unacceptable, or replace
// each received obs-fold with one or more SP octets prior to // each received obs-fold with one or more SP octets prior to
// interpreting the field value or forwarding the message downstream. // interpreting the field value or forwarding the message downstream.
RejectRequest("Header value line folding not supported."); RejectRequest(RequestRejectionReasons.HeaderValueLineFoldingNotSupported);
} }
// Trim trailing whitespace from header value by repeatedly advancing to next // Trim trailing whitespace from header value by repeatedly advancing to next
@ -1203,9 +1198,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
statusCode != 304; statusCode != 304;
} }
public void RejectRequest(string message) private void ThrowResponseAlreadyStartedException(string value)
{ {
var ex = new BadHttpRequestException(message); throw new InvalidOperationException(value + " cannot be set, response had already started.");
}
private void ThrowResponseAbortedException()
{
throw new ObjectDisposedException(
"The response has been aborted due to an unhandled application exception.",
_applicationException);
}
public void RejectRequest(RequestRejectionReasons reason)
{
var ex = BadHttpRequestException.GetException(reason);
SetBadRequestState(ex);
throw ex;
}
public void RejectRequest(RequestRejectionReasons reason, string value)
{
var ex = BadHttpRequestException.GetException(reason, value);
SetBadRequestState(ex); SetBadRequestState(ex);
throw ex; throw ex;
} }

View File

@ -4903,7 +4903,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (!AsciiUtilities.TryGetAsciiString(ptr, keyBuffer, keyLength)) if (!AsciiUtilities.TryGetAsciiString(ptr, keyBuffer, keyLength))
{ {
throw new BadHttpRequestException("Invalid characters in header name"); throw BadHttpRequestException.GetException(RequestRejectionReasons.InvalidCharactersInHeaderName);
} }
} }
} }

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (_isReadOnly) if (_isReadOnly)
{ {
ThrowReadOnlyException(); ThrowHeadersReadOnlyException();
} }
SetValueFast(key, value); SetValueFast(key, value);
} }
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
protected void ThrowReadOnlyException() protected void ThrowHeadersReadOnlyException()
{ {
throw new InvalidOperationException("Headers are read-only, response has already started."); throw new InvalidOperationException("Headers are read-only, response has already started.");
} }
@ -144,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (_isReadOnly) if (_isReadOnly)
{ {
ThrowReadOnlyException(); ThrowHeadersReadOnlyException();
} }
AddValueFast(key, value); AddValueFast(key, value);
} }
@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (_isReadOnly) if (_isReadOnly)
{ {
ThrowReadOnlyException(); ThrowHeadersReadOnlyException();
} }
ClearFast(); ClearFast();
} }
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (_isReadOnly) if (_isReadOnly)
{ {
ThrowReadOnlyException(); ThrowHeadersReadOnlyException();
} }
return RemoveFast(key); return RemoveFast(key);
} }
@ -226,10 +226,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (ch < 0x20 || ch > 0x7E) if (ch < 0x20 || ch > 0x7E)
{ {
throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch)); ThrowInvalidHeaderCharacter(ch);
} }
} }
} }
} }
private static void ThrowInvalidHeaderCharacter(char ch)
{
throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));
}
} }
} }

View File

@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (requestLineStatus != RequestLineStatus.Done) if (requestLineStatus != RequestLineStatus.Done)
{ {
RejectRequest($"Malformed request: {requestLineStatus}"); RejectRequest(RequestRejectionReasons.MalformedRequestLineStatus, requestLineStatus.ToString());
} }
break; break;
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// sent immediately before the a FIN from the client. // sent immediately before the a FIN from the client.
if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders)) if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders))
{ {
RejectRequest($"Malformed request: invalid headers."); RejectRequest(RequestRejectionReasons.MalformedRequestInvalidHeaders);
} }
break; break;

View File

@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
long contentLength; long contentLength;
if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0) if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0)
{ {
context.RejectRequest($"Invalid content length: {unparsedContentLength}"); context.RejectRequest(RequestRejectionReasons.InvalidContentLength, unparsedContentLength);
} }
else else
{ {
@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_inputLength -= actual; _inputLength -= actual;
if (actual == 0) if (actual == 0)
{ {
_context.RejectRequest("Unexpected end of request content"); _context.RejectRequest(RequestRejectionReasons.UnexpectedEndOfRequestContent);
} }
return new ValueTask<int>(actual); return new ValueTask<int>(actual);
} }
@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_inputLength -= actual; _inputLength -= actual;
if (actual == 0) if (actual == 0)
{ {
_context.RejectRequest("Unexpected end of request content"); _context.RejectRequest(RequestRejectionReasons.UnexpectedEndOfRequestContent);
} }
return actual; return actual;
@ -249,7 +249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (fin) else if (fin)
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
await input; await input;
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (fin) else if (fin)
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
await input; await input;
@ -289,7 +289,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (fin) else if (fin)
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
await input; await input;
@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (fin) else if (fin)
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
await input; await input;
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else if (fin) else if (fin)
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
await input; await input;
@ -345,7 +345,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else else
{ {
ThrowChunkedRequestIncomplete(); _context.RejectRequest(RequestRejectionReasons.ChunkedRequestIncomplete);
} }
} }
@ -504,7 +504,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else else
{ {
_context.RejectRequest("Bad chunk suffix"); _context.RejectRequest(RequestRejectionReasons.BadChunkSuffix);
} }
} }
finally finally
@ -560,15 +560,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
_context.RejectRequest("Bad chunk size data"); _context.RejectRequest(RequestRejectionReasons.BadChunkSizeData);
return -1; // can't happen, but compiler complains return -1; // can't happen, but compiler complains
} }
private void ThrowChunkedRequestIncomplete()
{
_context.RejectRequest("Chunked request incomplete");
}
private enum Mode private enum Mode
{ {
Prefix, Prefix,

View File

@ -0,0 +1,31 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public enum RequestRejectionReasons
{
MissingMethod,
InvalidMethod,
MissingRequestTarget,
MissingHTTPVersion,
UnrecognizedHTTPVersion,
MissingLFInRequestLine,
HeadersCorruptedInvalidHeaderSequence,
HeaderLineMustNotStartWithWhitespace,
NoColonCharacterFoundInHeaderLine,
WhitespaceIsNotAllowedInHeaderName,
HeaderLineMustEndInCRLFOnlyCRFound,
HeaderValueLineFoldingNotSupported,
MalformedRequestLineStatus,
MalformedRequestInvalidHeaders,
InvalidContentLength,
UnexpectedEndOfRequestContent,
BadChunkSuffix,
BadChunkSizeData,
ChunkedRequestIncomplete,
PathContainsNullCharacters,
InvalidCharactersInHeaderName,
NonAsciiOrNullCharactersInInputString
}
}

View File

@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (byte1 == 0) if (byte1 == 0)
{ {
throw new BadHttpRequestException("The path contains null characters."); throw BadHttpRequestException.GetException(RequestRejectionReasons.PathContainsNullCharacters);
} }
if (byte1 == -1) if (byte1 == -1)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{ {
@ -115,7 +116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{ {
if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following)) if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following))
{ {
throw new BadHttpRequestException("The input string contains non-ASCII or null characters."); throw BadHttpRequestException.GetException(RequestRejectionReasons.NonAsciiOrNullCharactersInInputString);
} }
outputOffset += following; outputOffset += following;

View File

@ -450,7 +450,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{{ {{
if (!AsciiUtilities.TryGetAsciiString(ptr, keyBuffer, keyLength)) if (!AsciiUtilities.TryGetAsciiString(ptr, keyBuffer, keyLength))
{{ {{
throw new BadHttpRequestException(""Invalid characters in header name""); throw BadHttpRequestException.GetException(RequestRejectionReasons.InvalidCharactersInHeaderName);
}} }}
}} }}
}} }}