Log rejected request targets.
This commit is contained in:
parent
b612da4e6c
commit
49f09d5a25
|
|
@ -1,6 +1,6 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26222.1
|
VisualStudioVersion = 15.0.26228.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|
@ -29,6 +29,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
|
||||||
test\shared\MockSocketOutput.cs = test\shared\MockSocketOutput.cs
|
test\shared\MockSocketOutput.cs = test\shared\MockSocketOutput.cs
|
||||||
test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs
|
test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs
|
||||||
test\shared\SocketInputExtensions.cs = test\shared\SocketInputExtensions.cs
|
test\shared\SocketInputExtensions.cs = test\shared\SocketInputExtensions.cs
|
||||||
|
test\shared\StringExtensions.cs = test\shared\StringExtensions.cs
|
||||||
test\shared\TestApp.cs = test\shared\TestApp.cs
|
test\shared\TestApp.cs = test\shared\TestApp.cs
|
||||||
test\shared\TestApplicationErrorLogger.cs = test\shared\TestApplicationErrorLogger.cs
|
test\shared\TestApplicationErrorLogger.cs = test\shared\TestApplicationErrorLogger.cs
|
||||||
test\shared\TestConnection.cs = test\shared\TestConnection.cs
|
test\shared\TestConnection.cs = test\shared\TestConnection.cs
|
||||||
|
|
|
||||||
|
|
@ -87,31 +87,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static BadHttpRequestException GetException(RequestRejectionReason reason, string value)
|
internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail)
|
||||||
{
|
{
|
||||||
BadHttpRequestException ex;
|
BadHttpRequestException ex;
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case RequestRejectionReason.InvalidRequestLine:
|
case RequestRejectionReason.InvalidRequestLine:
|
||||||
ex = new BadHttpRequestException($"Invalid request line: '{value}'", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException($"Invalid request line: '{detail}'", StatusCodes.Status400BadRequest);
|
||||||
|
break;
|
||||||
|
case RequestRejectionReason.InvalidRequestTarget:
|
||||||
|
ex = new BadHttpRequestException($"Invalid request target: '{detail}'", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.InvalidRequestHeader:
|
case RequestRejectionReason.InvalidRequestHeader:
|
||||||
ex = new BadHttpRequestException($"Invalid request header: '{value}'", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException($"Invalid request header: '{detail}'", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.InvalidContentLength:
|
case RequestRejectionReason.InvalidContentLength:
|
||||||
ex = new BadHttpRequestException($"Invalid content length: {value}", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException($"Invalid content length: {detail}", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.UnrecognizedHTTPVersion:
|
case RequestRejectionReason.UnrecognizedHTTPVersion:
|
||||||
ex = new BadHttpRequestException($"Unrecognized HTTP version: {value}", StatusCodes.Status505HttpVersionNotsupported);
|
ex = new BadHttpRequestException($"Unrecognized HTTP version: '{detail}'", StatusCodes.Status505HttpVersionNotsupported);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.FinalTransferCodingNotChunked:
|
case RequestRejectionReason.FinalTransferCodingNotChunked:
|
||||||
ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{value}\"", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{detail}\"", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.LengthRequired:
|
case RequestRejectionReason.LengthRequired:
|
||||||
ex = new BadHttpRequestException($"{value} request contains no Content-Length or Transfer-Encoding header", StatusCodes.Status411LengthRequired);
|
ex = new BadHttpRequestException($"{detail} request contains no Content-Length or Transfer-Encoding header", StatusCodes.Status411LengthRequired);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.LengthRequiredHttp10:
|
case RequestRejectionReason.LengthRequiredHttp10:
|
||||||
ex = new BadHttpRequestException($"{value} request contains no Content-Length header", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException($"{detail} request contains no Content-Length header", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ex = new BadHttpRequestException("Bad request.", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException("Bad request.", StatusCodes.Status400BadRequest);
|
||||||
|
|
|
||||||
|
|
@ -1184,23 +1184,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason)
|
public void RejectRequest(RequestRejectionReason reason)
|
||||||
{
|
=> throw BadHttpRequestException.GetException(reason);
|
||||||
throw BadHttpRequestException.GetException(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason, string value)
|
public void RejectRequest(RequestRejectionReason reason, string detail)
|
||||||
{
|
=> throw BadHttpRequestException.GetException(reason, detail);
|
||||||
throw BadHttpRequestException.GetException(reason, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RejectRequestLine(Span<byte> requestLine)
|
private void RejectRequestTarget(Span<byte> target)
|
||||||
{
|
=> throw GetInvalidRequestTargetException(target);
|
||||||
Debug.Assert(Log.IsEnabled(LogLevel.Information) == true, "Use RejectRequest instead to improve inlining when log is disabled");
|
|
||||||
|
|
||||||
const int MaxRequestLineError = 32;
|
private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target)
|
||||||
var line = requestLine.GetAsciiStringEscaped(MaxRequestLineError);
|
=> BadHttpRequestException.GetException(
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine, line);
|
RequestRejectionReason.InvalidRequestTarget,
|
||||||
}
|
Log.IsEnabled(LogLevel.Information)
|
||||||
|
? target.GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
|
||||||
|
: string.Empty);
|
||||||
|
|
||||||
public void SetBadRequestState(RequestRejectionReason reason)
|
public void SetBadRequestState(RequestRejectionReason reason)
|
||||||
{
|
{
|
||||||
|
|
@ -1239,7 +1236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
Log.ApplicationError(ConnectionId, ex);
|
Log.ApplicationError(ConnectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, Span<byte> line, bool pathEncoded)
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||||
{
|
{
|
||||||
Debug.Assert(target.Length != 0, "Request target must be non-zero length");
|
Debug.Assert(target.Length != 0, "Request target must be non-zero length");
|
||||||
|
|
||||||
|
|
@ -1257,15 +1254,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
else if (target.GetKnownHttpScheme(out var scheme))
|
else if (target.GetKnownHttpScheme(out var scheme))
|
||||||
{
|
{
|
||||||
OnAbsoluteFormTarget(target, query, line);
|
OnAbsoluteFormTarget(target, query);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Assume anything else is considered authority form.
|
// Assume anything else is considered authority form.
|
||||||
// FYI: this should be an edge case. This should only happen when
|
// FYI: this should be an edge case. This should only happen when
|
||||||
// a client mistakenly things this server is a proxy server.
|
// a client mistakenly thinks this server is a proxy server.
|
||||||
|
OnAuthorityFormTarget(method, target);
|
||||||
OnAuthorityFormTarget(method, target, line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Method = method != HttpMethod.Custom
|
Method = method != HttpMethod.Custom
|
||||||
|
|
@ -1287,40 +1283,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
// 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
|
||||||
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
|
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
|
||||||
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
|
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
|
||||||
string requestUrlPath;
|
string requestUrlPath = null;
|
||||||
string rawTarget;
|
string rawTarget = null;
|
||||||
if (pathEncoded)
|
|
||||||
{
|
|
||||||
// Read raw target before mutating memory.
|
|
||||||
rawTarget = target.GetAsciiStringNonNullCharacters();
|
|
||||||
|
|
||||||
// URI was encoded, unescape and then parse as utf8
|
try
|
||||||
int pathLength = UrlEncoder.Decode(path, path);
|
|
||||||
requestUrlPath = GetUtf8String(path.Slice(0, pathLength));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// URI wasn't encoded, parse as ASCII
|
if (pathEncoded)
|
||||||
requestUrlPath = path.GetAsciiStringNonNullCharacters();
|
|
||||||
|
|
||||||
if (query.Length == 0)
|
|
||||||
{
|
{
|
||||||
// No need to allocate an extra string if the path didn't need
|
// Read raw target before mutating memory.
|
||||||
// decoding and there's no query string following it.
|
rawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
rawTarget = requestUrlPath;
|
|
||||||
|
// URI was encoded, unescape and then parse as utf8
|
||||||
|
int pathLength = UrlEncoder.Decode(path, path);
|
||||||
|
requestUrlPath = GetUtf8String(path.Slice(0, pathLength));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rawTarget = target.GetAsciiStringNonNullCharacters();
|
// URI wasn't encoded, parse as ASCII
|
||||||
|
requestUrlPath = path.GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
|
if (query.Length == 0)
|
||||||
|
{
|
||||||
|
// No need to allocate an extra string if the path didn't need
|
||||||
|
// decoding and there's no query string following it.
|
||||||
|
rawTarget = requestUrlPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
RejectRequestTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
QueryString = query.GetAsciiStringNonNullCharacters();
|
QueryString = query.GetAsciiStringNonNullCharacters();
|
||||||
RawTarget = rawTarget;
|
RawTarget = rawTarget;
|
||||||
SetNormalizedPath(requestUrlPath);
|
SetNormalizedPath(requestUrlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target, Span<byte> line)
|
private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
|
||||||
{
|
{
|
||||||
// TODO Validate that target is a correct host[:port] string.
|
// TODO Validate that target is a correct host[:port] string.
|
||||||
// Reject as 400 if not. This is just a quick scan for invalid characters
|
// Reject as 400 if not. This is just a quick scan for invalid characters
|
||||||
|
|
@ -1330,12 +1334,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var ch = target[i];
|
var ch = target[i];
|
||||||
if (!UriUtilities.IsValidAuthorityCharacter(ch))
|
if (!UriUtilities.IsValidAuthorityCharacter(ch))
|
||||||
{
|
{
|
||||||
if (Log.IsEnabled(LogLevel.Information))
|
RejectRequestTarget(target);
|
||||||
{
|
|
||||||
RejectRequestLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1348,14 +1347,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
// When making a CONNECT request to establish a tunnel through one or
|
// When making a CONNECT request to establish a tunnel through one or
|
||||||
// more proxies, a client MUST send only the target URI's authority
|
// more proxies, a client MUST send only the target URI's authority
|
||||||
// component(excluding any userinfo and its "@" delimiter) as the
|
// component (excluding any userinfo and its "@" delimiter) as the
|
||||||
// request - target.For example,
|
// request-target.For example,
|
||||||
//
|
//
|
||||||
// CONNECT www.example.com:80 HTTP/1.1
|
// CONNECT www.example.com:80 HTTP/1.1
|
||||||
//
|
//
|
||||||
// Allowed characters in the 'host + port' section of authority.
|
// Allowed characters in the 'host + port' section of authority.
|
||||||
// See https://tools.ietf.org/html/rfc3986#section-3.2
|
// See https://tools.ietf.org/html/rfc3986#section-3.2
|
||||||
|
|
||||||
RawTarget = target.GetAsciiStringNonNullCharacters();
|
RawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
Path = string.Empty;
|
Path = string.Empty;
|
||||||
PathBase = string.Empty;
|
PathBase = string.Empty;
|
||||||
|
|
@ -1377,7 +1375,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
QueryString = string.Empty;
|
QueryString = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query, Span<byte> line)
|
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
|
||||||
{
|
{
|
||||||
// absolute-form
|
// absolute-form
|
||||||
// https://tools.ietf.org/html/rfc7230#section-5.3.2
|
// https://tools.ietf.org/html/rfc7230#section-5.3.2
|
||||||
|
|
@ -1396,12 +1394,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
|
if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
|
||||||
{
|
{
|
||||||
if (Log.IsEnabled(LogLevel.Information))
|
RejectRequestTarget(target);
|
||||||
{
|
|
||||||
RejectRequestLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetNormalizedPath(uri.LocalPath);
|
SetNormalizedPath(uri.LocalPath);
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
switch (_requestProcessingStatus)
|
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
|
||||||
{
|
{
|
||||||
case RequestProcessingStatus.ParsingRequestLine:
|
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
|
||||||
case RequestProcessingStatus.ParsingHeaders:
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
public interface IHttpRequestLineHandler
|
public interface IHttpRequestLineHandler
|
||||||
{
|
{
|
||||||
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, Span<byte> line, bool pathEncoded);
|
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,14 +117,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
if (pathStart == -1)
|
if (pathStart == -1)
|
||||||
{
|
{
|
||||||
// End of path not found
|
// Start of path not found
|
||||||
RejectRequestLine(data, length);
|
RejectRequestLine(data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
var pathBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
||||||
|
|
||||||
var queryStart = offset;
|
|
||||||
// Query string
|
// Query string
|
||||||
|
var queryStart = offset;
|
||||||
if (ch == ByteQuestionMark)
|
if (ch == ByteQuestionMark)
|
||||||
{
|
{
|
||||||
// We have a query string
|
// We have a query string
|
||||||
|
|
@ -138,6 +138,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End of query string not found
|
||||||
|
if (offset == length)
|
||||||
|
{
|
||||||
|
RejectRequestLine(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
var targetBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
var targetBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
||||||
var query = new Span<byte>(data + queryStart, offset - queryStart);
|
var query = new Span<byte>(data + queryStart, offset - queryStart);
|
||||||
|
|
||||||
|
|
@ -148,10 +154,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var httpVersion = HttpUtilities.GetKnownVersion(data + offset, length - offset);
|
var httpVersion = HttpUtilities.GetKnownVersion(data + offset, length - offset);
|
||||||
if (httpVersion == HttpVersion.Unknown)
|
if (httpVersion == HttpVersion.Unknown)
|
||||||
{
|
{
|
||||||
RejectUnknownVersion(data, length, offset);
|
if (data[offset] == ByteCR || data[length - 2] != ByteCR)
|
||||||
|
{
|
||||||
|
// If missing delimiter or CR before LF, reject and log entire line
|
||||||
|
RejectRequestLine(data, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// else inform HTTP version is unsupported.
|
||||||
|
RejectUnknownVersion(data + offset, length - offset - 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After version 8 bytes and cr 1 byte, expect lf
|
// After version's 8 bytes and CR, expect LF
|
||||||
if (data[offset + 8 + 1] != ByteLF)
|
if (data[offset + 8 + 1] != ByteLF)
|
||||||
{
|
{
|
||||||
RejectRequestLine(data, length);
|
RejectRequestLine(data, length);
|
||||||
|
|
@ -159,7 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
var line = new Span<byte>(data, length);
|
var line = new Span<byte>(data, length);
|
||||||
|
|
||||||
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, line, pathEncoded);
|
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe bool ParseHeaders<T>(T handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes) where T : IHttpHeadersHandler
|
public unsafe bool ParseHeaders<T>(T handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes) where T : IHttpHeadersHandler
|
||||||
|
|
@ -482,73 +497,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
c == '~';
|
c == '~';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RejectRequest(RequestRejectionReason reason)
|
private void RejectRequest(RequestRejectionReason reason)
|
||||||
{
|
=> throw BadHttpRequestException.GetException(reason);
|
||||||
throw BadHttpRequestException.GetException(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void RejectUnknownVersion(byte* data, int length, int versionStart)
|
private unsafe void RejectRequestLine(byte* requestLine, int length)
|
||||||
{
|
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
|
||||||
throw GetRejectUnknownVersion(data, length, versionStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void RejectRequestLine(byte* data, int length)
|
|
||||||
{
|
|
||||||
throw GetRejectRequestLineException(new Span<byte>(data, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RejectRequestLine(Span<byte> span)
|
|
||||||
{
|
|
||||||
throw GetRejectRequestLineException(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BadHttpRequestException GetRejectRequestLineException(Span<byte> span)
|
|
||||||
{
|
|
||||||
const int MaxRequestLineError = 32;
|
|
||||||
return BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine,
|
|
||||||
Log.IsEnabled(LogLevel.Information) ? span.GetAsciiStringEscaped(MaxRequestLineError) : string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe BadHttpRequestException GetRejectUnknownVersion(byte* data, int length, int versionStart)
|
|
||||||
{
|
|
||||||
var span = new Span<byte>(data, length);
|
|
||||||
length -= versionStart;
|
|
||||||
for (var i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
var ch = span[i + versionStart];
|
|
||||||
if (ch == ByteCR)
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
return GetRejectRequestLineException(span);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BadHttpRequestException.GetException(RequestRejectionReason.UnrecognizedHTTPVersion,
|
|
||||||
span.Slice(versionStart, i).GetAsciiStringEscaped(32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetRejectRequestLineException(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void RejectRequestHeader(byte* headerLine, int length)
|
private unsafe void RejectRequestHeader(byte* headerLine, int length)
|
||||||
{
|
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine, length);
|
||||||
RejectRequestHeader(new Span<byte>(headerLine, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RejectRequestHeader(Span<byte> span)
|
private unsafe void RejectUnknownVersion(byte* version, int length)
|
||||||
{
|
=> throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length);
|
||||||
throw GetRejectRequestHeaderException(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BadHttpRequestException GetRejectRequestHeaderException(Span<byte> span)
|
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
|
||||||
{
|
=> BadHttpRequestException.GetException(
|
||||||
const int MaxRequestHeaderError = 128;
|
reason,
|
||||||
return BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestHeader,
|
Log.IsEnabled(LogLevel.Information)
|
||||||
Log.IsEnabled(LogLevel.Information) ? span.GetAsciiStringEscaped(MaxRequestHeaderError) : string.Empty);
|
? new Span<byte>(detail, length).GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
|
||||||
}
|
: string.Empty);
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
BadChunkSuffix,
|
BadChunkSuffix,
|
||||||
BadChunkSizeData,
|
BadChunkSizeData,
|
||||||
ChunkedRequestIncomplete,
|
ChunkedRequestIncomplete,
|
||||||
|
InvalidRequestTarget,
|
||||||
InvalidCharactersInHeaderName,
|
InvalidCharactersInHeaderName,
|
||||||
RequestLineTooLong,
|
RequestLineTooLong,
|
||||||
HeadersExceedMaxTotalSize,
|
HeadersExceedMaxTotalSize,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
public const int ListenBacklog = 128;
|
public const int ListenBacklog = 128;
|
||||||
|
|
||||||
|
public const int MaxExceptionDetailSize = 128;
|
||||||
|
|
||||||
public const int EOF = -4095;
|
public const int EOF = -4095;
|
||||||
public static readonly int? ECONNRESET = GetECONNRESET();
|
public static readonly int? ECONNRESET = GetECONNRESET();
|
||||||
public static readonly int? EADDRINUSE = GetEADDRINUSE();
|
public static readonly int? EADDRINUSE = GetEADDRINUSE();
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
int i;
|
for (var i = 0; i < Math.Min(span.Length, maxChars); i++)
|
||||||
for (i = 0; i < Math.Min(span.Length, maxChars); ++i)
|
|
||||||
{
|
{
|
||||||
var ch = span[i];
|
var ch = span[i];
|
||||||
sb.Append(ch < 0x20 || ch >= 0x7F ? $"\\x{ch:X2}" : ((char)ch).ToString());
|
sb.Append(ch < 0x20 || ch >= 0x7F ? $"\\x{ch:X2}" : ((char)ch).ToString());
|
||||||
|
|
@ -137,6 +136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
sb.Append("...");
|
sb.Append("...");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
return TestBadRequest(
|
return TestBadRequest(
|
||||||
$"GET / {httpVersion}\r\n",
|
$"GET / {httpVersion}\r\n",
|
||||||
"505 HTTP Version Not Supported",
|
"505 HTTP Version Not Supported",
|
||||||
$"Unrecognized HTTP version: {httpVersion}");
|
$"Unrecognized HTTP version: '{httpVersion}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -149,27 +149,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
var data = new TheoryData<string, string>();
|
var data = new TheoryData<string, string>();
|
||||||
|
|
||||||
string Escape(string line)
|
|
||||||
{
|
|
||||||
return line
|
|
||||||
.Replace("\r", @"\x0D")
|
|
||||||
.Replace("\n", @"\x0A")
|
|
||||||
.Replace("\0", @"\x00");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
|
foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
|
||||||
{
|
{
|
||||||
data.Add(requestLine, $"Invalid request line: '{Escape(requestLine)}'");
|
data.Add(requestLine, $"Invalid request line: '{requestLine.EscapeNonPrintable()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
foreach (var target in HttpParsingData.TargetWithEncodedNullCharData)
|
||||||
{
|
{
|
||||||
data.Add(requestLine, "Invalid request line.");
|
data.Add($"GET {target} HTTP/1.1\r\n", $"Invalid request target: '{target.EscapeNonPrintable()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
foreach (var target in HttpParsingData.TargetWithNullCharData)
|
||||||
{
|
{
|
||||||
data.Add(requestLine, $"Invalid request line.");
|
data.Add($"GET {target} HTTP/1.1\r\n", $"Invalid request target: '{target.EscapeNonPrintable()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
new Span<byte>(_target),
|
new Span<byte>(_target),
|
||||||
Span<byte>.Empty,
|
Span<byte>.Empty,
|
||||||
Span<byte>.Empty,
|
Span<byte>.Empty,
|
||||||
new Span<byte>(_startLine),
|
|
||||||
false);
|
false);
|
||||||
|
|
||||||
consumed = buffer.Start;
|
consumed = buffer.Start;
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, Span<byte> line, bool pathEncoded)
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -302,37 +303,39 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RequestLineWithEncodedNullCharInTargetData))]
|
[MemberData(nameof(TargetWithEncodedNullCharData))]
|
||||||
public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string requestLine)
|
public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string target)
|
||||||
{
|
{
|
||||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
|
||||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
_input.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal("The path contains null characters.", exception.Message);
|
Assert.Equal($"Invalid request target: '{target}'", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RequestLineWithNullCharInTargetData))]
|
[MemberData(nameof(TargetWithNullCharData))]
|
||||||
public async Task TakeStartLineThrowsOnNullCharInTarget(string requestLine)
|
public async Task TakeStartLineThrowsOnNullCharInTarget(string target)
|
||||||
{
|
{
|
||||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
|
||||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
_input.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal(new InvalidOperationException().Message, exception.Message);
|
Assert.Equal($"Invalid request target: '{target.EscapeNonPrintable()}'", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RequestLineWithInvalidRequestTargetData))]
|
[MemberData(nameof(MethodWithNullCharData))]
|
||||||
public async Task TakeStartLineThrowsWhenRequestTargetIsInvalid(string requestLine)
|
public async Task TakeStartLineThrowsOnNullCharInMethod(string method)
|
||||||
{
|
{
|
||||||
|
var requestLine = $"{method} / HTTP/1.1\r\n";
|
||||||
|
|
||||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
|
|
@ -340,9 +343,40 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
_input.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal($"Invalid request line: '{Escape(requestLine)}'", exception.Message);
|
Assert.Equal($"Invalid request line: '{requestLine.EscapeNonPrintable()}'", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(QueryStringWithNullCharData))]
|
||||||
|
public async Task TakeStartLineThrowsOnNullCharInQueryString(string queryString)
|
||||||
|
{
|
||||||
|
var target = $"/{queryString}";
|
||||||
|
|
||||||
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
|
||||||
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
|
Assert.Equal($"Invalid request target: '{target.EscapeNonPrintable()}'", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TargetInvalidData))]
|
||||||
|
public async Task TakeStartLineThrowsWhenRequestTargetIsInvalid(string method, string target)
|
||||||
|
{
|
||||||
|
var requestLine = $"{method} {target} HTTP/1.1\r\n";
|
||||||
|
|
||||||
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
|
Assert.Equal($"Invalid request target: '{target.EscapeNonPrintable()}'", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(MethodNotAllowedTargetData))]
|
[MemberData(nameof(MethodNotAllowedTargetData))]
|
||||||
|
|
@ -531,51 +565,98 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
|
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExceptionDetailNotIncludedWhenLogLevelInformationNotEnabled()
|
||||||
|
{
|
||||||
|
var previousLog = _serviceContext.Log;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mockTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
_serviceContext.Log = mockTrace.Object;
|
||||||
|
|
||||||
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET /%00 HTTP/1.1\r\n"));
|
||||||
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
|
Assert.Equal("Invalid request target: ''", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_serviceContext.Log = previousLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.RequestLineValidData;
|
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.RequestLineValidData;
|
||||||
|
|
||||||
public static TheoryData<string> RequestLineWithEncodedNullCharInTargetData
|
public static TheoryData<string> TargetWithEncodedNullCharData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var data = new TheoryData<string>();
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
foreach (var target in HttpParsingData.TargetWithEncodedNullCharData)
|
||||||
{
|
{
|
||||||
data.Add(requestLine);
|
data.Add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Escape(string requestLine)
|
public static TheoryData<string, string> TargetInvalidData
|
||||||
{
|
=> HttpParsingData.TargetInvalidData;
|
||||||
var ellipsis = requestLine.Length > 32
|
|
||||||
? "..."
|
|
||||||
: string.Empty;
|
|
||||||
return requestLine
|
|
||||||
.Substring(0, Math.Min(32, requestLine.Length))
|
|
||||||
.Replace("\r", @"\x0D")
|
|
||||||
.Replace("\n", @"\x0A")
|
|
||||||
.Replace("\0", @"\x00")
|
|
||||||
+ ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData<string> RequestLineWithInvalidRequestTargetData
|
|
||||||
=> HttpParsingData.RequestLineWithInvalidRequestTarget;
|
|
||||||
|
|
||||||
public static TheoryData<string, HttpMethod> MethodNotAllowedTargetData
|
public static TheoryData<string, HttpMethod> MethodNotAllowedTargetData
|
||||||
=> HttpParsingData.MethodNotAllowedRequestLine;
|
=> HttpParsingData.MethodNotAllowedRequestLine;
|
||||||
|
|
||||||
public static TheoryData<string> RequestLineWithNullCharInTargetData
|
public static TheoryData<string> TargetWithNullCharData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var data = new TheoryData<string>();
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
foreach (var target in HttpParsingData.TargetWithNullCharData)
|
||||||
{
|
{
|
||||||
data.Add(requestLine);
|
data.Add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<string> MethodWithNullCharData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
|
foreach (var target in HttpParsingData.MethodWithNullCharData)
|
||||||
|
{
|
||||||
|
data.Add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<string> QueryStringWithNullCharData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
|
foreach (var target in HttpParsingData.QueryStringWithNullCharData)
|
||||||
|
{
|
||||||
|
data.Add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class HttpParserTests
|
public class HttpParserTests
|
||||||
{
|
{
|
||||||
// Returns true when all headers parsed
|
|
||||||
// Return false otherwise
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RequestLineValidData))]
|
[MemberData(nameof(RequestLineValidData))]
|
||||||
public void ParsesRequestLine(
|
public void ParsesRequestLine(
|
||||||
|
|
@ -50,9 +47,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
It.IsAny<Span<byte>>(),
|
It.IsAny<Span<byte>>(),
|
||||||
It.IsAny<Span<byte>>(),
|
It.IsAny<Span<byte>>(),
|
||||||
It.IsAny<Span<byte>>(),
|
It.IsAny<Span<byte>>(),
|
||||||
It.IsAny<Span<byte>>(),
|
|
||||||
It.IsAny<bool>()))
|
It.IsAny<bool>()))
|
||||||
.Callback<HttpMethod, HttpVersion, Span<byte>, Span<byte>, Span<byte>, Span<byte>, Span<byte>, bool>((method, version, target, path, query, customMethod, line, pathEncoded) =>
|
.Callback<HttpMethod, HttpVersion, Span<byte>, Span<byte>, Span<byte>, Span<byte>, bool>((method, version, target, path, query, customMethod, pathEncoded) =>
|
||||||
{
|
{
|
||||||
parsedMethod = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
|
parsedMethod = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
|
||||||
parsedVersion = HttpUtilities.VersionToString(version);
|
parsedVersion = HttpUtilities.VersionToString(version);
|
||||||
|
|
@ -111,7 +107,28 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
Assert.Equal($"Invalid request line: '{requestLine.Replace("\r", "\\x0D").Replace("\n", "\\x0A")}'", exception.Message);
|
Assert.Equal($"Invalid request line: '{requestLine.EscapeNonPrintable()}'", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(MethodWithNonTokenCharData))]
|
||||||
|
public void ParseRequestLineThrowsOnNonTokenCharsInCustomMethod(string method)
|
||||||
|
{
|
||||||
|
var requestLine = $"{method} / HTTP/1.1\r\n";
|
||||||
|
|
||||||
|
var mockTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var parser = CreateParser(mockTrace.Object);
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal($"Invalid request line: '{method.EscapeNonPrintable()} / HTTP/1.1\\x0D\\x0A'", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +149,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
|
Assert.Equal($"Unrecognized HTTP version: '{httpVersion}'", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
|
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,6 +341,44 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExceptionDetailNotIncludedWhenLogLevelInformationNotEnabled()
|
||||||
|
{
|
||||||
|
var mockTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var parser = CreateParser(mockTrace.Object);
|
||||||
|
|
||||||
|
// Invalid request line
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes("GET % HTTP/1.1\r\n"));
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal("Invalid request line: ''", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||||
|
|
||||||
|
// Unrecognized HTTP version
|
||||||
|
buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n"));
|
||||||
|
|
||||||
|
exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal("Unrecognized HTTP version: ''", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
|
||||||
|
|
||||||
|
// Invalid request header
|
||||||
|
buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes("Header: value\n\r\n"));
|
||||||
|
|
||||||
|
exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes));
|
||||||
|
|
||||||
|
Assert.Equal("Invalid request header: ''", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
private void VerifyHeader(
|
private void VerifyHeader(
|
||||||
string headerName,
|
string headerName,
|
||||||
string rawHeaderValue,
|
string rawHeaderValue,
|
||||||
|
|
@ -384,6 +439,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
public static IEnumerable<object[]> RequestLineInvalidData => HttpParsingData.RequestLineInvalidData.Select(requestLine => new[] { requestLine });
|
public static IEnumerable<object[]> RequestLineInvalidData => HttpParsingData.RequestLineInvalidData.Select(requestLine => new[] { requestLine });
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MethodWithNonTokenCharData => HttpParsingData.MethodWithNonTokenCharData.Select(method => new[] { method });
|
||||||
|
|
||||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||||
|
|
||||||
public static IEnumerable<object[]> RequestHeaderInvalidData => HttpParsingData.RequestHeaderInvalidData;
|
public static IEnumerable<object[]> RequestHeaderInvalidData => HttpParsingData.RequestHeaderInvalidData;
|
||||||
|
|
|
||||||
|
|
@ -112,113 +112,144 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
"GET / HTTP/1.1\r",
|
"GET / HTTP/1.1\r",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static IEnumerable<string> RequestLineInvalidData => new[]
|
public static IEnumerable<string> RequestLineInvalidData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
"G\r\n",
|
||||||
|
"GE\r\n",
|
||||||
|
"GET\r\n",
|
||||||
|
"GET \r\n",
|
||||||
|
"GET /\r\n",
|
||||||
|
"GET / \r\n",
|
||||||
|
"GET/HTTP/1.1\r\n",
|
||||||
|
"GET /HTTP/1.1\r\n",
|
||||||
|
" \r\n",
|
||||||
|
" \r\n",
|
||||||
|
"/ HTTP/1.1\r\n",
|
||||||
|
" / HTTP/1.1\r\n",
|
||||||
|
"/ \r\n",
|
||||||
|
"GET \r\n",
|
||||||
|
"GET HTTP/1.0\r\n",
|
||||||
|
"GET HTTP/1.1\r\n",
|
||||||
|
"GET / \n",
|
||||||
|
"GET / HTTP/1.0\n",
|
||||||
|
"GET / HTTP/1.1\n",
|
||||||
|
"GET / HTTP/1.0\rA\n",
|
||||||
|
"GET / HTTP/1.1\ra\n",
|
||||||
|
"GET? / HTTP/1.1\r\n",
|
||||||
|
"GET ? HTTP/1.1\r\n",
|
||||||
|
"GET /a?b=cHTTP/1.1\r\n",
|
||||||
|
"GET /a%20bHTTP/1.1\r\n",
|
||||||
|
"GET /a%20b?c=dHTTP/1.1\r\n",
|
||||||
|
"GET %2F HTTP/1.1\r\n",
|
||||||
|
"GET %00 HTTP/1.1\r\n",
|
||||||
|
"CUSTOM \r\n",
|
||||||
|
"CUSTOM /\r\n",
|
||||||
|
"CUSTOM / \r\n",
|
||||||
|
"CUSTOM /HTTP/1.1\r\n",
|
||||||
|
"CUSTOM \r\n",
|
||||||
|
"CUSTOM HTTP/1.0\r\n",
|
||||||
|
"CUSTOM HTTP/1.1\r\n",
|
||||||
|
"CUSTOM / \n",
|
||||||
|
"CUSTOM / HTTP/1.0\n",
|
||||||
|
"CUSTOM / HTTP/1.1\n",
|
||||||
|
"CUSTOM / HTTP/1.0\rA\n",
|
||||||
|
"CUSTOM / HTTP/1.1\ra\n",
|
||||||
|
"CUSTOM ? HTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a?b=cHTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a%20bHTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a%20b?c=dHTTP/1.1\r\n",
|
||||||
|
"CUSTOM %2F HTTP/1.1\r\n",
|
||||||
|
"CUSTOM %00 HTTP/1.1\r\n",
|
||||||
|
}.Concat(MethodWithNonTokenCharData.Select(method => $"{method} / HTTP/1.0\r\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad HTTP Methods (invalid according to RFC)
|
||||||
|
public static IEnumerable<string> MethodWithNonTokenCharData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
"(",
|
||||||
|
")",
|
||||||
|
"<",
|
||||||
|
">",
|
||||||
|
"@",
|
||||||
|
",",
|
||||||
|
";",
|
||||||
|
":",
|
||||||
|
"\\",
|
||||||
|
"\"",
|
||||||
|
"/",
|
||||||
|
"[",
|
||||||
|
"]",
|
||||||
|
"?",
|
||||||
|
"=",
|
||||||
|
"{",
|
||||||
|
"}",
|
||||||
|
"get@",
|
||||||
|
"post=",
|
||||||
|
}.Concat(MethodWithNullCharData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> MethodWithNullCharData => new[]
|
||||||
{
|
{
|
||||||
"G\r\n",
|
|
||||||
"GE\r\n",
|
|
||||||
"GET\r\n",
|
|
||||||
"GET \r\n",
|
|
||||||
"GET /\r\n",
|
|
||||||
"GET / \r\n",
|
|
||||||
"GET/HTTP/1.1\r\n",
|
|
||||||
"GET /HTTP/1.1\r\n",
|
|
||||||
" \r\n",
|
|
||||||
" \r\n",
|
|
||||||
"/ HTTP/1.1\r\n",
|
|
||||||
" / HTTP/1.1\r\n",
|
|
||||||
"/ \r\n",
|
|
||||||
"GET \r\n",
|
|
||||||
"GET HTTP/1.0\r\n",
|
|
||||||
"GET HTTP/1.1\r\n",
|
|
||||||
"GET / \n",
|
|
||||||
"GET / HTTP/1.0\n",
|
|
||||||
"GET / HTTP/1.1\n",
|
|
||||||
"GET / HTTP/1.0\rA\n",
|
|
||||||
"GET / HTTP/1.1\ra\n",
|
|
||||||
"GET? / HTTP/1.1\r\n",
|
|
||||||
"GET ? HTTP/1.1\r\n",
|
|
||||||
"GET /a?b=cHTTP/1.1\r\n",
|
|
||||||
"GET /a%20bHTTP/1.1\r\n",
|
|
||||||
"GET /a%20b?c=dHTTP/1.1\r\n",
|
|
||||||
"GET %2F HTTP/1.1\r\n",
|
|
||||||
"GET %00 HTTP/1.1\r\n",
|
|
||||||
"CUSTOM \r\n",
|
|
||||||
"CUSTOM /\r\n",
|
|
||||||
"CUSTOM / \r\n",
|
|
||||||
"CUSTOM /HTTP/1.1\r\n",
|
|
||||||
"CUSTOM \r\n",
|
|
||||||
"CUSTOM HTTP/1.0\r\n",
|
|
||||||
"CUSTOM HTTP/1.1\r\n",
|
|
||||||
"CUSTOM / \n",
|
|
||||||
"CUSTOM / HTTP/1.0\n",
|
|
||||||
"CUSTOM / HTTP/1.1\n",
|
|
||||||
"CUSTOM / HTTP/1.0\rA\n",
|
|
||||||
"CUSTOM / HTTP/1.1\ra\n",
|
|
||||||
"CUSTOM ? HTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a?b=cHTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a%20bHTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a%20b?c=dHTTP/1.1\r\n",
|
|
||||||
"CUSTOM %2F HTTP/1.1\r\n",
|
|
||||||
"CUSTOM %00 HTTP/1.1\r\n",
|
|
||||||
// Bad HTTP Methods (invalid according to RFC)
|
// Bad HTTP Methods (invalid according to RFC)
|
||||||
"( / HTTP/1.0\r\n",
|
"\0",
|
||||||
") / HTTP/1.0\r\n",
|
"\0GET",
|
||||||
"< / HTTP/1.0\r\n",
|
"G\0T",
|
||||||
"> / HTTP/1.0\r\n",
|
"GET\0",
|
||||||
"@ / HTTP/1.0\r\n",
|
|
||||||
", / HTTP/1.0\r\n",
|
|
||||||
"; / HTTP/1.0\r\n",
|
|
||||||
": / HTTP/1.0\r\n",
|
|
||||||
"\\ / HTTP/1.0\r\n",
|
|
||||||
"\" / HTTP/1.0\r\n",
|
|
||||||
"/ / HTTP/1.0\r\n",
|
|
||||||
"[ / HTTP/1.0\r\n",
|
|
||||||
"] / HTTP/1.0\r\n",
|
|
||||||
"? / HTTP/1.0\r\n",
|
|
||||||
"= / HTTP/1.0\r\n",
|
|
||||||
"{ / HTTP/1.0\r\n",
|
|
||||||
"} / HTTP/1.0\r\n",
|
|
||||||
"get@ / HTTP/1.0\r\n",
|
|
||||||
"post= / HTTP/1.0\r\n",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static IEnumerable<string> RequestLineWithEncodedNullCharInTargetData => new[]
|
public static IEnumerable<string> TargetWithEncodedNullCharData => new[]
|
||||||
{
|
{
|
||||||
"GET /%00 HTTP/1.1\r\n",
|
"/%00",
|
||||||
"GET /%00%00 HTTP/1.1\r\n",
|
"/%00%00",
|
||||||
"GET /%E8%00%84 HTTP/1.1\r\n",
|
"/%E8%00%84",
|
||||||
"GET /%E8%85%00 HTTP/1.1\r\n",
|
"/%E8%85%00",
|
||||||
"GET /%F3%00%82%86 HTTP/1.1\r\n",
|
"/%F3%00%82%86",
|
||||||
"GET /%F3%85%00%82 HTTP/1.1\r\n",
|
"/%F3%85%00%82",
|
||||||
"GET /%F3%85%82%00 HTTP/1.1\r\n",
|
"/%F3%85%82%00",
|
||||||
"GET /%E8%01%00 HTTP/1.1\r\n",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TheoryData<string> RequestLineWithInvalidRequestTarget => new TheoryData<string>
|
public static TheoryData<string, string> TargetInvalidData
|
||||||
{
|
{
|
||||||
// Invalid absolute-form requests
|
get
|
||||||
"GET http:// HTTP/1.1\r\n",
|
{
|
||||||
"GET http:/ HTTP/1.1\r\n",
|
var data = new TheoryData<string, string>();
|
||||||
"GET https:/ HTTP/1.1\r\n",
|
|
||||||
"GET http:/// HTTP/1.1\r\n",
|
// Invalid absolute-form
|
||||||
"GET https:// HTTP/1.1\r\n",
|
data.Add("GET", "http://");
|
||||||
"GET http://// HTTP/1.1\r\n",
|
data.Add("GET", "http:/");
|
||||||
"GET http://:80 HTTP/1.1\r\n",
|
data.Add("GET", "https:/");
|
||||||
"GET http://:80/abc HTTP/1.1\r\n",
|
data.Add("GET", "http:///");
|
||||||
"GET http://user@ HTTP/1.1\r\n",
|
data.Add("GET", "https://");
|
||||||
"GET http://user@/abc HTTP/1.1\r\n",
|
data.Add("GET", "http:////");
|
||||||
"GET http://abc%20xyz/abc HTTP/1.1\r\n",
|
data.Add("GET", "http://:80");
|
||||||
"GET http://%20/abc?query=%0A HTTP/1.1\r\n",
|
data.Add("GET", "http://:80/abc");
|
||||||
// Valid absolute-form but with unsupported schemes
|
data.Add("GET", "http://user@");
|
||||||
"GET otherscheme://host/ HTTP/1.1\r\n",
|
data.Add("GET", "http://user@/abc");
|
||||||
"GET ws://host/ HTTP/1.1\r\n",
|
data.Add("GET", "http://abc%20xyz/abc");
|
||||||
"GET wss://host/ HTTP/1.1\r\n",
|
data.Add("GET", "http://%20/abc?query=%0A");
|
||||||
// Must only have one asterisk
|
// Valid absolute-form but with unsupported schemes
|
||||||
"OPTIONS ** HTTP/1.1\r\n",
|
data.Add("GET", "otherscheme://host/");
|
||||||
// Relative form
|
data.Add("GET", "ws://host/");
|
||||||
"GET ../../ HTTP/1.1\r\n",
|
data.Add("GET", "wss://host/");
|
||||||
"GET ..\\. HTTP/1.1\r\n",
|
// Must only have one asterisk
|
||||||
};
|
data.Add("OPTIONS", "**");
|
||||||
|
// Relative form
|
||||||
|
data.Add("GET", "../../");
|
||||||
|
data.Add("GET", "..\\.");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static TheoryData<string, HttpMethod> MethodNotAllowedRequestLine
|
public static TheoryData<string, HttpMethod> MethodNotAllowedRequestLine
|
||||||
{
|
{
|
||||||
|
|
@ -234,36 +265,44 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
"TRACE",
|
"TRACE",
|
||||||
"PATCH",
|
"PATCH",
|
||||||
"CONNECT",
|
"CONNECT",
|
||||||
//"OPTIONS",
|
"OPTIONS",
|
||||||
"CUSTOM",
|
"CUSTOM",
|
||||||
};
|
};
|
||||||
|
|
||||||
var theoryData = new TheoryData<string, HttpMethod>();
|
var data = new TheoryData<string, HttpMethod>();
|
||||||
foreach (var line in methods
|
|
||||||
.Select(m => Tuple.Create($"{m} * HTTP/1.1\r\n", HttpMethod.Options))
|
foreach (var method in methods.Except(new[] { "OPTIONS" }))
|
||||||
.Concat(new[]
|
|
||||||
{
|
|
||||||
// CONNECT required for authority-form targets
|
|
||||||
Tuple.Create("GET http:80 HTTP/1.1\r\n", HttpMethod.Connect),
|
|
||||||
Tuple.Create("GET http: HTTP/1.1\r\n", HttpMethod.Connect),
|
|
||||||
Tuple.Create("GET https: HTTP/1.1\r\n", HttpMethod.Connect),
|
|
||||||
Tuple.Create("GET . HTTP/1.1\r\n", HttpMethod.Connect),
|
|
||||||
}))
|
|
||||||
{
|
{
|
||||||
theoryData.Add(line.Item1, line.Item2);
|
data.Add($"{method} * HTTP/1.1\r\n", HttpMethod.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return theoryData;
|
foreach (var method in methods.Except(new[] { "CONNECT" }))
|
||||||
|
{
|
||||||
|
data.Add($"{method} www.example.com:80 HTTP/1.1\r\n", HttpMethod.Connect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<string> RequestLineWithNullCharInTargetData => new[]
|
public static IEnumerable<string> TargetWithNullCharData
|
||||||
{
|
{
|
||||||
// TODO re-enable after we get both #1469 and #1470 merged
|
get
|
||||||
// "GET \0 HTTP/1.1\r\n",
|
{
|
||||||
"GET /\0 HTTP/1.1\r\n",
|
return new[]
|
||||||
"GET /\0\0 HTTP/1.1\r\n",
|
{
|
||||||
"GET /%C8\0 HTTP/1.1\r\n",
|
"\0",
|
||||||
|
"/\0",
|
||||||
|
"/\0\0",
|
||||||
|
"/%C8\0",
|
||||||
|
}.Concat(QueryStringWithNullCharData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> QueryStringWithNullCharData => new[]
|
||||||
|
{
|
||||||
|
"/?\0=a",
|
||||||
|
"/?a=\0",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TheoryData<string> UnrecognizedHttpVersionData => new TheoryData<string>
|
public static TheoryData<string> UnrecognizedHttpVersionData => new TheoryData<string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string EscapeNonPrintable(this string s)
|
||||||
|
{
|
||||||
|
var ellipsis = s.Length > 128
|
||||||
|
? "..."
|
||||||
|
: string.Empty;
|
||||||
|
return s.Substring(0, Math.Min(128, s.Length))
|
||||||
|
.Replace("\r", @"\x0D")
|
||||||
|
.Replace("\n", @"\x0A")
|
||||||
|
.Replace("\0", @"\x00")
|
||||||
|
+ ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue