Refactor request line validation and error reporting (#1116).
This commit is contained in:
parent
4117ad8a1e
commit
a2439105ae
|
|
@ -21,24 +21,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
BadHttpRequestException ex;
|
BadHttpRequestException ex;
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case RequestRejectionReason.MissingMethod:
|
|
||||||
ex = new BadHttpRequestException("Missing method.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.InvalidMethod:
|
|
||||||
ex = new BadHttpRequestException("Invalid method.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.MissingRequestTarget:
|
|
||||||
ex = new BadHttpRequestException("Missing request target.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.MissingHTTPVersion:
|
|
||||||
ex = new BadHttpRequestException("Missing HTTP version.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.UnrecognizedHTTPVersion:
|
|
||||||
ex = new BadHttpRequestException("Unrecognized HTTP version.", 505);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.MissingLFInRequestLine:
|
|
||||||
ex = new BadHttpRequestException("Missing LF in request line.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
|
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
|
||||||
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", 400);
|
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", 400);
|
||||||
break;
|
break;
|
||||||
|
|
@ -84,15 +66,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
case RequestRejectionReason.RequestLineTooLong:
|
case RequestRejectionReason.RequestLineTooLong:
|
||||||
ex = new BadHttpRequestException("Request line too long.", 414);
|
ex = new BadHttpRequestException("Request line too long.", 414);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.MissingSpaceAfterMethod:
|
|
||||||
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.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.MissingCrAfterVersion:
|
|
||||||
ex = new BadHttpRequestException("Missing CR in request line.", 400);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.HeadersExceedMaxTotalSize:
|
case RequestRejectionReason.HeadersExceedMaxTotalSize:
|
||||||
ex = new BadHttpRequestException("Request headers too long.", 431);
|
ex = new BadHttpRequestException("Request headers too long.", 431);
|
||||||
break;
|
break;
|
||||||
|
|
@ -117,12 +90,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
BadHttpRequestException ex;
|
BadHttpRequestException ex;
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case RequestRejectionReason.MalformedRequestLineStatus:
|
case RequestRejectionReason.InvalidRequestLine:
|
||||||
ex = new BadHttpRequestException($"Invalid request line: {value}", 400);
|
ex = new BadHttpRequestException($"Invalid request line: {value}", 400);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.InvalidContentLength:
|
case RequestRejectionReason.InvalidContentLength:
|
||||||
ex = new BadHttpRequestException($"Invalid content length: {value}", 400);
|
ex = new BadHttpRequestException($"Invalid content length: {value}", 400);
|
||||||
break;
|
break;
|
||||||
|
case RequestRejectionReason.UnrecognizedHTTPVersion:
|
||||||
|
ex = new BadHttpRequestException($"Unrecognized HTTP version: {value}", 505);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ex = new BadHttpRequestException("Bad request.", 400);
|
ex = new BadHttpRequestException("Bad request.", 400);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -857,7 +857,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
public RequestLineStatus TakeStartLine(SocketInput input)
|
public RequestLineStatus TakeStartLine(SocketInput input)
|
||||||
{
|
{
|
||||||
|
const int MaxInvalidRequestLineChars = 32;
|
||||||
|
|
||||||
var scan = input.ConsumingStart();
|
var scan = input.ConsumingStart();
|
||||||
|
var start = scan;
|
||||||
var consumed = scan;
|
var consumed = scan;
|
||||||
var end = scan;
|
var end = scan;
|
||||||
|
|
||||||
|
|
@ -890,6 +893,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
return RequestLineStatus.Incomplete;
|
return RequestLineStatus.Incomplete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
end.Take();
|
||||||
|
|
||||||
string method;
|
string method;
|
||||||
var begin = scan;
|
var begin = scan;
|
||||||
|
|
@ -897,14 +901,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingSpaceAfterMethod);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
method = begin.GetAsciiString(scan);
|
method = begin.GetAsciiString(scan);
|
||||||
|
|
||||||
if (method == null)
|
if (method == null)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingMethod);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
@ -913,7 +919,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
if (!IsValidTokenChar(method[i]))
|
if (!IsValidTokenChar(method[i]))
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.InvalidMethod);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -928,7 +935,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages, ref end);
|
var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages, ref end);
|
||||||
if (chFound == -1)
|
if (chFound == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
else if (chFound == '%')
|
else if (chFound == '%')
|
||||||
{
|
{
|
||||||
|
|
@ -936,7 +944,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref end);
|
chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref end);
|
||||||
if (chFound == -1)
|
if (chFound == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -949,7 +958,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
begin = scan;
|
begin = scan;
|
||||||
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
queryString = begin.GetAsciiString(scan);
|
queryString = begin.GetAsciiString(scan);
|
||||||
}
|
}
|
||||||
|
|
@ -958,43 +968,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
if (pathBegin.Peek() == ' ')
|
if (pathBegin.Peek() == ' ')
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingRequestTarget);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
scan.Take();
|
scan.Take();
|
||||||
begin = scan;
|
begin = scan;
|
||||||
if (scan.Seek(ref _vectorCRs, ref end) == -1)
|
if (scan.Seek(ref _vectorCRs, ref end) == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingCrAfterVersion);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
string httpVersion;
|
string httpVersion;
|
||||||
if (!begin.GetKnownVersion(out httpVersion))
|
if (!begin.GetKnownVersion(out httpVersion))
|
||||||
{
|
{
|
||||||
// A slower fallback is necessary since the iterator's PeekLong() method
|
httpVersion = begin.GetAsciiStringEscaped(scan, 9);
|
||||||
// used in GetKnownVersion() only examines two memory blocks at most.
|
|
||||||
// Although unlikely, it is possible that the 8 bytes forming the version
|
|
||||||
// could be spread out on more than two blocks, if the connection
|
|
||||||
// happens to be unusually slow.
|
|
||||||
httpVersion = begin.GetAsciiString(scan);
|
|
||||||
|
|
||||||
if (httpVersion == null)
|
if (httpVersion == string.Empty)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingHTTPVersion);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1")
|
else
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion);
|
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, httpVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scan.Take(); // consume CR
|
scan.Take(); // consume CR
|
||||||
if (scan.Block != end.Block || scan.Index != end.Index)
|
if (scan.Take() != '\n')
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingLFInRequestLine);
|
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||||
|
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
|
||||||
}
|
}
|
||||||
scan.Take(); // consume LF
|
|
||||||
end = scan;
|
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
if (requestLineStatus != RequestLineStatus.Done)
|
if (requestLineStatus != RequestLineStatus.Done)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MalformedRequestLineStatus, requestLineStatus.ToString());
|
RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
public enum RequestRejectionReason
|
public enum RequestRejectionReason
|
||||||
{
|
{
|
||||||
MissingMethod,
|
|
||||||
InvalidMethod,
|
|
||||||
MissingRequestTarget,
|
|
||||||
MissingHTTPVersion,
|
|
||||||
UnrecognizedHTTPVersion,
|
UnrecognizedHTTPVersion,
|
||||||
MissingLFInRequestLine,
|
|
||||||
HeadersCorruptedInvalidHeaderSequence,
|
HeadersCorruptedInvalidHeaderSequence,
|
||||||
HeaderLineMustNotStartWithWhitespace,
|
HeaderLineMustNotStartWithWhitespace,
|
||||||
NoColonCharacterFoundInHeaderLine,
|
NoColonCharacterFoundInHeaderLine,
|
||||||
WhitespaceIsNotAllowedInHeaderName,
|
WhitespaceIsNotAllowedInHeaderName,
|
||||||
HeaderValueMustNotContainCR,
|
HeaderValueMustNotContainCR,
|
||||||
HeaderValueLineFoldingNotSupported,
|
HeaderValueLineFoldingNotSupported,
|
||||||
MalformedRequestLineStatus,
|
InvalidRequestLine,
|
||||||
MalformedRequestInvalidHeaders,
|
MalformedRequestInvalidHeaders,
|
||||||
InvalidContentLength,
|
InvalidContentLength,
|
||||||
UnexpectedEndOfRequestContent,
|
UnexpectedEndOfRequestContent,
|
||||||
|
|
@ -28,9 +23,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
InvalidCharactersInHeaderName,
|
InvalidCharactersInHeaderName,
|
||||||
NonAsciiOrNullCharactersInInputString,
|
NonAsciiOrNullCharactersInInputString,
|
||||||
RequestLineTooLong,
|
RequestLineTooLong,
|
||||||
MissingSpaceAfterMethod,
|
|
||||||
MissingSpaceAfterTarget,
|
|
||||||
MissingCrAfterVersion,
|
|
||||||
HeadersExceedMaxTotalSize,
|
HeadersExceedMaxTotalSize,
|
||||||
MissingCRInHeaderLine,
|
MissingCRInHeaderLine,
|
||||||
TooManyHeaders,
|
TooManyHeaders,
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
return asciiString;
|
return asciiString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetAsciiStringEscaped(this MemoryPoolIterator start, MemoryPoolIterator end, int maxChars)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var scan = start;
|
||||||
|
|
||||||
|
while (maxChars > 0 && (scan.Block != end.Block || scan.Index != end.Index))
|
||||||
|
{
|
||||||
|
var ch = scan.Take();
|
||||||
|
sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch.ToString("X2")}>" : ((char)ch).ToString());
|
||||||
|
maxChars--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scan.Block != end.Block || scan.Index != end.Index)
|
||||||
|
{
|
||||||
|
sb.Append("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetUtf8String(this MemoryPoolIterator start, MemoryPoolIterator end)
|
public static string GetUtf8String(this MemoryPoolIterator start, MemoryPoolIterator end)
|
||||||
{
|
{
|
||||||
if (start.IsDefault || end.IsDefault)
|
if (start.IsDefault || end.IsDefault)
|
||||||
|
|
|
||||||
|
|
@ -1057,17 +1057,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("GET/HTTP/1.1\r\n", "No space character found after method in request line.")]
|
[InlineData("GET/HTTP/1.1\r\n", "Invalid request line: GET/HTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData(" / HTTP/1.1\r\n", "Missing method.")]
|
[InlineData(" / HTTP/1.1\r\n", "Invalid request line: / HTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET? / HTTP/1.1\r\n", "Invalid method.")]
|
[InlineData("GET? / HTTP/1.1\r\n", "Invalid request line: GET? / HTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET /HTTP/1.1\r\n", "No space character found after target in request line.")]
|
[InlineData("GET /HTTP/1.1\r\n", "Invalid request line: GET /HTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET /a?b=cHTTP/1.1\r\n", "No space character found after target in request line.")]
|
[InlineData("GET /a?b=cHTTP/1.1\r\n", "Invalid request line: GET /a?b=cHTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET /a%20bHTTP/1.1\r\n", "No space character found after target in request line.")]
|
[InlineData("GET /a%20bHTTP/1.1\r\n", "Invalid request line: GET /a%20bHTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET /a%20b?c=dHTTP/1.1\r\n", "No space character found after target in request line.")]
|
[InlineData("GET /a%20b?c=dHTTP/1.1\r\n", "Invalid request line: GET /a%20b?c=dHTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET HTTP/1.1\r\n", "Missing request target.")]
|
[InlineData("GET HTTP/1.1\r\n", "Invalid request line: GET HTTP/1.1<0x0D><0x0A>")]
|
||||||
[InlineData("GET / HTTP/1.1\n", "Missing CR in request line.")]
|
[InlineData("GET / HTTP/1.1\n", "Invalid request line: GET / HTTP/1.1<0x0A>")]
|
||||||
[InlineData("GET / \r\n", "Missing HTTP version.")]
|
[InlineData("GET / \r\n", "Invalid request line: GET / <0x0D><0x0A>")]
|
||||||
[InlineData("GET / HTTP/1.1\ra\n", "Missing LF in request line.")]
|
[InlineData("GET / HTTP/1.1\ra\n", "Invalid request line: GET / HTTP/1.1<0x0D>a<0x0A>")]
|
||||||
public void TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
|
public void TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
|
||||||
{
|
{
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
|
@ -1130,7 +1130,41 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
|
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
|
||||||
Assert.Equal("Unrecognized HTTP version.", exception.Message);
|
Assert.Equal("Unrecognized HTTP version: HTTP/1.2", exception.Message);
|
||||||
|
Assert.Equal(505, exception.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEigthCharacters()
|
||||||
|
{
|
||||||
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
var ltp = new LoggingThreadPool(trace);
|
||||||
|
using (var pool = new MemoryPool())
|
||||||
|
using (var socketInput = new SocketInput(pool, ltp))
|
||||||
|
{
|
||||||
|
var serviceContext = new ServiceContext
|
||||||
|
{
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
Log = trace
|
||||||
|
};
|
||||||
|
var listenerContext = new ListenerContext(serviceContext)
|
||||||
|
{
|
||||||
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||||
|
};
|
||||||
|
var connectionContext = new ConnectionContext(listenerContext)
|
||||||
|
{
|
||||||
|
ConnectionControl = Mock.Of<IConnectionControl>(),
|
||||||
|
};
|
||||||
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
frame.Reset();
|
||||||
|
|
||||||
|
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.1ab\r\n");
|
||||||
|
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() => frame.TakeStartLine(socketInput));
|
||||||
|
Assert.Equal("Unrecognized HTTP version: HTTP/1.1a...", exception.Message);
|
||||||
Assert.Equal(505, exception.StatusCode);
|
Assert.Equal(505, exception.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -502,6 +502,52 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
TestKnownStringsInterning(input, expected, MemoryPoolIteratorExtensions.GetKnownVersion);
|
TestKnownStringsInterning(input, expected, MemoryPoolIteratorExtensions.GetKnownVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", "HTTP/1.1\r")]
|
||||||
|
[InlineData("H", "TTP/1.1\r")]
|
||||||
|
[InlineData("HT", "TP/1.1\r")]
|
||||||
|
[InlineData("HTT", "P/1.1\r")]
|
||||||
|
[InlineData("HTTP", "/1.1\r")]
|
||||||
|
[InlineData("HTTP/", "1.1\r")]
|
||||||
|
[InlineData("HTTP/1", ".1\r")]
|
||||||
|
[InlineData("HTTP/1.", "1\r")]
|
||||||
|
[InlineData("HTTP/1.1", "\r")]
|
||||||
|
[InlineData("HTTP/1.1\r", "")]
|
||||||
|
public void KnownVersionCanBeReadAtAnyBlockBoundary(string block1Input, string block2Input)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block1 = null;
|
||||||
|
MemoryPoolBlock block2 = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chars1 = block1Input.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
var chars2 = block2Input.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
block1 = _pool.Lease();
|
||||||
|
block2 = _pool.Lease();
|
||||||
|
Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
|
||||||
|
Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
|
||||||
|
block1.End += chars1.Length;
|
||||||
|
block2.End += chars2.Length;
|
||||||
|
block1.Next = block2;
|
||||||
|
var iterator = block1.GetIterator();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string knownVersion;
|
||||||
|
var result = iterator.GetKnownVersion(out knownVersion);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal("HTTP/1.1", knownVersion);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (block1 != null) _pool.Return(block1);
|
||||||
|
if (block2 != null) _pool.Return(block2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("CONNECT / HTTP/1.1", "CONNECT")]
|
[InlineData("CONNECT / HTTP/1.1", "CONNECT")]
|
||||||
[InlineData("DELETE / HTTP/1.1", "DELETE")]
|
[InlineData("DELETE / HTTP/1.1", "DELETE")]
|
||||||
|
|
@ -766,6 +812,39 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("a", "a", 1)]
|
||||||
|
[InlineData("ab", "a...", 1)]
|
||||||
|
[InlineData("abcde", "abcde", 5)]
|
||||||
|
[InlineData("abcde", "abcd...", 4)]
|
||||||
|
[InlineData("abcde", "abcde", 6)]
|
||||||
|
public void TestGetAsciiStringEscaped(string input, string expected, int maxChars)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
block = _pool.Lease();
|
||||||
|
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
|
||||||
|
block.End += chars.Length;
|
||||||
|
var start = block.GetIterator();
|
||||||
|
var end = start;
|
||||||
|
end.Skip(input.Length);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = start.GetAsciiStringEscaped(end, maxChars);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (block != null) _pool.Return(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private delegate bool GetKnownString(MemoryPoolIterator iter, out string result);
|
private delegate bool GetKnownString(MemoryPoolIterator iter, out string result);
|
||||||
|
|
||||||
private void TestKnownStringsInterning(string input, string expected, GetKnownString action)
|
private void TestKnownStringsInterning(string input, string expected, GetKnownString action)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue