Limit request line length (#784).
This commit is contained in:
parent
dfe12223b8
commit
8836eec7d8
|
|
@ -79,6 +79,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
case RequestRejectionReason.NonAsciiOrNullCharactersInInputString:
|
case RequestRejectionReason.NonAsciiOrNullCharactersInInputString:
|
||||||
ex = new BadHttpRequestException("The input string contains non-ASCII or null characters.");
|
ex = new BadHttpRequestException("The input string contains non-ASCII or null characters.");
|
||||||
break;
|
break;
|
||||||
|
case RequestRejectionReason.RequestLineTooLong:
|
||||||
|
ex = new BadHttpRequestException("Request line too long.");
|
||||||
|
break;
|
||||||
|
case RequestRejectionReason.MissingSpaceAfterMethod:
|
||||||
|
ex = new BadHttpRequestException("No space character found after method in request line.");
|
||||||
|
break;
|
||||||
|
case RequestRejectionReason.MissingSpaceAfterTarget:
|
||||||
|
ex = new BadHttpRequestException("No space character found after target in request line.");
|
||||||
|
break;
|
||||||
|
case RequestRejectionReason.MissingCrAfterVersion:
|
||||||
|
ex = new BadHttpRequestException("Missing CR in request line.");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ex = new BadHttpRequestException("Bad request.");
|
ex = new BadHttpRequestException("Bad request.");
|
||||||
break;
|
break;
|
||||||
|
|
@ -92,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case RequestRejectionReason.MalformedRequestLineStatus:
|
case RequestRejectionReason.MalformedRequestLineStatus:
|
||||||
ex = new BadHttpRequestException($"Malformed request: {value}");
|
ex = new BadHttpRequestException($"Invalid request line: {value}");
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.InvalidContentLength:
|
case RequestRejectionReason.InvalidContentLength:
|
||||||
ex = new BadHttpRequestException($"Invalid content length: {value}");
|
ex = new BadHttpRequestException($"Invalid content length: {value}");
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
|
ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
|
||||||
|
|
||||||
if (ServerOptions.MaxRequestBufferSize.HasValue)
|
if (ServerOptions.Limits.MaxRequestBufferSize.HasValue)
|
||||||
{
|
{
|
||||||
_bufferSizeControl = new BufferSizeControl(ServerOptions.MaxRequestBufferSize.Value, this, Thread);
|
_bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value, this, Thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
|
SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
||||||
|
|
||||||
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
|
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
|
||||||
|
private static Vector<byte> _vectorLFs = new Vector<byte>((byte)'\n');
|
||||||
private static Vector<byte> _vectorColons = new Vector<byte>((byte)':');
|
private static Vector<byte> _vectorColons = new Vector<byte>((byte)':');
|
||||||
private static Vector<byte> _vectorSpaces = new Vector<byte>((byte)' ');
|
private static Vector<byte> _vectorSpaces = new Vector<byte>((byte)' ');
|
||||||
private static Vector<byte> _vectorTabs = new Vector<byte>((byte)'\t');
|
private static Vector<byte> _vectorTabs = new Vector<byte>((byte)'\t');
|
||||||
|
|
@ -801,13 +802,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
||||||
|
|
||||||
|
var end = scan;
|
||||||
|
int bytesScanned;
|
||||||
|
if (end.Seek(ref _vectorLFs, out bytesScanned, ServerOptions.Limits.MaxRequestLineSize) == -1)
|
||||||
|
{
|
||||||
|
if (bytesScanned >= ServerOptions.Limits.MaxRequestLineSize)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.RequestLineTooLong);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return RequestLineStatus.Incomplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string method;
|
string method;
|
||||||
var begin = scan;
|
var begin = scan;
|
||||||
if (!begin.GetKnownMethod(out method))
|
if (!begin.GetKnownMethod(out method))
|
||||||
{
|
{
|
||||||
if (scan.Seek(ref _vectorSpaces) == -1)
|
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
||||||
{
|
{
|
||||||
return RequestLineStatus.MethodIncomplete;
|
RejectRequest(RequestRejectionReason.MissingSpaceAfterMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
method = begin.GetAsciiString(scan);
|
method = begin.GetAsciiString(scan);
|
||||||
|
|
@ -835,18 +850,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
scan.Take();
|
scan.Take();
|
||||||
begin = scan;
|
begin = scan;
|
||||||
var needDecode = false;
|
var needDecode = false;
|
||||||
var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages);
|
var chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref _vectorPercentages, ref end);
|
||||||
if (chFound == -1)
|
if (chFound == -1)
|
||||||
{
|
{
|
||||||
return RequestLineStatus.TargetIncomplete;
|
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
||||||
}
|
}
|
||||||
else if (chFound == '%')
|
else if (chFound == '%')
|
||||||
{
|
{
|
||||||
needDecode = true;
|
needDecode = true;
|
||||||
chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks);
|
chFound = scan.Seek(ref _vectorSpaces, ref _vectorQuestionMarks, ref end);
|
||||||
if (chFound == -1)
|
if (chFound == -1)
|
||||||
{
|
{
|
||||||
return RequestLineStatus.TargetIncomplete;
|
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -857,9 +872,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
if (chFound == '?')
|
if (chFound == '?')
|
||||||
{
|
{
|
||||||
begin = scan;
|
begin = scan;
|
||||||
if (scan.Seek(ref _vectorSpaces) == -1)
|
if (scan.Seek(ref _vectorSpaces, ref end) == -1)
|
||||||
{
|
{
|
||||||
return RequestLineStatus.TargetIncomplete;
|
RejectRequest(RequestRejectionReason.MissingSpaceAfterTarget);
|
||||||
}
|
}
|
||||||
queryString = begin.GetAsciiString(scan);
|
queryString = begin.GetAsciiString(scan);
|
||||||
}
|
}
|
||||||
|
|
@ -873,9 +888,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
scan.Take();
|
scan.Take();
|
||||||
begin = scan;
|
begin = scan;
|
||||||
if (scan.Seek(ref _vectorCRs) == -1)
|
if (scan.Seek(ref _vectorCRs, ref end) == -1)
|
||||||
{
|
{
|
||||||
return RequestLineStatus.VersionIncomplete;
|
RejectRequest(RequestRejectionReason.MissingCrAfterVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
string httpVersion;
|
string httpVersion;
|
||||||
|
|
@ -898,16 +913,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scan.Take();
|
scan.Take(); // consume CR
|
||||||
var next = scan.Take();
|
if (scan.Block != end.Block || scan.Index != end.Index)
|
||||||
if (next == -1)
|
|
||||||
{
|
|
||||||
return RequestLineStatus.Incomplete;
|
|
||||||
}
|
|
||||||
else if (next != '\n')
|
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.MissingLFInRequestLine);
|
RejectRequest(RequestRejectionReason.MissingLFInRequestLine);
|
||||||
}
|
}
|
||||||
|
scan.Take(); // consume LF
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
@ -1155,7 +1166,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
// Trim trailing whitespace from header value by repeatedly advancing to next
|
// Trim trailing whitespace from header value by repeatedly advancing to next
|
||||||
// whitespace or CR.
|
// whitespace or CR.
|
||||||
//
|
//
|
||||||
// - If CR is found, this is the end of the header value.
|
// - If CR is found, this is the end of the header value.
|
||||||
// - If whitespace is found, this is the _tentative_ end of the header value.
|
// - If whitespace is found, this is the _tentative_ end of the header value.
|
||||||
// If non-whitespace is found after it and it's not CR, seek again to the next
|
// If non-whitespace is found after it and it's not CR, seek again to the next
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
ChunkedRequestIncomplete,
|
ChunkedRequestIncomplete,
|
||||||
PathContainsNullCharacters,
|
PathContainsNullCharacters,
|
||||||
InvalidCharactersInHeaderName,
|
InvalidCharactersInHeaderName,
|
||||||
NonAsciiOrNullCharactersInInputString
|
NonAsciiOrNullCharactersInInputString,
|
||||||
|
RequestLineTooLong,
|
||||||
|
MissingSpaceAfterMethod,
|
||||||
|
MissingSpaceAfterTarget,
|
||||||
|
MissingCrAfterVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,123 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe int Seek(ref Vector<byte> byte0Vector)
|
public unsafe int Seek(ref Vector<byte> byte0Vector)
|
||||||
|
{
|
||||||
|
int bytesScanned;
|
||||||
|
return Seek(ref byte0Vector, out bytesScanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe int Seek(
|
||||||
|
ref Vector<byte> byte0Vector,
|
||||||
|
out int bytesScanned,
|
||||||
|
int limit = int.MaxValue)
|
||||||
|
{
|
||||||
|
bytesScanned = 0;
|
||||||
|
|
||||||
|
if (IsDefault || limit <= 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var block = _block;
|
||||||
|
var index = _index;
|
||||||
|
var wasLastBlock = block.Next == null;
|
||||||
|
var following = block.End - index;
|
||||||
|
byte[] array;
|
||||||
|
var byte0 = byte0Vector[0];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (following == 0)
|
||||||
|
{
|
||||||
|
if (bytesScanned >= limit || wasLastBlock)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
wasLastBlock = block.Next == null;
|
||||||
|
following = block.End - index;
|
||||||
|
}
|
||||||
|
array = block.Array;
|
||||||
|
while (following > 0)
|
||||||
|
{
|
||||||
|
// Need unit tests to test Vector path
|
||||||
|
#if !DEBUG
|
||||||
|
// Check will be Jitted away https://github.com/dotnet/coreclr/issues/1079
|
||||||
|
if (Vector.IsHardwareAccelerated)
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
if (following >= _vectorSpan)
|
||||||
|
{
|
||||||
|
var byte0Equals = Vector.Equals(new Vector<byte>(array, index), byte0Vector);
|
||||||
|
|
||||||
|
if (byte0Equals.Equals(Vector<byte>.Zero))
|
||||||
|
{
|
||||||
|
if (bytesScanned + _vectorSpan >= limit)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = index + (limit - bytesScanned);
|
||||||
|
bytesScanned = limit;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesScanned += _vectorSpan;
|
||||||
|
following -= _vectorSpan;
|
||||||
|
index += _vectorSpan;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_block = block;
|
||||||
|
|
||||||
|
var firstEqualByteIndex = FindFirstEqualByte(ref byte0Equals);
|
||||||
|
var vectorBytesScanned = firstEqualByteIndex + 1;
|
||||||
|
|
||||||
|
if (bytesScanned + vectorBytesScanned > limit)
|
||||||
|
{
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = index + (limit - bytesScanned);
|
||||||
|
bytesScanned = limit;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_index = index + firstEqualByteIndex;
|
||||||
|
bytesScanned += vectorBytesScanned;
|
||||||
|
|
||||||
|
return byte0;
|
||||||
|
}
|
||||||
|
// Need unit tests to test Vector path
|
||||||
|
#if !DEBUG
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var pCurrent = (block.DataFixedPtr + index);
|
||||||
|
var pEnd = pCurrent + Math.Min(following, limit - bytesScanned);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytesScanned++;
|
||||||
|
if (*pCurrent == byte0)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index;
|
||||||
|
return byte0;
|
||||||
|
}
|
||||||
|
pCurrent++;
|
||||||
|
index++;
|
||||||
|
} while (pCurrent < pEnd);
|
||||||
|
|
||||||
|
following = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe int Seek(
|
||||||
|
ref Vector<byte> byte0Vector,
|
||||||
|
ref MemoryPoolIterator limit)
|
||||||
{
|
{
|
||||||
if (IsDefault)
|
if (IsDefault)
|
||||||
{
|
{
|
||||||
|
|
@ -233,12 +350,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
while (following == 0)
|
while (following == 0)
|
||||||
{
|
{
|
||||||
if (wasLastBlock)
|
if ((block == limit.Block && index > limit.Index) ||
|
||||||
|
wasLastBlock)
|
||||||
{
|
{
|
||||||
_block = block;
|
_block = block;
|
||||||
_index = index;
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
block = block.Next;
|
block = block.Next;
|
||||||
index = block.Start;
|
index = block.Start;
|
||||||
wasLastBlock = block.Next == null;
|
wasLastBlock = block.Next == null;
|
||||||
|
|
@ -259,13 +379,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
|
|
||||||
if (byte0Equals.Equals(Vector<byte>.Zero))
|
if (byte0Equals.Equals(Vector<byte>.Zero))
|
||||||
{
|
{
|
||||||
|
if (block == limit.Block && index + _vectorSpan > limit.Index)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
following -= _vectorSpan;
|
following -= _vectorSpan;
|
||||||
index += _vectorSpan;
|
index += _vectorSpan;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_block = block;
|
_block = block;
|
||||||
_index = index + FindFirstEqualByte(ref byte0Equals);
|
|
||||||
|
var firstEqualByteIndex = FindFirstEqualByte(ref byte0Equals);
|
||||||
|
var vectorBytesScanned = firstEqualByteIndex + 1;
|
||||||
|
|
||||||
|
if (_block == limit.Block && index + firstEqualByteIndex > limit.Index)
|
||||||
|
{
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_index = index + firstEqualByteIndex;
|
||||||
|
|
||||||
return byte0;
|
return byte0;
|
||||||
}
|
}
|
||||||
// Need unit tests to test Vector path
|
// Need unit tests to test Vector path
|
||||||
|
|
@ -274,7 +414,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
var pCurrent = (block.DataFixedPtr + index);
|
||||||
var pEnd = pCurrent + following;
|
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (*pCurrent == byte0)
|
if (*pCurrent == byte0)
|
||||||
|
|
@ -294,6 +434,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe int Seek(ref Vector<byte> byte0Vector, ref Vector<byte> byte1Vector)
|
public unsafe int Seek(ref Vector<byte> byte0Vector, ref Vector<byte> byte1Vector)
|
||||||
|
{
|
||||||
|
var limit = new MemoryPoolIterator();
|
||||||
|
return Seek(ref byte0Vector, ref byte1Vector, ref limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe int Seek(
|
||||||
|
ref Vector<byte> byte0Vector,
|
||||||
|
ref Vector<byte> byte1Vector,
|
||||||
|
ref MemoryPoolIterator limit)
|
||||||
{
|
{
|
||||||
if (IsDefault)
|
if (IsDefault)
|
||||||
{
|
{
|
||||||
|
|
@ -314,10 +463,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
while (following == 0)
|
while (following == 0)
|
||||||
{
|
{
|
||||||
if (wasLastBlock)
|
if ((block == limit.Block && index > limit.Index) ||
|
||||||
|
wasLastBlock)
|
||||||
{
|
{
|
||||||
_block = block;
|
_block = block;
|
||||||
_index = index;
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
block = block.Next;
|
block = block.Next;
|
||||||
|
|
@ -354,6 +505,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
following -= _vectorSpan;
|
following -= _vectorSpan;
|
||||||
index += _vectorSpan;
|
index += _vectorSpan;
|
||||||
|
|
||||||
|
if (block == limit.Block && index > limit.Index)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,10 +522,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
if (byte0Index < byte1Index)
|
if (byte0Index < byte1Index)
|
||||||
{
|
{
|
||||||
_index = index + byte0Index;
|
_index = index + byte0Index;
|
||||||
|
|
||||||
|
if (block == limit.Block && _index > limit.Index)
|
||||||
|
{
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return byte0;
|
return byte0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_index = index + byte1Index;
|
_index = index + byte1Index;
|
||||||
|
|
||||||
|
if (block == limit.Block && _index > limit.Index)
|
||||||
|
{
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return byte1;
|
return byte1;
|
||||||
}
|
}
|
||||||
// Need unit tests to test Vector path
|
// Need unit tests to test Vector path
|
||||||
|
|
@ -373,7 +549,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
var pCurrent = (block.DataFixedPtr + index);
|
||||||
var pEnd = pCurrent + following;
|
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (*pCurrent == byte0)
|
if (*pCurrent == byte0)
|
||||||
|
|
@ -399,6 +575,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe int Seek(ref Vector<byte> byte0Vector, ref Vector<byte> byte1Vector, ref Vector<byte> byte2Vector)
|
public unsafe int Seek(ref Vector<byte> byte0Vector, ref Vector<byte> byte1Vector, ref Vector<byte> byte2Vector)
|
||||||
|
{
|
||||||
|
var limit = new MemoryPoolIterator();
|
||||||
|
return Seek(ref byte0Vector, ref byte1Vector, ref byte2Vector, ref limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe int Seek(
|
||||||
|
ref Vector<byte> byte0Vector,
|
||||||
|
ref Vector<byte> byte1Vector,
|
||||||
|
ref Vector<byte> byte2Vector,
|
||||||
|
ref MemoryPoolIterator limit)
|
||||||
{
|
{
|
||||||
if (IsDefault)
|
if (IsDefault)
|
||||||
{
|
{
|
||||||
|
|
@ -421,10 +607,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
while (following == 0)
|
while (following == 0)
|
||||||
{
|
{
|
||||||
if (wasLastBlock)
|
if ((block == limit.Block && index > limit.Index) ||
|
||||||
|
wasLastBlock)
|
||||||
{
|
{
|
||||||
_block = block;
|
_block = block;
|
||||||
_index = index;
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
block = block.Next;
|
block = block.Next;
|
||||||
|
|
@ -465,6 +653,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
following -= _vectorSpan;
|
following -= _vectorSpan;
|
||||||
index += _vectorSpan;
|
index += _vectorSpan;
|
||||||
|
|
||||||
|
if (block == limit.Block && index > limit.Index)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,6 +696,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
_index = index + toMove;
|
_index = index + toMove;
|
||||||
|
|
||||||
|
if (block == limit.Block && _index > limit.Index)
|
||||||
|
{
|
||||||
|
// Ensure iterator is left at limit position
|
||||||
|
_index = limit.Index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
// Need unit tests to test Vector path
|
// Need unit tests to test Vector path
|
||||||
|
|
@ -506,7 +711,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
var pCurrent = (block.DataFixedPtr + index);
|
||||||
var pEnd = pCurrent + following;
|
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (*pCurrent == byte0)
|
if (*pCurrent == byte0)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
|
||||||
public void Start<TContext>(IHttpApplication<TContext> application)
|
public void Start<TContext>(IHttpApplication<TContext> application)
|
||||||
{
|
{
|
||||||
|
ValidateOptions();
|
||||||
|
|
||||||
if (_disposables != null)
|
if (_disposables != null)
|
||||||
{
|
{
|
||||||
// The server has already started and/or has not been cleaned up yet
|
// The server has already started and/or has not been cleaned up yet
|
||||||
|
|
@ -196,5 +198,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
_disposables = null;
|
_disposables = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateOptions()
|
||||||
|
{
|
||||||
|
if (Options.Limits.MaxRequestBufferSize.HasValue &&
|
||||||
|
Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestLineSize)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Maximum request buffer size ({Options.Limits.MaxRequestBufferSize.Value}) must be greater than or equal to maximum request line size ({Options.Limits.MaxRequestLineSize}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// 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.Server.Kestrel
|
||||||
|
{
|
||||||
|
public class KestrelServerLimits
|
||||||
|
{
|
||||||
|
// Matches the default client_max_body_size in nginx. Also large enough that most requests
|
||||||
|
// should be under the limit.
|
||||||
|
private long? _maxRequestBufferSize = 1024 * 1024;
|
||||||
|
|
||||||
|
// Matches the default large_client_header_buffers in nginx.
|
||||||
|
private int _maxRequestLineSize = 8 * 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum size of the request buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When set to null, the size of the request buffer is unlimited.
|
||||||
|
/// Defaults to 1,048,576 bytes (1 MB).
|
||||||
|
/// </remarks>
|
||||||
|
public long? MaxRequestBufferSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _maxRequestBufferSize;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.HasValue && value.Value <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Value must be null or a positive integer.");
|
||||||
|
}
|
||||||
|
_maxRequestBufferSize = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum allowed size for the HTTP request line.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to 8,192 bytes (8 KB).
|
||||||
|
/// </remarks>
|
||||||
|
public int MaxRequestLineSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _maxRequestLineSize;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Value must be a positive integer.");
|
||||||
|
}
|
||||||
|
_maxRequestLineSize = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
// 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;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||||
|
|
||||||
|
|
@ -11,10 +8,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class KestrelServerOptions
|
public class KestrelServerOptions
|
||||||
{
|
{
|
||||||
// Matches the default client_max_body_size in nginx. Also large enough that most requests
|
|
||||||
// should be under the limit.
|
|
||||||
private long? _maxRequestBufferSize = 1024 * 1024;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the <c>Server</c> header should be included in each response.
|
/// Gets or sets whether the <c>Server</c> header should be included in each response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -41,28 +34,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
public IConnectionFilter ConnectionFilter { get; set; }
|
public IConnectionFilter ConnectionFilter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum size of the request buffer.
|
/// <para>
|
||||||
/// If value is null, the size of the request buffer is unlimited.
|
/// This property is obsolete and will be removed in a future version.
|
||||||
|
/// Use <c>Limits.MaxRequestBufferSize</c> instead.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Gets or sets the maximum size of the request buffer.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// When set to null, the size of the request buffer is unlimited.
|
||||||
/// Defaults to 1,048,576 bytes (1 MB).
|
/// Defaults to 1,048,576 bytes (1 MB).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
[Obsolete]
|
||||||
public long? MaxRequestBufferSize
|
public long? MaxRequestBufferSize
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _maxRequestBufferSize;
|
return Limits.MaxRequestBufferSize;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value.HasValue && value.Value <= 0)
|
Limits.MaxRequestBufferSize = value;
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException("value", "Value must be null or a positive integer.");
|
|
||||||
}
|
|
||||||
_maxRequestBufferSize = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to request limit options.
|
||||||
|
/// </summary>
|
||||||
|
public KestrelServerLimits Limits { get; } = new KestrelServerLimits();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set to false to enable Nagle's algorithm for all connections.
|
/// Set to false to enable Nagle's algorithm for all connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var maxRequestBufferSizeValues = new Tuple<long?, bool>[] {
|
var maxRequestBufferSizeValues = new Tuple<long?, bool>[] {
|
||||||
// Smallest allowed buffer. Server should call pause/resume between each read.
|
// Smallest buffer that can hold a POST request line to the root.
|
||||||
Tuple.Create((long?)1, true),
|
Tuple.Create((long?)"POST / HTTP/1.1\r\n".Length, true),
|
||||||
|
|
||||||
// Small buffer, but large enough to hold all request headers.
|
// Small buffer, but large enough to hold all request headers.
|
||||||
Tuple.Create((long?)16 * 1024, true),
|
Tuple.Create((long?)16 * 1024, true),
|
||||||
|
|
@ -171,8 +171,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseKestrel(options =>
|
.UseKestrel(options =>
|
||||||
{
|
{
|
||||||
options.MaxRequestBufferSize = maxRequestBufferSize;
|
options.Limits.MaxRequestBufferSize = maxRequestBufferSize;
|
||||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||||
|
|
||||||
|
if (maxRequestBufferSize.HasValue &&
|
||||||
|
maxRequestBufferSize.Value < options.Limits.MaxRequestLineSize)
|
||||||
|
{
|
||||||
|
options.Limits.MaxRequestLineSize = (int)maxRequestBufferSize;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.UseUrls("http://127.0.0.1:0/", "https://127.0.0.1:0/")
|
.UseUrls("http://127.0.0.1:0/", "https://127.0.0.1:0/")
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
{
|
||||||
|
public class MaxRequestLineSizeTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GET / HTTP/1.1\r\n", 16)]
|
||||||
|
[InlineData("GET / HTTP/1.1\r\n", 17)]
|
||||||
|
[InlineData("GET / HTTP/1.1\r\n", 137)]
|
||||||
|
[InlineData("POST /abc/de HTTP/1.1\r\n", 23)]
|
||||||
|
[InlineData("POST /abc/de HTTP/1.1\r\n", 24)]
|
||||||
|
[InlineData("POST /abc/de HTTP/1.1\r\n", 287)]
|
||||||
|
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 28)]
|
||||||
|
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 29)]
|
||||||
|
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 589)]
|
||||||
|
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 40)]
|
||||||
|
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 41)]
|
||||||
|
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 1027)]
|
||||||
|
public async Task ServerAcceptsRequestLineWithinLimit(string requestLine, int limit)
|
||||||
|
{
|
||||||
|
var maxRequestLineSize = limit;
|
||||||
|
|
||||||
|
using (var host = BuildWebHost(options =>
|
||||||
|
{
|
||||||
|
options.Limits.MaxRequestLineSize = maxRequestLineSize;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
host.Start();
|
||||||
|
|
||||||
|
using (var connection = new TestConnection(host.GetPort()))
|
||||||
|
{
|
||||||
|
await connection.SendEnd($"{requestLine}\r\n");
|
||||||
|
await connection.Receive($"HTTP/1.1 200 OK\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GET / HTTP/1.1\r\n")]
|
||||||
|
[InlineData("POST /abc/de HTTP/1.1\r\n")]
|
||||||
|
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n")]
|
||||||
|
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n")]
|
||||||
|
public async Task ServerRejectsRequestLineExceedingLimit(string requestLine)
|
||||||
|
{
|
||||||
|
using (var host = BuildWebHost(options =>
|
||||||
|
{
|
||||||
|
options.Limits.MaxRequestLineSize = requestLine.Length - 1; // stop short of the '\n'
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
host.Start();
|
||||||
|
|
||||||
|
using (var connection = new TestConnection(host.GetPort()))
|
||||||
|
{
|
||||||
|
await connection.SendAllTryEnd($"{requestLine}\r\n");
|
||||||
|
await connection.Receive($"HTTP/1.1 400 Bad Request\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1, 2)]
|
||||||
|
[InlineData(int.MaxValue - 1, int.MaxValue)]
|
||||||
|
public void ServerFailsToStartWhenMaxRequestBufferSizeIsLessThanMaxRequestLineSize(long maxRequestBufferSize, int maxRequestLineSize)
|
||||||
|
{
|
||||||
|
using (var host = BuildWebHost(options =>
|
||||||
|
{
|
||||||
|
options.Limits.MaxRequestBufferSize = maxRequestBufferSize;
|
||||||
|
options.Limits.MaxRequestLineSize = maxRequestLineSize;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() => host.Start());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWebHost BuildWebHost(Action<KestrelServerOptions> options)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseKestrel(options)
|
||||||
|
.UseUrls("http://127.0.0.1:0/")
|
||||||
|
.Configure(app => app.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("hello, world");
|
||||||
|
}))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,64 +10,47 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class BadHttpRequestTests
|
public class BadHttpRequestTests
|
||||||
{
|
{
|
||||||
// Don't send more data than necessary to fail, otherwise the test throws trying to
|
// All test cases for this theory must end in '\n', otherwise the server will spin forever
|
||||||
// send data after the server has already closed the connection. This would cause the
|
|
||||||
// test to fail on Windows, due to a winsock limitation: after the error when trying
|
|
||||||
// to write to the socket closed by the server, winsock disposes all resources used
|
|
||||||
// by that socket. The test then fails when we try to read the expected response
|
|
||||||
// from the server because, although it would have been buffered, it got discarded
|
|
||||||
// by winsock on the send() error.
|
|
||||||
// The solution for this is for the client to always try to receive before doing
|
|
||||||
// any sends, that way it can detect that the connection has been closed by the server
|
|
||||||
// and not try to send() on the closed connection, triggering the error that would cause
|
|
||||||
// any buffered received data to be lost.
|
|
||||||
// We do not deem necessary to mitigate this issue in TestConnection, since it would only
|
|
||||||
// be ensuring that we have a properly implemented HTTP client that can handle the
|
|
||||||
// winsock issue. There is nothing to be verified in Kestrel in this situation.
|
|
||||||
[Theory]
|
[Theory]
|
||||||
// Incomplete request lines
|
// Incomplete request lines
|
||||||
[InlineData("G")]
|
[InlineData("G\r\n")]
|
||||||
[InlineData("GE")]
|
[InlineData("GE\r\n")]
|
||||||
[InlineData("GET")]
|
[InlineData("GET\r\n")]
|
||||||
[InlineData("GET ")]
|
[InlineData("GET \r\n")]
|
||||||
[InlineData("GET /")]
|
[InlineData("GET /\r\n")]
|
||||||
[InlineData("GET / ")]
|
[InlineData("GET / \r\n")]
|
||||||
[InlineData("GET / H")]
|
[InlineData("GET / H\r\n")]
|
||||||
[InlineData("GET / HT")]
|
[InlineData("GET / HT\r\n")]
|
||||||
[InlineData("GET / HTT")]
|
[InlineData("GET / HTT\r\n")]
|
||||||
[InlineData("GET / HTTP")]
|
[InlineData("GET / HTTP\r\n")]
|
||||||
[InlineData("GET / HTTP/")]
|
[InlineData("GET / HTTP/\r\n")]
|
||||||
[InlineData("GET / HTTP/1")]
|
[InlineData("GET / HTTP/1\r\n")]
|
||||||
[InlineData("GET / HTTP/1.")]
|
[InlineData("GET / HTTP/1.\r\n")]
|
||||||
[InlineData("GET / HTTP/1.1")]
|
|
||||||
[InlineData("GET / HTTP/1.1\r")]
|
|
||||||
[InlineData("GET / HTTP/1.0")]
|
|
||||||
[InlineData("GET / HTTP/1.0\r")]
|
|
||||||
// Missing method
|
// Missing method
|
||||||
[InlineData(" ")]
|
[InlineData(" \r\n")]
|
||||||
// Missing second space
|
// Missing second space
|
||||||
[InlineData("/ ")] // This fails trying to read the '/' because that's invalid for an HTTP method
|
[InlineData("/ \r\n")] // This fails trying to read the '/' because that's invalid for an HTTP method
|
||||||
[InlineData("GET /\r\n")]
|
[InlineData("GET /\r\n")]
|
||||||
// Missing target
|
// Missing target
|
||||||
[InlineData("GET ")]
|
[InlineData("GET \r\n")]
|
||||||
// Missing version
|
// Missing version
|
||||||
[InlineData("GET / \r")]
|
[InlineData("GET / \r\n")]
|
||||||
// Missing CR
|
// Missing CR
|
||||||
[InlineData("GET / \n")]
|
[InlineData("GET / \n")]
|
||||||
// Unrecognized HTTP version
|
// Unrecognized HTTP version
|
||||||
[InlineData("GET / http/1.0\r")]
|
[InlineData("GET / http/1.0\r\n")]
|
||||||
[InlineData("GET / http/1.1\r")]
|
[InlineData("GET / http/1.1\r\n")]
|
||||||
[InlineData("GET / HTTP/1.1 \r")]
|
[InlineData("GET / HTTP/1.1 \r\n")]
|
||||||
[InlineData("GET / HTTP/1.1a\r")]
|
[InlineData("GET / HTTP/1.1a\r\n")]
|
||||||
[InlineData("GET / HTTP/1.0\n\r")]
|
[InlineData("GET / HTTP/1.0\n\r\n")]
|
||||||
[InlineData("GET / HTTP/1.2\r")]
|
[InlineData("GET / HTTP/1.2\r\n")]
|
||||||
[InlineData("GET / HTTP/3.0\r")]
|
[InlineData("GET / HTTP/3.0\r\n")]
|
||||||
[InlineData("GET / H\r")]
|
[InlineData("GET / H\r\n")]
|
||||||
[InlineData("GET / HTTP/1.\r")]
|
[InlineData("GET / HTTP/1.\r\n")]
|
||||||
[InlineData("GET / hello\r")]
|
[InlineData("GET / hello\r\n")]
|
||||||
[InlineData("GET / 8charact\r")]
|
[InlineData("GET / 8charact\r\n")]
|
||||||
// Missing LF
|
// Missing LF after CR
|
||||||
[InlineData("GET / HTTP/1.0\rA")]
|
[InlineData("GET / HTTP/1.0\rA\n")]
|
||||||
// Bad HTTP Methods (invalid according to RFC)
|
// Bad HTTP Methods (invalid according to RFC)
|
||||||
[InlineData("( / HTTP/1.0\r\n")]
|
[InlineData("( / HTTP/1.0\r\n")]
|
||||||
[InlineData(") / HTTP/1.0\r\n")]
|
[InlineData(") / HTTP/1.0\r\n")]
|
||||||
|
|
@ -88,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[InlineData("} / HTTP/1.0\r\n")]
|
[InlineData("} / HTTP/1.0\r\n")]
|
||||||
[InlineData("get@ / HTTP/1.0\r\n")]
|
[InlineData("get@ / HTTP/1.0\r\n")]
|
||||||
[InlineData("post= / HTTP/1.0\r\n")]
|
[InlineData("post= / HTTP/1.0\r\n")]
|
||||||
public async Task TestBadRequestLines(string request)
|
public async Task TestInvalidRequestLines(string request)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => TaskUtilities.CompletedTask))
|
using (var server = new TestServer(context => TaskUtilities.CompletedTask))
|
||||||
{
|
{
|
||||||
|
|
@ -100,6 +83,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove test once people agree to change this behavior
|
||||||
|
/*
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(" ")]
|
[InlineData(" ")]
|
||||||
[InlineData("GET ")]
|
[InlineData("GET ")]
|
||||||
|
|
@ -136,6 +121,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
// Missing final CRLF
|
// Missing final CRLF
|
||||||
|
|
@ -219,13 +205,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedDateHeaderValue)
|
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedDateHeaderValue)
|
||||||
{
|
{
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 400 Bad Request",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"Connection: close",
|
|
||||||
"");
|
|
||||||
await connection.ReceiveForcedEnd(
|
await connection.ReceiveForcedEnd(
|
||||||
|
"HTTP/1.1 400 Bad Request",
|
||||||
|
"Connection: close",
|
||||||
$"Date: {expectedDateHeaderValue}",
|
$"Date: {expectedDateHeaderValue}",
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public class KestrelServerLimitsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void MaxRequestBufferSizeDefault()
|
||||||
|
{
|
||||||
|
Assert.Equal(1024 * 1024, (new KestrelServerLimits()).MaxRequestBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-1)]
|
||||||
|
[InlineData(0)]
|
||||||
|
public void MaxRequestBufferSizeInvalid(int value)
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||||
|
{
|
||||||
|
(new KestrelServerLimits()).MaxRequestBufferSize = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null)]
|
||||||
|
[InlineData(1)]
|
||||||
|
public void MaxRequestBufferSizeValid(int? value)
|
||||||
|
{
|
||||||
|
var o = new KestrelServerLimits();
|
||||||
|
o.MaxRequestBufferSize = value;
|
||||||
|
Assert.Equal(value, o.MaxRequestBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MaxRequestLineSizeDefault()
|
||||||
|
{
|
||||||
|
Assert.Equal(8 * 1024, (new KestrelServerLimits()).MaxRequestLineSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(int.MinValue)]
|
||||||
|
[InlineData(-1)]
|
||||||
|
[InlineData(0)]
|
||||||
|
public void MaxRequestLineSizeInvalid(int value)
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||||
|
{
|
||||||
|
(new KestrelServerLimits()).MaxRequestLineSize = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(int.MaxValue)]
|
||||||
|
public void MaxRequestLineSizeValid(int value)
|
||||||
|
{
|
||||||
|
var o = new KestrelServerLimits();
|
||||||
|
o.MaxRequestLineSize = value;
|
||||||
|
Assert.Equal(value, o.MaxRequestLineSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,42 +2,43 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class KestrelServerInformationTests
|
public class KestrelServerInformationTests
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS0612
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MaxRequestBufferSizeDefault()
|
public void MaxRequestBufferSizeIsMarkedObsolete()
|
||||||
{
|
{
|
||||||
Assert.Equal(1024 * 1024, (new KestrelServerOptions()).MaxRequestBufferSize);
|
Assert.NotNull(typeof(KestrelServerOptions)
|
||||||
|
.GetProperty(nameof(KestrelServerOptions.MaxRequestBufferSize))
|
||||||
|
.GetCustomAttributes(false)
|
||||||
|
.OfType<ObsoleteAttribute>()
|
||||||
|
.SingleOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(-1)]
|
public void MaxRequestBufferSizeGetsLimitsProperty()
|
||||||
[InlineData(0)]
|
|
||||||
public void MaxRequestBufferSizeInvalid(int value)
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
||||||
{
|
|
||||||
(new KestrelServerOptions()).MaxRequestBufferSize = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(null)]
|
|
||||||
[InlineData(1)]
|
|
||||||
public void MaxRequestBufferSizeValid(int? value)
|
|
||||||
{
|
{
|
||||||
var o = new KestrelServerOptions();
|
var o = new KestrelServerOptions();
|
||||||
o.MaxRequestBufferSize = value;
|
o.Limits.MaxRequestBufferSize = 42;
|
||||||
Assert.Equal(value, o.MaxRequestBufferSize);
|
Assert.Equal(42, o.MaxRequestBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MaxRequestBufferSizeSetsLimitsProperty()
|
||||||
|
{
|
||||||
|
var o = new KestrelServerOptions();
|
||||||
|
o.MaxRequestBufferSize = 42;
|
||||||
|
Assert.Equal(42, o.Limits.MaxRequestBufferSize);
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0612
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SetThreadCountUsingProcessorCount()
|
public void SetThreadCountUsingProcessorCount()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
@ -21,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindFirstEqualByte()
|
public void TestFindFirstEqualByte()
|
||||||
{
|
{
|
||||||
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
|
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
|
||||||
for (int i = 0; i < Vector<byte>.Count; i++)
|
for (int i = 0; i < Vector<byte>.Count; i++)
|
||||||
|
|
@ -41,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindFirstEqualByteSlow()
|
public void TestFindFirstEqualByteSlow()
|
||||||
{
|
{
|
||||||
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
|
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
|
||||||
for (int i = 0; i < Vector<byte>.Count; i++)
|
for (int i = 0; i < Vector<byte>.Count; i++)
|
||||||
|
|
@ -406,6 +411,255 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
TestKnownStringsInterning(input, expected, MemoryPoolIteratorExtensions.GetKnownMethod);
|
TestKnownStringsInterning(input, expected, MemoryPoolIteratorExtensions.GetKnownMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SeekByteLimitData))]
|
||||||
|
public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var seekVector = new Vector<byte>((byte)seek);
|
||||||
|
|
||||||
|
block = _pool.Lease();
|
||||||
|
var chars = input.ToString().ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
|
||||||
|
block.End += chars.Length;
|
||||||
|
var scan = block.GetIterator();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
int bytesScanned;
|
||||||
|
var returnValue = scan.Seek(ref seekVector, out bytesScanned, limit);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedBytesScanned, bytesScanned);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue);
|
||||||
|
|
||||||
|
Assert.Same(block, scan.Block);
|
||||||
|
var expectedEndIndex = expectedReturnValue != -1 ?
|
||||||
|
block.Start + input.IndexOf(seek) :
|
||||||
|
block.Start + expectedBytesScanned;
|
||||||
|
Assert.Equal(expectedEndIndex, scan.Index);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (block != null) _pool.Return(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SeekByteLimitData))]
|
||||||
|
public void TestSeekByteLimitAcrossBlocks(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block1 = null;
|
||||||
|
MemoryPoolBlock block2 = null;
|
||||||
|
MemoryPoolBlock emptyBlock = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var seekVector = new Vector<byte>((byte)seek);
|
||||||
|
|
||||||
|
var input1 = input.Substring(0, input.Length / 2);
|
||||||
|
block1 = _pool.Lease();
|
||||||
|
var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
|
||||||
|
block1.End += chars1.Length;
|
||||||
|
|
||||||
|
emptyBlock = _pool.Lease();
|
||||||
|
block1.Next = emptyBlock;
|
||||||
|
|
||||||
|
var input2 = input.Substring(input.Length / 2);
|
||||||
|
block2 = _pool.Lease();
|
||||||
|
var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
|
||||||
|
block2.End += chars2.Length;
|
||||||
|
emptyBlock.Next = block2;
|
||||||
|
|
||||||
|
var scan = block1.GetIterator();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
int bytesScanned;
|
||||||
|
var returnValue = scan.Seek(ref seekVector, out bytesScanned, limit);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedBytesScanned, bytesScanned);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue);
|
||||||
|
|
||||||
|
var seekCharIndex = input.IndexOf(seek);
|
||||||
|
var expectedEndBlock = limit <= input.Length / 2 ?
|
||||||
|
block1 :
|
||||||
|
(seekCharIndex != -1 && seekCharIndex < input.Length / 2 ? block1 : block2);
|
||||||
|
Assert.Same(expectedEndBlock, scan.Block);
|
||||||
|
var expectedEndIndex = expectedReturnValue != -1 ?
|
||||||
|
expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) :
|
||||||
|
expectedEndBlock.Start + (expectedEndBlock == block1 ? expectedBytesScanned : expectedBytesScanned - (input.Length / 2));
|
||||||
|
Assert.Equal(expectedEndIndex, scan.Index);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (block1 != null) _pool.Return(block1);
|
||||||
|
if (emptyBlock != null) _pool.Return(emptyBlock);
|
||||||
|
if (block2 != null) _pool.Return(block2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SeekIteratorLimitData))]
|
||||||
|
public void TestSeekIteratorLimitWithinSameBlock(string input, char seek, char limitAt, int expectedReturnValue)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var seekVector = new Vector<byte>((byte)seek);
|
||||||
|
var limitAtVector = new Vector<byte>((byte)limitAt);
|
||||||
|
var afterSeekVector = new Vector<byte>((byte)'B');
|
||||||
|
|
||||||
|
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 scan1 = block.GetIterator();
|
||||||
|
var scan2_1 = scan1;
|
||||||
|
var scan2_2 = scan1;
|
||||||
|
var scan3_1 = scan1;
|
||||||
|
var scan3_2 = scan1;
|
||||||
|
var scan3_3 = scan1;
|
||||||
|
var end = scan1;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endReturnValue = end.Seek(ref limitAtVector);
|
||||||
|
var returnValue1 = scan1.Seek(ref seekVector, ref end);
|
||||||
|
var returnValue2_1 = scan2_1.Seek(ref seekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue2_2 = scan2_2.Seek(ref afterSeekVector, ref seekVector, ref end);
|
||||||
|
var returnValue3_1 = scan3_1.Seek(ref seekVector, ref afterSeekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue3_2 = scan3_2.Seek(ref afterSeekVector, ref seekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue3_3 = scan3_3.Seek(ref afterSeekVector, ref afterSeekVector, ref seekVector, ref end);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue2_1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue2_2);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_2);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_3);
|
||||||
|
|
||||||
|
Assert.Same(block, scan1.Block);
|
||||||
|
Assert.Same(block, scan2_1.Block);
|
||||||
|
Assert.Same(block, scan2_2.Block);
|
||||||
|
Assert.Same(block, scan3_1.Block);
|
||||||
|
Assert.Same(block, scan3_2.Block);
|
||||||
|
Assert.Same(block, scan3_3.Block);
|
||||||
|
|
||||||
|
var expectedEndIndex = expectedReturnValue != -1 ? block.Start + input.IndexOf(seek) : end.Index;
|
||||||
|
Assert.Equal(expectedEndIndex, scan1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan2_1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan2_2.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_2.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_3.Index);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (block != null) _pool.Return(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SeekIteratorLimitData))]
|
||||||
|
public void TestSeekIteratorLimitAcrossBlocks(string input, char seek, char limitAt, int expectedReturnValue)
|
||||||
|
{
|
||||||
|
MemoryPoolBlock block1 = null;
|
||||||
|
MemoryPoolBlock block2 = null;
|
||||||
|
MemoryPoolBlock emptyBlock = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var seekVector = new Vector<byte>((byte)seek);
|
||||||
|
var limitAtVector = new Vector<byte>((byte)limitAt);
|
||||||
|
var afterSeekVector = new Vector<byte>((byte)'B');
|
||||||
|
|
||||||
|
var input1 = input.Substring(0, input.Length / 2);
|
||||||
|
block1 = _pool.Lease();
|
||||||
|
var chars1 = input1.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
|
||||||
|
block1.End += chars1.Length;
|
||||||
|
|
||||||
|
emptyBlock = _pool.Lease();
|
||||||
|
block1.Next = emptyBlock;
|
||||||
|
|
||||||
|
var input2 = input.Substring(input.Length / 2);
|
||||||
|
block2 = _pool.Lease();
|
||||||
|
var chars2 = input2.ToCharArray().Select(c => (byte)c).ToArray();
|
||||||
|
Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
|
||||||
|
block2.End += chars2.Length;
|
||||||
|
emptyBlock.Next = block2;
|
||||||
|
|
||||||
|
var scan1 = block1.GetIterator();
|
||||||
|
var scan2_1 = scan1;
|
||||||
|
var scan2_2 = scan1;
|
||||||
|
var scan3_1 = scan1;
|
||||||
|
var scan3_2 = scan1;
|
||||||
|
var scan3_3 = scan1;
|
||||||
|
var end = scan1;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var endReturnValue = end.Seek(ref limitAtVector);
|
||||||
|
var returnValue1 = scan1.Seek(ref seekVector, ref end);
|
||||||
|
var returnValue2_1 = scan2_1.Seek(ref seekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue2_2 = scan2_2.Seek(ref afterSeekVector, ref seekVector, ref end);
|
||||||
|
var returnValue3_1 = scan3_1.Seek(ref seekVector, ref afterSeekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue3_2 = scan3_2.Seek(ref afterSeekVector, ref seekVector, ref afterSeekVector, ref end);
|
||||||
|
var returnValue3_3 = scan3_3.Seek(ref afterSeekVector, ref afterSeekVector, ref seekVector, ref end);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(input.Contains(limitAt) ? limitAt : -1, endReturnValue);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue2_1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue2_2);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_1);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_2);
|
||||||
|
Assert.Equal(expectedReturnValue, returnValue3_3);
|
||||||
|
|
||||||
|
var seekCharIndex = input.IndexOf(seek);
|
||||||
|
var limitAtIndex = input.IndexOf(limitAt);
|
||||||
|
var expectedEndBlock = seekCharIndex != -1 && seekCharIndex < input.Length / 2 ?
|
||||||
|
block1 :
|
||||||
|
(limitAtIndex != -1 && limitAtIndex < input.Length / 2 ? block1 : block2);
|
||||||
|
Assert.Same(expectedEndBlock, scan1.Block);
|
||||||
|
Assert.Same(expectedEndBlock, scan2_1.Block);
|
||||||
|
Assert.Same(expectedEndBlock, scan2_2.Block);
|
||||||
|
Assert.Same(expectedEndBlock, scan3_1.Block);
|
||||||
|
Assert.Same(expectedEndBlock, scan3_2.Block);
|
||||||
|
Assert.Same(expectedEndBlock, scan3_3.Block);
|
||||||
|
|
||||||
|
var expectedEndIndex = expectedReturnValue != -1 ?
|
||||||
|
expectedEndBlock.Start + (expectedEndBlock == block1 ? input1.IndexOf(seek) : input2.IndexOf(seek)) :
|
||||||
|
end.Index;
|
||||||
|
Assert.Equal(expectedEndIndex, scan1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan2_1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan2_2.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_1.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_2.Index);
|
||||||
|
Assert.Equal(expectedEndIndex, scan3_3.Index);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
if (block1 != null) _pool.Return(block1);
|
||||||
|
if (emptyBlock != null) _pool.Return(emptyBlock);
|
||||||
|
if (block2 != null) _pool.Return(block2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -435,5 +689,127 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.Equal(knownString1, expected);
|
Assert.Equal(knownString1, expected);
|
||||||
Assert.Same(knownString1, knownString2);
|
Assert.Same(knownString1, knownString2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> SeekByteLimitData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var vectorSpan = Vector<byte>.Count;
|
||||||
|
|
||||||
|
// string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue
|
||||||
|
var data = new List<object[]>();
|
||||||
|
|
||||||
|
// Non-vector inputs
|
||||||
|
|
||||||
|
data.Add(new object[] { "hello, world", 'h', 12, 1, 'h' });
|
||||||
|
data.Add(new object[] { "hello, world", ' ', 12, 7, ' ' });
|
||||||
|
data.Add(new object[] { "hello, world", 'd', 12, 12, 'd' });
|
||||||
|
data.Add(new object[] { "hello, world", '!', 12, 12, -1 });
|
||||||
|
data.Add(new object[] { "hello, world", 'h', 13, 1, 'h' });
|
||||||
|
data.Add(new object[] { "hello, world", ' ', 13, 7, ' ' });
|
||||||
|
data.Add(new object[] { "hello, world", 'd', 13, 12, 'd' });
|
||||||
|
data.Add(new object[] { "hello, world", '!', 13, 12, -1 });
|
||||||
|
data.Add(new object[] { "hello, world", 'h', 5, 1, 'h' });
|
||||||
|
data.Add(new object[] { "hello, world", 'o', 5, 5, 'o' });
|
||||||
|
data.Add(new object[] { "hello, world", ',', 5, 5, -1 });
|
||||||
|
data.Add(new object[] { "hello, world", 'd', 5, 5, -1 });
|
||||||
|
data.Add(new object[] { "abba", 'a', 4, 1, 'a' });
|
||||||
|
data.Add(new object[] { "abba", 'b', 4, 2, 'b' });
|
||||||
|
|
||||||
|
// Vector inputs
|
||||||
|
|
||||||
|
// Single vector, no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan), 'b', vectorSpan, vectorSpan, -1 });
|
||||||
|
// Two vectors, no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan * 2), 'b', vectorSpan * 2, vectorSpan * 2, -1 });
|
||||||
|
// Two vectors plus non vector length (thus hitting slow path too), no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan * 2 + vectorSpan / 2), 'b', vectorSpan * 2 + vectorSpan / 2, vectorSpan * 2 + vectorSpan / 2, -1 });
|
||||||
|
|
||||||
|
// For each input length from 1/2 to 3 1/2 vector spans in 1/2 vector span increments...
|
||||||
|
for (var length = vectorSpan / 2; length <= vectorSpan * 3 + vectorSpan / 2; length += vectorSpan / 2)
|
||||||
|
{
|
||||||
|
// ...place the seek char at vector and input boundaries...
|
||||||
|
for (var i = Math.Min(vectorSpan - 1, length - 1); i < length; i += ((i + 1) % vectorSpan == 0) ? 1 : Math.Min(i + (vectorSpan - 1), length - 1))
|
||||||
|
{
|
||||||
|
var input = new StringBuilder(new string('a', length));
|
||||||
|
input[i] = 'b';
|
||||||
|
|
||||||
|
// ...and check with a seek byte limit before, at, and past the seek char position...
|
||||||
|
for (var limitOffset = -1; limitOffset <= 1; limitOffset++)
|
||||||
|
{
|
||||||
|
var limit = (i + 1) + limitOffset;
|
||||||
|
|
||||||
|
if (limit >= i + 1)
|
||||||
|
{
|
||||||
|
// ...that Seek() succeeds when the seek char is within that limit...
|
||||||
|
data.Add(new object[] { input.ToString(), 'b', limit, i + 1, 'b' });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ...and fails when it's not.
|
||||||
|
data.Add(new object[] { input.ToString(), 'b', limit, Math.Min(length, limit), -1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> SeekIteratorLimitData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var vectorSpan = Vector<byte>.Count;
|
||||||
|
|
||||||
|
// string input, char seek, char limitAt, int expectedReturnValue
|
||||||
|
var data = new List<object[]>();
|
||||||
|
|
||||||
|
// Non-vector inputs
|
||||||
|
|
||||||
|
data.Add(new object[] { "hello, world", 'h', 'd', 'h' });
|
||||||
|
data.Add(new object[] { "hello, world", ' ', 'd', ' ' });
|
||||||
|
data.Add(new object[] { "hello, world", 'd', 'd', 'd' });
|
||||||
|
data.Add(new object[] { "hello, world", '!', 'd', -1 });
|
||||||
|
data.Add(new object[] { "hello, world", 'h', 'w', 'h' });
|
||||||
|
data.Add(new object[] { "hello, world", 'o', 'w', 'o' });
|
||||||
|
data.Add(new object[] { "hello, world", 'r', 'w', -1 });
|
||||||
|
data.Add(new object[] { "hello, world", 'd', 'w', -1 });
|
||||||
|
|
||||||
|
// Vector inputs
|
||||||
|
|
||||||
|
// Single vector, no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan), 'b', 'b', -1 });
|
||||||
|
// Two vectors, no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan * 2), 'b', 'b', -1 });
|
||||||
|
// Two vectors plus non vector length (thus hitting slow path too), no seek char in input, expect failure
|
||||||
|
data.Add(new object[] { new string('a', vectorSpan * 2 + vectorSpan / 2), 'b', 'b', -1 });
|
||||||
|
|
||||||
|
// For each input length from 1/2 to 3 1/2 vector spans in 1/2 vector span increments...
|
||||||
|
for (var length = vectorSpan / 2; length <= vectorSpan * 3 + vectorSpan / 2; length += vectorSpan / 2)
|
||||||
|
{
|
||||||
|
// ...place the seek char at vector and input boundaries...
|
||||||
|
for (var i = Math.Min(vectorSpan - 1, length - 1); i < length; i += ((i + 1) % vectorSpan == 0) ? 1 : Math.Min(i + (vectorSpan - 1), length - 1))
|
||||||
|
{
|
||||||
|
var input = new StringBuilder(new string('a', length));
|
||||||
|
input[i] = 'b';
|
||||||
|
|
||||||
|
// ...along with sentinel characters to seek the limit iterator to...
|
||||||
|
input[i - 1] = 'A';
|
||||||
|
if (i < length - 1) input[i + 1] = 'B';
|
||||||
|
|
||||||
|
// ...and check that Seek() succeeds with a limit iterator at or past the seek char position...
|
||||||
|
data.Add(new object[] { input.ToString(), 'b', 'b', 'b' });
|
||||||
|
if (i < length - 1) data.Add(new object[] { input.ToString(), 'b', 'B', 'b' });
|
||||||
|
|
||||||
|
// ...and fails with a limit iterator before the seek char position.
|
||||||
|
data.Add(new object[] { input.ToString(), 'b', 'A', -1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
|
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
|
||||||
if (!Debugger.IsAttached)
|
if (!Debugger.IsAttached)
|
||||||
{
|
{
|
||||||
Assert.True(task.Wait(4000), "timeout");
|
Assert.True(await Task.WhenAny(task, Task.Delay(10000)) == task, "TestConnection.Receive timed out.");
|
||||||
}
|
}
|
||||||
var count = await task;
|
var count = await task;
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue