Make TakeStartLine more robust (#683).

This commit is contained in:
Cesar Blum Silveira 2016-04-28 17:39:02 -07:00
parent 388841c1d8
commit 3186e1bd72
9 changed files with 397 additions and 173 deletions

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
private static readonly byte[] _bytesSpace = Encoding.ASCII.GetBytes(" ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static readonly int _httpVersionLength = "HTTP/1.*".Length;
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
private static Vector<byte> _vectorColons = new Vector<byte>((byte)':');
@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object();
protected bool _corruptedRequest = false;
private bool _requestRejected;
private Headers _frameHeaders;
private Streams _frameStreams;
@ -60,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
protected CancellationTokenSource _abortedCts;
protected CancellationToken? _manuallySetRequestAbortToken;
protected bool _responseStarted;
protected RequestProcessingStatus _requestProcessingStatus;
protected bool _keepAlive;
private bool _autoChunk;
protected Exception _applicationException;
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
return "HTTP/1.0";
}
return "";
return string.Empty;
}
set
{
@ -167,9 +168,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
return cts;
}
}
public bool HasResponseStarted
{
get { return _responseStarted; }
get { return _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; }
}
protected FrameRequestHeaders FrameRequestHeaders => _frameHeaders.RequestHeaders;
@ -216,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_onStarting = null;
_onCompleted = null;
_responseStarted = false;
_requestProcessingStatus = RequestProcessingStatus.RequestPending;
_keepAlive = false;
_autoChunk = false;
_applicationException = null;
@ -446,7 +448,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
{
if (!_responseStarted)
if (!HasResponseStarted)
{
return WriteAsyncAwaited(data, cancellationToken);
}
@ -506,7 +508,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
public void ProduceContinue()
{
if (_responseStarted) return;
if (HasResponseStarted)
{
return;
}
StringValues expect;
if (_httpVersion == HttpVersionType.Http1_1 &&
@ -519,7 +524,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
public Task ProduceStartAndFireOnStarting()
{
if (_responseStarted) return TaskUtilities.CompletedTask;
if (HasResponseStarted)
{
return TaskUtilities.CompletedTask;
}
if (_onStarting != null)
{
@ -554,30 +562,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private void ProduceStart(bool appCompleted)
{
if (_responseStarted) return;
_responseStarted = true;
if (HasResponseStarted)
{
return;
}
_requestProcessingStatus = RequestProcessingStatus.ResponseStarted;
var statusBytes = ReasonPhrases.ToStatusBytes(StatusCode, ReasonPhrase);
CreateResponseHeader(statusBytes, appCompleted);
}
protected Task TryProduceInvalidRequestResponse()
{
if (_requestProcessingStatus == RequestProcessingStatus.RequestStarted && _requestRejected)
{
if (_frameHeaders == null)
{
InitializeHeaders();
}
return ProduceEnd();
}
return TaskUtilities.CompletedTask;
}
protected Task ProduceEnd()
{
if (_corruptedRequest || _applicationException != null)
if (_requestRejected || _applicationException != null)
{
if (_corruptedRequest)
if (_requestRejected)
{
// 400 Bad Request
StatusCode = 400;
}
}
else
{
// 500 Internal Server Error
StatusCode = 500;
}
if (_responseStarted)
if (HasResponseStarted)
{
// We can no longer respond with a 500, so we simply close the connection.
_requestProcessingStopping = true;
@ -601,7 +628,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
}
if (!_responseStarted)
if (!HasResponseStarted)
{
return ProduceEndAwaited();
}
@ -709,31 +736,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
SocketOutput.ProducingComplete(end);
}
protected bool TakeStartLine(SocketInput input)
protected RequestLineStatus TakeStartLine(SocketInput input)
{
var scan = input.ConsumingStart();
var consumed = scan;
try
{
// We may hit this when the client has stopped sending data but
// the connection hasn't closed yet, and therefore Frame.Stop()
// hasn't been called yet.
if (scan.Peek() == -1)
{
return RequestLineStatus.Empty;
}
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
string method;
var begin = scan;
if (!begin.GetKnownMethod(ref scan, out method))
if (!begin.GetKnownMethod(out method))
{
if (scan.Seek(ref _vectorSpaces) == -1)
{
return false;
return RequestLineStatus.MethodIncomplete;
}
method = begin.GetAsciiString(scan);
scan.Take();
if (method == null)
{
RejectRequest("Missing method.");
}
}
else
{
scan.Skip(method.Length);
}
scan.Take();
begin = scan;
var needDecode = false;
var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages);
if (chFound == -1)
{
return false;
return RequestLineStatus.TargetIncomplete;
}
else if (chFound == '%')
{
@ -741,7 +787,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks);
if (chFound == -1)
{
return false;
return RequestLineStatus.TargetIncomplete;
}
}
@ -752,35 +798,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
if (chFound == '?')
{
begin = scan;
if (scan.Seek(ref _vectorSpaces) != ' ')
if (scan.Seek(ref _vectorSpaces) == -1)
{
return false;
return RequestLineStatus.TargetIncomplete;
}
queryString = begin.GetAsciiString(scan);
}
if (pathBegin.Peek() == ' ')
{
RejectRequest("Missing request target.");
}
scan.Take();
begin = scan;
if (scan.Seek(ref _vectorCRs) == -1)
{
return RequestLineStatus.VersionIncomplete;
}
string httpVersion;
if (!begin.GetKnownVersion(ref scan, out httpVersion))
if (!begin.GetKnownVersion(out httpVersion))
{
scan = begin;
if (scan.Seek(ref _vectorCRs) == -1)
{
return false;
}
// A slower fallback is necessary since the iterator's PeekLong() method
// 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);
scan.Take();
}
if (scan.Take() != '\n')
{
return false;
if (httpVersion == null)
{
RejectRequest("Missing HTTP version.");
}
else if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1")
{
RejectRequest("Malformed request.");
}
}
// 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;
// HttpVersion must be set here to send correct response when request is rejected
HttpVersion = httpVersion;
scan.Take();
var next = scan.Take();
if (next == -1)
{
return RequestLineStatus.Incomplete;
}
else if (next != '\n')
{
RejectRequest("Missing LF in request line.");
}
// 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;
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
string requestUrlPath;
if (needDecode)
@ -802,7 +874,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Method = method;
RequestUri = requestUrlPath;
QueryString = queryString;
HttpVersion = httpVersion;
bool caseMatches;
@ -818,7 +889,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Path = requestUrlPath;
}
return true;
return RequestLineStatus.Done;
}
finally
{
@ -881,7 +952,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
return true;
}
ReportCorruptedHttpRequest(new BadHttpRequestException("Headers corrupted, invalid header sequence."));
RejectRequest("Headers corrupted, invalid header sequence.");
// Headers corrupted, parsing headers is complete
return true;
}
@ -994,10 +1065,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
statusCode != 304;
}
public void ReportCorruptedHttpRequest(BadHttpRequestException ex)
public void RejectRequest(string message)
{
_corruptedRequest = true;
_requestProcessingStopping = true;
_requestRejected = true;
var ex = new BadHttpRequestException(message);
Log.ConnectionBadRequest(ConnectionId, ex);
throw ex;
}
protected void ReportApplicationError(Exception ex)
@ -1024,5 +1098,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Http1_0 = 0,
Http1_1 = 1
}
protected enum RequestLineStatus
{
Empty,
MethodIncomplete,
TargetIncomplete,
VersionIncomplete,
Incomplete,
Done
}
protected enum RequestProcessingStatus
{
RequestPending,
RequestStarted,
ResponseStarted
}
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Exceptions;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Http
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
while (!_requestProcessingStopping)
{
while (!_requestProcessingStopping && !TakeStartLine(SocketInput))
while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done)
{
if (SocketInput.RemoteIntakeFin)
{
@ -41,12 +40,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
// SocketInput.RemoteIntakeFin is set to true to ensure we don't close a
// connection without giving the application a chance to respond to a request
// sent immediately before the a FIN from the client.
if (TakeStartLine(SocketInput))
var requestLineStatus = TakeStartLine(SocketInput);
if (requestLineStatus == RequestLineStatus.Empty)
{
break;
return;
}
return;
if (requestLineStatus != RequestLineStatus.Done)
{
RejectRequest($"Malformed request: {requestLineStatus}");
}
break;
}
await SocketInput;
@ -62,12 +68,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
// SocketInput.RemoteIntakeFin is set to true to ensure we don't close a
// connection without giving the application a chance to respond to a request
// sent immediately before the a FIN from the client.
if (TakeMessageHeaders(SocketInput, FrameRequestHeaders))
if (!TakeMessageHeaders(SocketInput, FrameRequestHeaders))
{
break;
RejectRequest($"Malformed request: invalid headers.");
}
return;
break;
}
await SocketInput;
@ -83,66 +89,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_abortedCts = null;
_manuallySetRequestAbortToken = null;
if (!_corruptedRequest)
var context = _application.CreateContext(this);
try
{
var context = _application.CreateContext(this);
try
await _application.ProcessRequestAsync(context).ConfigureAwait(false);
}
catch (Exception ex)
{
ReportApplicationError(ex);
}
finally
{
// Trigger OnStarting if it hasn't been called yet and the app hasn't
// already failed. If an OnStarting callback throws we can go through
// our normal error handling in ProduceEnd.
// https://github.com/aspnet/KestrelHttpServer/issues/43
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
{
await _application.ProcessRequestAsync(context).ConfigureAwait(false);
}
catch (Exception ex)
{
ReportApplicationError(ex);
}
finally
{
// Trigger OnStarting if it hasn't been called yet and the app hasn't
// already failed. If an OnStarting callback throws we can go through
// our normal error handling in ProduceEnd.
// https://github.com/aspnet/KestrelHttpServer/issues/43
if (!_responseStarted && _applicationException == null && _onStarting != null)
{
await FireOnStarting();
}
PauseStreams();
if (_onCompleted != null)
{
await FireOnCompleted();
}
_application.DisposeContext(context, _applicationException);
await FireOnStarting();
}
// If _requestAbort is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0)
PauseStreams();
if (_onCompleted != null)
{
ResumeStreams();
if (_keepAlive && !_corruptedRequest)
{
try
{
// Finish reading the request body in case the app did not.
await messageBody.Consume();
}
catch (BadHttpRequestException ex)
{
ReportCorruptedHttpRequest(ex);
}
}
await ProduceEnd();
await FireOnCompleted();
}
StopStreams();
_application.DisposeContext(context, _applicationException);
}
if (!_keepAlive || _corruptedRequest)
// If _requestAbort is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0)
{
// End the connection for non keep alive and Bad Requests
// as data incoming may have been thrown off
ResumeStreams();
if (_keepAlive)
{
// Finish reading the request body in case the app did not.
await messageBody.Consume();
}
await ProduceEnd();
}
StopStreams();
if (!_keepAlive)
{
// End the connection for non keep alive as data incoming may have been thrown off
return;
}
}
@ -158,6 +153,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
try
{
await TryProduceInvalidRequestResponse();
ResetComponents();
_abortedCts = null;

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
return ConsumeAwaited(result.AsTask(), cancellationToken);
}
// ValueTask uses .GetAwaiter().GetResult() if necessary
else if (result.Result == 0)
else if (result.Result == 0)
{
// Completed Task, end of stream
return TaskUtilities.CompletedTask;
@ -125,8 +125,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
long contentLength;
if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0)
{
context.ReportCorruptedHttpRequest(new BadHttpRequestException("Invalid content length."));
return new ForContentLength(keepAlive, 0, context);
context.RejectRequest($"Invalid content length: {unparsedContentLength}");
}
else
{
@ -142,15 +141,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
return new ForRemainingData(context);
}
private int ThrowBadRequestException(string message)
{
// returns int so can be used as item non-void function
var ex = new BadHttpRequestException(message);
_context.ReportCorruptedHttpRequest(ex);
throw ex;
}
private class ForRemainingData : MessageBody
{
public ForRemainingData(Frame context)
@ -197,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_inputLength -= actual;
if (actual == 0)
{
ThrowBadRequestException("Unexpected end of request content");
_context.RejectRequest("Unexpected end of request content");
}
return actual;
}
@ -213,7 +203,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
_inputLength -= actual;
if (actual == 0)
{
ThrowBadRequestException("Unexpected end of request content");
_context.RejectRequest("Unexpected end of request content");
}
return actual;
@ -514,7 +504,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
}
else
{
ThrowBadRequestException("Bad chunk suffix");
_context.RejectRequest("Bad chunk suffix");
}
}
finally
@ -568,16 +558,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
{
return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10));
}
else
{
return ThrowBadRequestException("Bad chunk size data");
}
}
_context.RejectRequest("Bad chunk size data");
return -1; // can't happen, but compiler complains
}
private void ThrowChunkedRequestIncomplete()
{
ThrowBadRequestException("Chunked request incomplete");
_context.RejectRequest("Chunked request incomplete");
}
private enum Mode

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{
if (wasLastBlock)
{
return;
throw new InvalidOperationException("Attempted to skip more bytes than available.");
}
else
{
@ -248,7 +248,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
while (following > 0)
{
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
// Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
if (Vector.IsHardwareAccelerated)
{
@ -269,7 +269,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
return byte0;
}
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
}
#endif
@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
// Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
if (Vector.IsHardwareAccelerated)
{
@ -369,7 +369,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
return byte1;
}
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
}
#endif
var pCurrent = (block.DataFixedPtr + index);
@ -436,7 +436,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
while (following > 0)
{
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
// Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
if (Vector.IsHardwareAccelerated)
{
@ -502,7 +502,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
return toReturn;
}
// Need unit tests to test Vector path
#if !DEBUG
#if !DEBUG
}
#endif
var pCurrent = (block.DataFixedPtr + index);

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
private readonly static long _http10VersionLong = GetAsciiStringAsLong("HTTP/1.0");
private readonly static long _http11VersionLong = GetAsciiStringAsLong("HTTP/1.1");
private readonly static long _mask8Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff });
private readonly static long _mask7Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 });
private readonly static long _mask6Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 });
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
return null;
}
// Bytes out of the range of ascii are treated as "opaque data"
// Bytes out of the range of ascii are treated as "opaque data"
// and kept in string as a char value that casts to same input byte value
// https://tools.ietf.org/html/rfc7230#section-3.2.4
@ -283,16 +283,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
/// <remarks>
/// A "known HTTP method" can be an HTTP method name defined in the HTTP/1.1 RFC.
/// Since all of those fit in at most 8 bytes, they can be optimally looked up by reading those bytes as a long. Once
/// in that format, it can be checked against the known method.
/// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE) are all less than 8 bytes
/// in that format, it can be checked against the known method.
/// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE) are all less than 8 bytes
/// and will be compared with the required space. A mask is used if the Known method is less than 8 bytes.
/// To optimize performance the GET method will be checked first.
/// </remarks>
/// <param name="begin">The iterator from which to start the known string lookup.</param>
/// <param name="scan">If we found a valid method, then scan will be updated to new position</param>
/// <param name="knownMethod">A reference to a pre-allocated known string, if the input matches any.</param>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
public static bool GetKnownMethod(this MemoryPoolIterator begin, ref MemoryPoolIterator scan, out string knownMethod)
public static bool GetKnownMethod(this MemoryPoolIterator begin, out string knownMethod)
{
knownMethod = null;
var value = begin.PeekLong();
@ -300,7 +299,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
if ((value & _mask4Chars) == _httpGetMethodLong)
{
knownMethod = HttpGetMethod;
scan.Skip(4);
return true;
}
foreach (var x in _knownMethods)
@ -308,7 +306,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
if ((value & x.Item1) == x.Item2)
{
knownMethod = x.Item3;
scan.Skip(knownMethod.Length + 1);
return true;
}
}
@ -327,10 +324,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
/// To optimize performance the HTTP/1.1 will be checked first.
/// </remarks>
/// <param name="begin">The iterator from which to start the known string lookup.</param>
/// <param name="scan">If we found a valid method, then scan will be updated to new position</param>
/// <param name="knownVersion">A reference to a pre-allocated known string, if the input matches any.</param>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
public static bool GetKnownVersion(this MemoryPoolIterator begin, ref MemoryPoolIterator scan, out string knownVersion)
public static bool GetKnownVersion(this MemoryPoolIterator begin, out string knownVersion)
{
knownVersion = null;
var value = begin.PeekLong();
@ -338,24 +334,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
if (value == _http11VersionLong)
{
knownVersion = Http11Version;
scan.Skip(8);
if (scan.Take() == '\r')
{
return true;
}
}
else if (value == _http10VersionLong)
{
knownVersion = Http10Version;
scan.Skip(8);
if (scan.Take() == '\r')
}
if (knownVersion != null)
{
begin.Skip(knownVersion.Length);
if (begin.Peek() != '\r')
{
return true;
knownVersion = null;
}
}
knownVersion = null;
return false;
return knownVersion != null;
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class BadHttpRequestTests
{
[Theory]
[InlineData("/ HTTP/1.1\r\n\r\n")]
[InlineData(" / HTTP/1.1\r\n\r\n")]
[InlineData(" / HTTP/1.1\r\n\r\n")]
[InlineData("GET / HTTP/1.1\r\n\r\n")]
[InlineData("GET / HTTP/1.1\r\n\r\n")]
[InlineData("GET HTTP/1.1\r\n\r\n")]
[InlineData("GET /")]
[InlineData("GET / ")]
[InlineData("GET / H")]
[InlineData("GET / HTTP/1.")]
[InlineData("GET /\r\n")]
[InlineData("GET / \r\n")]
[InlineData("GET / \n")]
[InlineData("GET / http/1.0\r\n\r\n")]
[InlineData("GET / http/1.1\r\n\r\n")]
[InlineData("GET / HTTP/1.1 \r\n\r\n")]
[InlineData("GET / HTTP/1.1a\r\n\r\n")]
[InlineData("GET / HTTP/1.0\n\r\n")]
[InlineData("GET / HTTP/3.0\r\n\r\n")]
[InlineData("GET / H\r\n\r\n")]
[InlineData("GET / HTTP/1.\r\n\r\n")]
[InlineData("GET / hello\r\n\r\n")]
[InlineData("GET / 8charact\r\n\r\n")]
public async Task TestBadRequests(string request)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = new TestConnection(server.Port))
{
var receiveTask = Task.Run(async () =>
{
await connection.Receive(
"HTTP/1.0 400 Bad Request",
"");
await connection.ReceiveStartsWith("Date: ");
await connection.ReceiveForcedEnd(
"Content-Length: 0",
"Server: Kestrel",
"",
"");
});
try
{
await connection.SendEnd(request).ConfigureAwait(false);
}
catch (Exception)
{
// TestConnection.SendEnd will start throwing while sending characters
// in cases where the server rejects the request as soon as it
// determines the request line is malformed, even though there
// are more characters following.
}
await receiveTask;
}
}
}
}
}

View File

@ -217,7 +217,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"C",
"C",
"HelloChunked",
"0",
""};
@ -364,10 +364,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"Cii");
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"Cii");
await connection.Receive(
"HTTP/1.1 400 Bad Request",

View File

@ -728,18 +728,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
await connection.SendEnd(
"GET /");
await connection.ReceiveEnd();
}
using (var connection = new TestConnection(server.Port))
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"Post / HTTP/1.1");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
await connection.Receive(
"HTTP/1.0 400 Bad Request",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveForcedEnd(
"Content-Length: 0",
"Server: Kestrel",
"",
"");
}
@ -749,12 +744,40 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"Post / HTTP/1.1",
"Content-Length: 7");
await connection.ReceiveEnd(
"POST / HTTP/1.1");
await connection.Receive(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"HTTP/1.0 400 Bad Request",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveForcedEnd(
"Content-Length: 0",
"Server: Kestrel",
"",
"");
}
using (var connection = new TestConnection(server.Port))
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"POST / HTTP/1.1",
"Content-Length: 7");
await connection.Receive(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"HTTP/1.1 400 Bad Request",
"Connection: close",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveForcedEnd(
"Content-Length: 0",
"Server: Kestrel",
"",
"");
}
}
@ -1017,7 +1040,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.True(registrationWh.Wait(1000));
}
}
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(ServiceContext testContext)
@ -1049,5 +1072,30 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(0, testLogger.TotalErrorsLogged);
}
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest(ServiceContext testContext)
{
var testLogger = new TestApplicationErrorLogger();
testContext.Log = new KestrelTrace(testLogger);
using (var server = new TestServer(httpContext =>
{
httpContext.Abort();
return Task.FromResult(0);
}, testContext))
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"POST / HTTP/1.0",
"Content-Length: 1",
"",
"");
await connection.ReceiveEnd();
}
}
}
}
}

View File

@ -285,6 +285,40 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_pool.Return(nextBlock);
}
[Fact]
public void SkipThrowsWhenSkippingMoreBytesThanAvailableInSingleBlock()
{
// Arrange
var block = _pool.Lease();
block.End += 5;
var scan = block.GetIterator();
// Act/Assert
Assert.ThrowsAny<InvalidOperationException>(() => scan.Skip(8));
_pool.Return(block);
}
[Fact]
public void SkipThrowsWhenSkippingMoreBytesThanAvailableInMultipleBlocks()
{
// Arrange
var block = _pool.Lease();
block.End += 3;
var nextBlock = _pool.Lease();
nextBlock.End += 2;
block.Next = nextBlock;
var scan = block.GetIterator();
// Act/Assert
Assert.ThrowsAny<InvalidOperationException>(() => scan.Skip(8));
_pool.Return(block);
}
[Theory]
[InlineData("CONNECT / HTTP/1.1", ' ', true, MemoryPoolIteratorExtensions.HttpConnectMethod)]
[InlineData("DELETE / HTTP/1.1", ' ', true, MemoryPoolIteratorExtensions.HttpDeleteMethod)]
@ -309,12 +343,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length;
var scan = block.GetIterator();
var begin = scan;
var begin = block.GetIterator();
string knownString;
// Act
var result = begin.GetKnownMethod(ref scan, out knownString);
var result = begin.GetKnownMethod(out knownString);
// Assert
Assert.Equal(expectedResult, result);
@ -337,12 +370,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length;
var scan = block.GetIterator();
var begin = scan;
var begin = block.GetIterator();
string knownString;
// Act
var result = begin.GetKnownVersion(ref scan, out knownString);
var result = begin.GetKnownVersion(out knownString);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(expectedKnownString, knownString);