Merge branch 'dev' into knownmethods-optimizations
This commit is contained in:
commit
0a45cbbb95
|
|
@ -32,5 +32,5 @@ branches:
|
||||||
before_install:
|
before_install:
|
||||||
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi
|
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi
|
||||||
script:
|
script:
|
||||||
- ./build.sh --quiet verify
|
- ./build.sh
|
||||||
- if test "$TRAVIS_OS_NAME" != "osx"; then bash test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh; fi
|
- if test "$TRAVIS_OS_NAME" != "osx"; then bash test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh; fi
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
|
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
|
||||||
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
|
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
|
||||||
<add key="appveyor-bdn" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
|
|
||||||
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ branches:
|
||||||
- feature/dev-si
|
- feature/dev-si
|
||||||
- /^(.*\/)?ci-.*$/
|
- /^(.*\/)?ci-.*$/
|
||||||
build_script:
|
build_script:
|
||||||
- build.cmd --quiet verify
|
- ps: .\build.ps1
|
||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
test: off
|
test: off
|
||||||
deploy: off
|
deploy: off
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ cd $PSScriptRoot
|
||||||
$repoFolder = $PSScriptRoot
|
$repoFolder = $PSScriptRoot
|
||||||
$env:REPO_FOLDER = $repoFolder
|
$env:REPO_FOLDER = $repoFolder
|
||||||
|
|
||||||
$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip"
|
$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
|
||||||
if ($env:KOREBUILD_ZIP)
|
if ($env:KOREBUILD_ZIP)
|
||||||
{
|
{
|
||||||
$koreBuildZip=$env:KOREBUILD_ZIP
|
$koreBuildZip=$env:KOREBUILD_ZIP
|
||||||
|
|
@ -64,4 +64,4 @@ if (!(Test-Path $buildFolder)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&"$buildFile" $args
|
&"$buildFile" @args
|
||||||
|
|
|
||||||
2
build.sh
2
build.sh
|
|
@ -2,7 +2,7 @@
|
||||||
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
cd $repoFolder
|
cd $repoFolder
|
||||||
|
|
||||||
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip"
|
koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip"
|
||||||
if [ ! -z $KOREBUILD_ZIP ]; then
|
if [ ! -z $KOREBUILD_ZIP ]; then
|
||||||
koreBuildZip=$KOREBUILD_ZIP
|
koreBuildZip=$KOREBUILD_ZIP
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,7 @@
|
||||||
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
|
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
|
||||||
<CoreFxVersion>4.3.0</CoreFxVersion>
|
<CoreFxVersion>4.3.0</CoreFxVersion>
|
||||||
<LibUvVersion>1.9.1</LibUvVersion>
|
<LibUvVersion>1.9.1</LibUvVersion>
|
||||||
|
<CoreFxLabsVersion>0.1.0-*</CoreFxLabsVersion>
|
||||||
|
<CoreFxLabsPipelinesVersion>0.1.0-*</CoreFxLabsPipelinesVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
|
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
|
||||||
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
case RequestRejectionReason.HeaderLineMustNotStartWithWhitespace:
|
|
||||||
ex = new BadHttpRequestException("Header line must not start with whitespace.", StatusCodes.Status400BadRequest);
|
|
||||||
break;
|
|
||||||
case RequestRejectionReason.NoColonCharacterFoundInHeaderLine:
|
case RequestRejectionReason.NoColonCharacterFoundInHeaderLine:
|
||||||
ex = new BadHttpRequestException("No ':' character found in header line.", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException("No ':' character found in header line.", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
|
|
@ -40,6 +37,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
case RequestRejectionReason.HeaderValueLineFoldingNotSupported:
|
case RequestRejectionReason.HeaderValueLineFoldingNotSupported:
|
||||||
ex = new BadHttpRequestException("Header value line folding not supported.", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException("Header value line folding not supported.", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
|
case RequestRejectionReason.InvalidRequestLine:
|
||||||
|
ex = new BadHttpRequestException("Invalid request line.", StatusCodes.Status400BadRequest);
|
||||||
|
break;
|
||||||
case RequestRejectionReason.MalformedRequestInvalidHeaders:
|
case RequestRejectionReason.MalformedRequestInvalidHeaders:
|
||||||
ex = new BadHttpRequestException("Malformed request: invalid headers.", StatusCodes.Status400BadRequest);
|
ex = new BadHttpRequestException("Malformed request: invalid headers.", StatusCodes.Status400BadRequest);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private CancellationTokenSource _abortedCts;
|
private CancellationTokenSource _abortedCts;
|
||||||
private CancellationToken? _manuallySetRequestAbortToken;
|
private CancellationToken? _manuallySetRequestAbortToken;
|
||||||
|
|
||||||
private RequestProcessingStatus _requestProcessingStatus;
|
protected RequestProcessingStatus _requestProcessingStatus;
|
||||||
protected bool _keepAlive;
|
protected bool _keepAlive;
|
||||||
protected bool _upgrade;
|
protected bool _upgrade;
|
||||||
private bool _canHaveBody;
|
private bool _canHaveBody;
|
||||||
|
|
@ -982,15 +982,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
Output.ProducingComplete(end);
|
Output.ProducingComplete(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ParseRequest(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||||
|
{
|
||||||
|
consumed = buffer.Start;
|
||||||
|
examined = buffer.End;
|
||||||
|
|
||||||
|
switch (_requestProcessingStatus)
|
||||||
|
{
|
||||||
|
case RequestProcessingStatus.RequestPending:
|
||||||
|
if (buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
||||||
|
|
||||||
|
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
|
||||||
|
goto case RequestProcessingStatus.ParsingRequestLine;
|
||||||
|
case RequestProcessingStatus.ParsingRequestLine:
|
||||||
|
if (TakeStartLine(buffer, out consumed, out examined))
|
||||||
|
{
|
||||||
|
buffer = buffer.Slice(consumed, buffer.End);
|
||||||
|
|
||||||
|
_requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
|
||||||
|
goto case RequestProcessingStatus.ParsingHeaders;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RequestProcessingStatus.ParsingHeaders:
|
||||||
|
if (TakeMessageHeaders(buffer, out consumed, out examined))
|
||||||
|
{
|
||||||
|
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||||
{
|
{
|
||||||
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
|
|
||||||
{
|
|
||||||
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
|
||||||
|
|
||||||
var overLength = false;
|
var overLength = false;
|
||||||
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
|
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
|
||||||
{
|
{
|
||||||
|
|
@ -1039,7 +1070,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TakeMessageHeaders(ReadableBuffer buffer, FrameRequestHeaders requestHeaders, out ReadCursor consumed, out ReadCursor examined)
|
public bool TakeMessageHeaders(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||||
{
|
{
|
||||||
// Make sure the buffer is limited
|
// Make sure the buffer is limited
|
||||||
bool overLength = false;
|
bool overLength = false;
|
||||||
|
|
@ -1085,7 +1116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
if (!appCompleted)
|
if (!appCompleted)
|
||||||
{
|
{
|
||||||
// Back out of header creation surface exeception in user code
|
// Back out of header creation surface exeception in user code
|
||||||
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1141,18 +1172,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason)
|
public void RejectRequest(RequestRejectionReason reason)
|
||||||
{
|
{
|
||||||
RejectRequest(BadHttpRequestException.GetException(reason));
|
throw BadHttpRequestException.GetException(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason, string value)
|
public void RejectRequest(RequestRejectionReason reason, string value)
|
||||||
{
|
{
|
||||||
RejectRequest(BadHttpRequestException.GetException(reason, value));
|
throw BadHttpRequestException.GetException(reason, value);
|
||||||
}
|
|
||||||
|
|
||||||
private void RejectRequest(BadHttpRequestException ex)
|
|
||||||
{
|
|
||||||
Log.ConnectionBadRequest(ConnectionId, ex);
|
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBadRequestState(RequestRejectionReason reason)
|
public void SetBadRequestState(RequestRejectionReason reason)
|
||||||
|
|
@ -1162,6 +1187,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
public void SetBadRequestState(BadHttpRequestException ex)
|
public void SetBadRequestState(BadHttpRequestException ex)
|
||||||
{
|
{
|
||||||
|
Log.ConnectionBadRequest(ConnectionId, ex);
|
||||||
|
|
||||||
if (!HasResponseStarted)
|
if (!HasResponseStarted)
|
||||||
{
|
{
|
||||||
SetErrorResponseHeaders(ex.StatusCode);
|
SetErrorResponseHeaders(ex.StatusCode);
|
||||||
|
|
@ -1190,20 +1217,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
Log.ApplicationError(ConnectionId, ex);
|
Log.ApplicationError(ConnectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RequestLineStatus
|
|
||||||
{
|
|
||||||
Empty,
|
|
||||||
Incomplete,
|
|
||||||
Done
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum RequestProcessingStatus
|
|
||||||
{
|
|
||||||
RequestPending,
|
|
||||||
RequestStarted,
|
|
||||||
ResponseStarted
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod)
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod)
|
||||||
{
|
{
|
||||||
// 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
|
||||||
|
|
@ -1215,7 +1228,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
if (needDecode)
|
if (needDecode)
|
||||||
{
|
{
|
||||||
// Read raw target before mutating memory.
|
// Read raw target before mutating memory.
|
||||||
rawTarget = target.GetAsciiString() ?? string.Empty;
|
rawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
// URI was encoded, unescape and then parse as utf8
|
// URI was encoded, unescape and then parse as utf8
|
||||||
int pathLength = UrlEncoder.Decode(path, path);
|
int pathLength = UrlEncoder.Decode(path, path);
|
||||||
|
|
@ -1224,7 +1237,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// URI wasn't encoded, parse as ASCII
|
// URI wasn't encoded, parse as ASCII
|
||||||
requestUrlPath = path.GetAsciiString() ?? string.Empty;
|
requestUrlPath = path.GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
if (query.Length == 0)
|
if (query.Length == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1234,21 +1247,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rawTarget = target.GetAsciiString() ?? string.Empty;
|
rawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath);
|
var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath);
|
||||||
if (method != HttpMethod.Custom)
|
if (method != HttpMethod.Custom)
|
||||||
{
|
{
|
||||||
Method = HttpUtilities.MethodToString(method) ?? String.Empty;
|
Method = HttpUtilities.MethodToString(method) ?? string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Method = customMethod.GetAsciiString() ?? string.Empty;
|
Method = customMethod.GetAsciiStringNonNullCharacters();
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryString = query.GetAsciiString() ?? string.Empty;
|
QueryString = query.GetAsciiStringNonNullCharacters();
|
||||||
RawTarget = rawTarget;
|
RawTarget = rawTarget;
|
||||||
HttpVersion = HttpUtilities.VersionToString(version);
|
HttpVersion = HttpUtilities.VersionToString(version);
|
||||||
|
|
||||||
|
|
@ -1276,7 +1289,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.TooManyHeaders);
|
RejectRequest(RequestRejectionReason.TooManyHeaders);
|
||||||
}
|
}
|
||||||
var valueString = value.GetAsciiString() ?? string.Empty;
|
var valueString = value.GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
FrameRequestHeaders.Append(name, valueString);
|
FrameRequestHeaders.Append(name, valueString);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
|
@ -31,16 +30,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override async Task RequestProcessingAsync()
|
public override async Task RequestProcessingAsync()
|
||||||
{
|
{
|
||||||
var requestLineStatus = default(RequestLineStatus);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_requestProcessingStopping)
|
while (!_requestProcessingStopping)
|
||||||
{
|
{
|
||||||
// If writer completes with an error Input.ReadAsyncDispatched would throw and
|
|
||||||
// this would not be reset to empty. But it's required by ECONNRESET check lower in the method.
|
|
||||||
requestLineStatus = RequestLineStatus.Empty;
|
|
||||||
|
|
||||||
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
|
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
|
||||||
|
|
||||||
while (!_requestProcessingStopping)
|
while (!_requestProcessingStopping)
|
||||||
|
|
@ -49,76 +42,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var examined = result.Buffer.End;
|
var examined = result.Buffer.End;
|
||||||
var consumed = result.Buffer.End;
|
var consumed = result.Buffer.End;
|
||||||
|
|
||||||
|
InitializeHeaders();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!result.Buffer.IsEmpty)
|
ParseRequest(result.Buffer, out consumed, out examined);
|
||||||
{
|
|
||||||
requestLineStatus = TakeStartLine(result.Buffer, out consumed, out examined)
|
|
||||||
? RequestLineStatus.Done : RequestLineStatus.Incomplete;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
requestLineStatus = RequestLineStatus.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
switch (_requestProcessingStatus)
|
||||||
|
{
|
||||||
|
case RequestProcessingStatus.ParsingRequestLine:
|
||||||
|
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
||||||
|
case RequestProcessingStatus.ParsingHeaders:
|
||||||
|
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Input.Reader.Advance(consumed, examined);
|
Input.Reader.Advance(consumed, examined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestLineStatus == RequestLineStatus.Done)
|
if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.IsCompleted)
|
if (result.IsCompleted)
|
||||||
{
|
{
|
||||||
if (requestLineStatus == RequestLineStatus.Empty)
|
switch (_requestProcessingStatus)
|
||||||
{
|
{
|
||||||
return;
|
case RequestProcessingStatus.RequestPending:
|
||||||
|
return;
|
||||||
|
case RequestProcessingStatus.ParsingRequestLine:
|
||||||
|
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
|
||||||
|
case RequestProcessingStatus.ParsingHeaders:
|
||||||
|
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeHeaders();
|
|
||||||
|
|
||||||
while (!_requestProcessingStopping)
|
|
||||||
{
|
|
||||||
|
|
||||||
var result = await Input.Reader.ReadAsync();
|
|
||||||
var examined = result.Buffer.End;
|
|
||||||
var consumed = result.Buffer.End;
|
|
||||||
|
|
||||||
bool headersDone;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
headersDone = TakeMessageHeaders(result.Buffer, FrameRequestHeaders, out consumed,
|
|
||||||
out examined);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Input.Reader.Advance(consumed, examined);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headersDone)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
catch (IOException ex) when (ex.InnerException is UvException)
|
catch (IOException ex) when (ex.InnerException is UvException)
|
||||||
{
|
{
|
||||||
// Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly.
|
// Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly.
|
||||||
if (requestLineStatus != RequestLineStatus.Empty ||
|
if (_requestProcessingStatus != RequestProcessingStatus.RequestPending ||
|
||||||
((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET)
|
((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET)
|
||||||
{
|
{
|
||||||
Log.RequestProcessingError(ConnectionId, ex);
|
Log.RequestProcessingError(ConnectionId, ex);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
// 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.Buffers;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -31,38 +34,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
consumed = buffer.Start;
|
consumed = buffer.Start;
|
||||||
examined = buffer.End;
|
examined = buffer.End;
|
||||||
|
|
||||||
var start = buffer.Start;
|
|
||||||
ReadCursor end;
|
ReadCursor end;
|
||||||
if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) == -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int stackAllocLimit = 512;
|
|
||||||
|
|
||||||
// Move 1 byte past the \n
|
|
||||||
end = buffer.Move(end, 1);
|
|
||||||
var startLineBuffer = buffer.Slice(start, end);
|
|
||||||
|
|
||||||
Span<byte> span;
|
Span<byte> span;
|
||||||
|
|
||||||
if (startLineBuffer.IsSingleSpan)
|
// If the buffer is a single span then use it to find the LF
|
||||||
|
if (buffer.IsSingleSpan)
|
||||||
{
|
{
|
||||||
// No copies, directly use the one and only span
|
var startLineSpan = buffer.First.Span;
|
||||||
span = startLineBuffer.First.Span;
|
var lineIndex = startLineSpan.IndexOfVectorized(ByteLF);
|
||||||
}
|
|
||||||
else if (startLineBuffer.Length < stackAllocLimit)
|
if (lineIndex == -1)
|
||||||
{
|
{
|
||||||
// Multiple buffers and < stackAllocLimit, copy into a stack buffer
|
return false;
|
||||||
byte* stackBuffer = stackalloc byte[startLineBuffer.Length];
|
}
|
||||||
span = new Span<byte>(stackBuffer, startLineBuffer.Length);
|
|
||||||
startLineBuffer.CopyTo(span);
|
end = buffer.Move(consumed, lineIndex + 1);
|
||||||
|
span = startLineSpan.Slice(0, lineIndex + 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
|
var start = buffer.Start;
|
||||||
span = new Span<byte>(new byte[startLineBuffer.Length]);
|
if (ReadCursorOperations.Seek(start, buffer.End, out end, ByteLF) == -1)
|
||||||
startLineBuffer.CopyTo(span);
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move 1 byte past the \n
|
||||||
|
end = buffer.Move(end, 1);
|
||||||
|
var startLineBuffer = buffer.Slice(start, end);
|
||||||
|
|
||||||
|
span = startLineBuffer.ToSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathStart = -1;
|
var pathStart = -1;
|
||||||
|
|
@ -71,37 +72,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var pathEnd = -1;
|
var pathEnd = -1;
|
||||||
var versionStart = -1;
|
var versionStart = -1;
|
||||||
|
|
||||||
HttpVersion httpVersion = HttpVersion.Unknown;
|
var httpVersion = HttpVersion.Unknown;
|
||||||
HttpMethod method = HttpMethod.Custom;
|
HttpMethod method;
|
||||||
Span<byte> customMethod;
|
Span<byte> customMethod;
|
||||||
var state = StartLineState.KnownMethod;
|
var i = 0;
|
||||||
|
var length = span.Length;
|
||||||
|
var done = false;
|
||||||
|
|
||||||
int i;
|
|
||||||
fixed (byte* data = &span.DangerousGetPinnableReference())
|
fixed (byte* data = &span.DangerousGetPinnableReference())
|
||||||
{
|
{
|
||||||
var length = span.Length;
|
switch (StartLineState.KnownMethod)
|
||||||
for (i = 0; i < length; i++)
|
|
||||||
{
|
{
|
||||||
var ch = data[i];
|
case StartLineState.KnownMethod:
|
||||||
|
if (span.GetKnownMethod(out method, out var methodLength))
|
||||||
|
{
|
||||||
|
// Update the index, current char, state and jump directly
|
||||||
|
// to the next state
|
||||||
|
i += methodLength + 1;
|
||||||
|
|
||||||
switch (state)
|
goto case StartLineState.Path;
|
||||||
{
|
}
|
||||||
case StartLineState.KnownMethod:
|
goto case StartLineState.UnknownMethod;
|
||||||
if (span.GetKnownMethod(out method, out var methodLength))
|
|
||||||
{
|
|
||||||
// Update the index, current char, state and jump directly
|
|
||||||
// to the next state
|
|
||||||
i += methodLength + 1;
|
|
||||||
ch = data[i];
|
|
||||||
state = StartLineState.Path;
|
|
||||||
|
|
||||||
goto case StartLineState.Path;
|
case StartLineState.UnknownMethod:
|
||||||
}
|
for (; i < length; i++)
|
||||||
|
{
|
||||||
|
var ch = data[i];
|
||||||
|
|
||||||
state = StartLineState.UnknownMethod;
|
|
||||||
goto case StartLineState.UnknownMethod;
|
|
||||||
|
|
||||||
case StartLineState.UnknownMethod:
|
|
||||||
if (ch == ByteSpace)
|
if (ch == ByteSpace)
|
||||||
{
|
{
|
||||||
customMethod = span.Slice(0, i);
|
customMethod = span.Slice(0, i);
|
||||||
|
|
@ -110,16 +107,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
RejectRequestLine(span);
|
RejectRequestLine(span);
|
||||||
}
|
}
|
||||||
|
// Consume space
|
||||||
|
i++;
|
||||||
|
|
||||||
state = StartLineState.Path;
|
goto case StartLineState.Path;
|
||||||
}
|
}
|
||||||
else if (!IsValidTokenChar((char)ch))
|
|
||||||
|
if (!IsValidTokenChar((char)ch))
|
||||||
{
|
{
|
||||||
RejectRequestLine(span);
|
RejectRequestLine(span);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case StartLineState.Path:
|
case StartLineState.Path:
|
||||||
|
for (; i < length; i++)
|
||||||
|
{
|
||||||
|
var ch = data[i];
|
||||||
if (ch == ByteSpace)
|
if (ch == ByteSpace)
|
||||||
{
|
{
|
||||||
pathEnd = i;
|
pathEnd = i;
|
||||||
|
|
@ -133,7 +137,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
// No query string found
|
// No query string found
|
||||||
queryStart = queryEnd = i;
|
queryStart = queryEnd = i;
|
||||||
|
|
||||||
state = StartLineState.KnownVersion;
|
// Consume space
|
||||||
|
i++;
|
||||||
|
|
||||||
|
goto case StartLineState.KnownVersion;
|
||||||
}
|
}
|
||||||
else if (ch == ByteQuestionMark)
|
else if (ch == ByteQuestionMark)
|
||||||
{
|
{
|
||||||
|
|
@ -146,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
queryStart = i;
|
queryStart = i;
|
||||||
state = StartLineState.QueryString;
|
goto case StartLineState.QueryString;
|
||||||
}
|
}
|
||||||
else if (ch == BytePercentage)
|
else if (ch == BytePercentage)
|
||||||
{
|
{
|
||||||
|
|
@ -160,35 +167,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
pathStart = i;
|
pathStart = i;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
break;
|
||||||
case StartLineState.QueryString:
|
case StartLineState.QueryString:
|
||||||
|
for (; i < length; i++)
|
||||||
|
{
|
||||||
|
var ch = data[i];
|
||||||
if (ch == ByteSpace)
|
if (ch == ByteSpace)
|
||||||
{
|
{
|
||||||
queryEnd = i;
|
queryEnd = i;
|
||||||
state = StartLineState.KnownVersion;
|
|
||||||
|
|
||||||
|
// Consume space
|
||||||
|
i++;
|
||||||
|
|
||||||
|
goto case StartLineState.KnownVersion;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case StartLineState.KnownVersion:
|
break;
|
||||||
// REVIEW: We don't *need* to slice here but it makes the API
|
case StartLineState.KnownVersion:
|
||||||
// nicer, slicing should be free :)
|
// REVIEW: We don't *need* to slice here but it makes the API
|
||||||
if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght))
|
// nicer, slicing should be free :)
|
||||||
{
|
if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght))
|
||||||
// Update the index, current char, state and jump directly
|
{
|
||||||
// to the next state
|
// Update the index, current char, state and jump directly
|
||||||
i += versionLenght + 1;
|
// to the next state
|
||||||
ch = data[i];
|
i += versionLenght + 1;
|
||||||
state = StartLineState.NewLine;
|
goto case StartLineState.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
goto case StartLineState.NewLine;
|
versionStart = i;
|
||||||
}
|
|
||||||
|
|
||||||
versionStart = i;
|
goto case StartLineState.UnknownVersion;
|
||||||
state = StartLineState.UnknownVersion;
|
|
||||||
goto case StartLineState.UnknownVersion;
|
|
||||||
|
|
||||||
case StartLineState.UnknownVersion:
|
case StartLineState.UnknownVersion:
|
||||||
|
for (; i < length; i++)
|
||||||
|
{
|
||||||
|
var ch = data[i];
|
||||||
if (ch == ByteCR)
|
if (ch == ByteCR)
|
||||||
{
|
{
|
||||||
var versionSpan = span.Slice(versionStart, i - versionStart);
|
var versionSpan = span.Slice(versionStart, i - versionStart);
|
||||||
|
|
@ -199,25 +213,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, versionSpan.GetAsciiStringEscaped(32));
|
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion,
|
||||||
|
versionSpan.GetAsciiStringEscaped(32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case StartLineState.NewLine:
|
break;
|
||||||
if (ch != ByteLF)
|
case StartLineState.NewLine:
|
||||||
{
|
if (data[i] != ByteLF)
|
||||||
RejectRequestLine(span);
|
{
|
||||||
}
|
RejectRequestLine(span);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
|
||||||
state = StartLineState.Complete;
|
goto case StartLineState.Complete;
|
||||||
break;
|
case StartLineState.Complete:
|
||||||
case StartLineState.Complete:
|
done = true;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != StartLineState.Complete)
|
if (!done)
|
||||||
{
|
{
|
||||||
RejectRequestLine(span);
|
RejectRequestLine(span);
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +243,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var query = span.Slice(queryStart, queryEnd - queryStart);
|
var query = span.Slice(queryStart, queryEnd - queryStart);
|
||||||
|
|
||||||
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod);
|
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod);
|
||||||
consumed = buffer.Move(start, i);
|
|
||||||
|
consumed = end;
|
||||||
examined = consumed;
|
examined = consumed;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -241,241 +258,256 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var bufferEnd = buffer.End;
|
var bufferEnd = buffer.End;
|
||||||
|
|
||||||
var reader = new ReadableBufferReader(buffer);
|
var reader = new ReadableBufferReader(buffer);
|
||||||
while (true)
|
var start = default(ReadableBufferReader);
|
||||||
|
var done = false;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var start = reader;
|
while (!reader.End)
|
||||||
int ch1 = reader.Take();
|
|
||||||
var ch2 = reader.Take();
|
|
||||||
|
|
||||||
if (ch1 == -1)
|
|
||||||
{
|
{
|
||||||
return false;
|
var span = reader.Span;
|
||||||
}
|
var remaining = span.Length;
|
||||||
|
|
||||||
if (ch1 == ByteCR)
|
fixed (byte* pBuffer = &span.DangerousGetPinnableReference())
|
||||||
{
|
|
||||||
// Check for final CRLF.
|
|
||||||
if (ch2 == -1)
|
|
||||||
{
|
{
|
||||||
return false;
|
while (remaining > 0)
|
||||||
}
|
|
||||||
else if (ch2 == ByteLF)
|
|
||||||
{
|
|
||||||
consumed = reader.Cursor;
|
|
||||||
examined = consumed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers don't end in CRLF line.
|
|
||||||
RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
|
|
||||||
}
|
|
||||||
else if (ch1 == ByteSpace || ch1 == ByteTab)
|
|
||||||
{
|
|
||||||
RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the reader since we're not at the end of headers
|
|
||||||
reader = start;
|
|
||||||
|
|
||||||
if (ReadCursorOperations.Seek(consumed, bufferEnd, out var lineEnd, ByteLF) == -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int stackAllocLimit = 512;
|
|
||||||
|
|
||||||
if (lineEnd != bufferEnd)
|
|
||||||
{
|
|
||||||
lineEnd = buffer.Move(lineEnd, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var headerBuffer = buffer.Slice(consumed, lineEnd);
|
|
||||||
|
|
||||||
Span<byte> span;
|
|
||||||
if (headerBuffer.IsSingleSpan)
|
|
||||||
{
|
|
||||||
// No copies, directly use the one and only span
|
|
||||||
span = headerBuffer.First.Span;
|
|
||||||
}
|
|
||||||
else if (headerBuffer.Length < stackAllocLimit)
|
|
||||||
{
|
|
||||||
// Multiple buffers and < stackAllocLimit, copy into a stack buffer
|
|
||||||
byte* stackBuffer = stackalloc byte[headerBuffer.Length];
|
|
||||||
span = new Span<byte>(stackBuffer, headerBuffer.Length);
|
|
||||||
headerBuffer.CopyTo(span);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
|
|
||||||
span = new Span<byte>(new byte[headerBuffer.Length]);
|
|
||||||
headerBuffer.CopyTo(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
var state = HeaderState.Name;
|
|
||||||
var nameStart = 0;
|
|
||||||
var nameEnd = -1;
|
|
||||||
var valueStart = -1;
|
|
||||||
var valueEnd = -1;
|
|
||||||
var nameHasWhitespace = false;
|
|
||||||
var previouslyWhitespace = false;
|
|
||||||
var headerLineLength = span.Length;
|
|
||||||
|
|
||||||
fixed (byte* data = &span.DangerousGetPinnableReference())
|
|
||||||
{
|
|
||||||
for (var i = 0; i < headerLineLength; i++)
|
|
||||||
{
|
|
||||||
var ch = data[i];
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
{
|
||||||
case HeaderState.Name:
|
var index = reader.Index;
|
||||||
if (ch == ByteColon)
|
int ch1;
|
||||||
{
|
int ch2;
|
||||||
if (nameHasWhitespace)
|
|
||||||
{
|
|
||||||
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = HeaderState.Whitespace;
|
// Fast path, we're still looking at the same span
|
||||||
nameEnd = i;
|
if (remaining >= 2)
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == ByteSpace || ch == ByteTab)
|
|
||||||
{
|
|
||||||
nameHasWhitespace = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HeaderState.Whitespace:
|
|
||||||
{
|
{
|
||||||
var whitespace = ch == ByteTab || ch == ByteSpace || ch == ByteCR;
|
ch1 = pBuffer[index];
|
||||||
|
ch2 = pBuffer[index + 1];
|
||||||
if (!whitespace)
|
|
||||||
{
|
|
||||||
// Mark the first non whitespace char as the start of the
|
|
||||||
// header value and change the state to expect to the header value
|
|
||||||
valueStart = i;
|
|
||||||
state = HeaderState.ExpectValue;
|
|
||||||
}
|
|
||||||
// If we see a CR then jump to the next state directly
|
|
||||||
else if (ch == ByteCR)
|
|
||||||
{
|
|
||||||
state = HeaderState.ExpectValue;
|
|
||||||
goto case HeaderState.ExpectValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
case HeaderState.ExpectValue:
|
|
||||||
{
|
{
|
||||||
var whitespace = ch == ByteTab || ch == ByteSpace;
|
// Store the reader before we look ahead 2 bytes (probably straddling
|
||||||
|
// spans)
|
||||||
|
start = reader;
|
||||||
|
|
||||||
if (whitespace)
|
// Possibly split across spans
|
||||||
{
|
ch1 = reader.Take();
|
||||||
if (!previouslyWhitespace)
|
ch2 = reader.Take();
|
||||||
{
|
|
||||||
// If we see a whitespace char then maybe it's end of the
|
|
||||||
// header value
|
|
||||||
valueEnd = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ch == ByteCR)
|
|
||||||
{
|
|
||||||
// If we see a CR and we haven't ever seen whitespace then
|
|
||||||
// this is the end of the header value
|
|
||||||
if (valueEnd == -1)
|
|
||||||
{
|
|
||||||
valueEnd = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We never saw a non whitespace character before the CR
|
|
||||||
if (valueStart == -1)
|
|
||||||
{
|
|
||||||
valueStart = valueEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = HeaderState.ExpectNewLine;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If we find a non whitespace char that isn't CR then reset the end index
|
|
||||||
valueEnd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
previouslyWhitespace = whitespace;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case HeaderState.ExpectNewLine:
|
if (ch1 == ByteCR)
|
||||||
if (ch != ByteLF)
|
{
|
||||||
|
// Check for final CRLF.
|
||||||
|
if (ch2 == -1)
|
||||||
{
|
{
|
||||||
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
|
// Reset the reader so we don't consume anything
|
||||||
|
reader = start;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (ch2 == ByteLF)
|
||||||
|
{
|
||||||
|
// If we got 2 bytes from the span directly so skip ahead 2 so that
|
||||||
|
// the reader's state matches what we expect
|
||||||
|
if (index == reader.Index)
|
||||||
|
{
|
||||||
|
reader.Skip(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
done = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = HeaderState.Complete;
|
// Headers don't end in CRLF line.
|
||||||
break;
|
RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
|
||||||
default:
|
}
|
||||||
break;
|
else if(ch1 == ByteSpace || ch1 == ByteTab)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We moved the reader so look ahead 2 bytes so reset both the reader
|
||||||
|
// and the index
|
||||||
|
if (index != reader.Index)
|
||||||
|
{
|
||||||
|
reader = start;
|
||||||
|
index = reader.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex = new Span<byte>(pBuffer + index, remaining).IndexOfVectorized(ByteLF);
|
||||||
|
var length = 0;
|
||||||
|
|
||||||
|
if (endIndex != -1)
|
||||||
|
{
|
||||||
|
length = endIndex + 1;
|
||||||
|
var pHeader = pBuffer + index;
|
||||||
|
|
||||||
|
TakeSingleHeader(pHeader, length, handler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var current = reader.Cursor;
|
||||||
|
|
||||||
|
// Split buffers
|
||||||
|
if (ReadCursorOperations.Seek(current, bufferEnd, out var lineEnd, ByteLF) == -1)
|
||||||
|
{
|
||||||
|
// Not there
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure LF is included in lineEnd
|
||||||
|
lineEnd = buffer.Move(lineEnd, 1);
|
||||||
|
var headerSpan = buffer.Slice(current, lineEnd).ToSpan();
|
||||||
|
length = headerSpan.Length;
|
||||||
|
|
||||||
|
fixed (byte* pHeader = &headerSpan.DangerousGetPinnableReference())
|
||||||
|
{
|
||||||
|
TakeSingleHeader(pHeader, length, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to the next span after this since we know we crossed spans here
|
||||||
|
// so mark the remaining as equal to the headerSpan so that we end up at 0
|
||||||
|
// on the next iteration
|
||||||
|
remaining = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the reader forward past the header line
|
||||||
|
reader.Skip(length);
|
||||||
|
remaining -= length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == HeaderState.Name)
|
return false;
|
||||||
{
|
|
||||||
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == HeaderState.ExpectValue || state == HeaderState.Whitespace)
|
|
||||||
{
|
|
||||||
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state != HeaderState.Complete)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the reader forward past the header line
|
|
||||||
reader.Skip(headerLineLength);
|
|
||||||
|
|
||||||
// Before accepting the header line, we need to see at least one character
|
|
||||||
// > so we can make sure there's no space or tab
|
|
||||||
var next = reader.Peek();
|
|
||||||
|
|
||||||
// TODO: We don't need to reject the line here, we can use the state machine
|
|
||||||
// to store the fact that we're reading a header value
|
|
||||||
if (next == -1)
|
|
||||||
{
|
|
||||||
// If we can't see the next char then reject the entire line
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next == ByteSpace || next == ByteTab)
|
|
||||||
{
|
|
||||||
// From https://tools.ietf.org/html/rfc7230#section-3.2.4:
|
|
||||||
//
|
|
||||||
// Historically, HTTP header field values could be extended over
|
|
||||||
// multiple lines by preceding each extra line with at least one space
|
|
||||||
// or horizontal tab (obs-fold). This specification deprecates such
|
|
||||||
// line folding except within the message/http media type
|
|
||||||
// (Section 8.3.1). A sender MUST NOT generate a message that includes
|
|
||||||
// line folding (i.e., that has any field-value that contains a match to
|
|
||||||
// the obs-fold rule) unless the message is intended for packaging
|
|
||||||
// within the message/http media type.
|
|
||||||
//
|
|
||||||
// A server that receives an obs-fold in a request message that is not
|
|
||||||
// within a message/http container MUST either reject the message by
|
|
||||||
// sending a 400 (Bad Request), preferably with a representation
|
|
||||||
// explaining that obsolete line folding is unacceptable, or replace
|
|
||||||
// each received obs-fold with one or more SP octets prior to
|
|
||||||
// interpreting the field value or forwarding the message downstream.
|
|
||||||
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameBuffer = span.Slice(nameStart, nameEnd - nameStart);
|
|
||||||
var valueBuffer = span.Slice(valueStart, valueEnd - valueStart);
|
|
||||||
consumedBytes += headerLineLength;
|
|
||||||
|
|
||||||
handler.OnHeader(nameBuffer, valueBuffer);
|
|
||||||
consumed = reader.Cursor;
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
consumed = reader.Cursor;
|
||||||
|
consumedBytes = reader.ConsumedBytes;
|
||||||
|
|
||||||
|
if (done)
|
||||||
|
{
|
||||||
|
examined = consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe int FindEndOfName(byte* headerLine, int length)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var sawWhitespace = false;
|
||||||
|
for (; index < length; index++)
|
||||||
|
{
|
||||||
|
var ch = headerLine[index];
|
||||||
|
if (ch == ByteColon)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ch == ByteTab || ch == ByteSpace || ch == ByteCR)
|
||||||
|
{
|
||||||
|
sawWhitespace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == length)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
|
||||||
|
}
|
||||||
|
if (sawWhitespace)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private unsafe void TakeSingleHeader<T>(byte* headerLine, int length, T handler) where T : IHttpHeadersHandler
|
||||||
|
{
|
||||||
|
// Skip CR, LF from end position
|
||||||
|
var valueEnd = length - 3;
|
||||||
|
var nameEnd = FindEndOfName(headerLine, length);
|
||||||
|
|
||||||
|
if (headerLine[valueEnd + 2] != ByteLF)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
|
||||||
|
}
|
||||||
|
if (headerLine[valueEnd + 1] != ByteCR)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip colon from value start
|
||||||
|
var valueStart = nameEnd + 1;
|
||||||
|
// Ignore start whitespace
|
||||||
|
for(; valueStart < valueEnd; valueStart++)
|
||||||
|
{
|
||||||
|
var ch = headerLine[valueStart];
|
||||||
|
if (ch != ByteTab && ch != ByteSpace && ch != ByteCR)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (ch == ByteCR)
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check for CR in value
|
||||||
|
var i = valueStart + 1;
|
||||||
|
if (Contains(headerLine + i, valueEnd - i, ByteCR))
|
||||||
|
{
|
||||||
|
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore end whitespace
|
||||||
|
for (; valueEnd >= valueStart; valueEnd--)
|
||||||
|
{
|
||||||
|
var ch = headerLine[valueEnd];
|
||||||
|
if (ch != ByteTab && ch != ByteSpace)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameBuffer = new Span<byte>(headerLine, nameEnd);
|
||||||
|
var valueBuffer = new Span<byte>(headerLine + valueStart, valueEnd - valueStart + 1);
|
||||||
|
|
||||||
|
handler.OnHeader(nameBuffer, valueBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe bool Contains(byte* searchSpace, int length, byte value)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
if (Vector.IsHardwareAccelerated)
|
||||||
|
{
|
||||||
|
// Check Vector lengths
|
||||||
|
if (length - Vector<byte>.Count >= i)
|
||||||
|
{
|
||||||
|
var vValue = GetVector(value);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!Vector<byte>.Zero.Equals(Vector.Equals(vValue, Unsafe.Read<Vector<byte>>(searchSpace + i))))
|
||||||
|
{
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += Vector<byte>.Count;
|
||||||
|
} while (length - Vector<byte>.Count >= i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check remaining for CR
|
||||||
|
for (; i <= length; i++)
|
||||||
|
{
|
||||||
|
var ch = searchSpace[i];
|
||||||
|
if (ch == value)
|
||||||
|
{
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
found:
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidTokenChar(char c)
|
private static bool IsValidTokenChar(char c)
|
||||||
|
|
@ -503,25 +535,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
c == '~';
|
c == '~';
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason)
|
public static void RejectRequest(RequestRejectionReason reason)
|
||||||
{
|
{
|
||||||
RejectRequest(BadHttpRequestException.GetException(reason));
|
throw BadHttpRequestException.GetException(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RejectRequest(RequestRejectionReason reason, string value)
|
public static void RejectRequest(RequestRejectionReason reason, string value)
|
||||||
{
|
{
|
||||||
RejectRequest(BadHttpRequestException.GetException(reason, value));
|
throw BadHttpRequestException.GetException(reason, value);
|
||||||
}
|
|
||||||
|
|
||||||
private void RejectRequest(BadHttpRequestException ex)
|
|
||||||
{
|
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RejectRequestLine(Span<byte> span)
|
private void RejectRequestLine(Span<byte> span)
|
||||||
|
{
|
||||||
|
throw GetRejectRequestLineException(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BadHttpRequestException GetRejectRequestLineException(Span<byte> span)
|
||||||
{
|
{
|
||||||
const int MaxRequestLineError = 32;
|
const int MaxRequestLineError = 32;
|
||||||
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
return BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine,
|
||||||
Log.IsEnabled(LogLevel.Information) ? span.GetAsciiStringEscaped(MaxRequestLineError) : string.Empty);
|
Log.IsEnabled(LogLevel.Information) ? span.GetAsciiStringEscaped(MaxRequestLineError) : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -530,6 +562,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Vector<byte> GetVector(byte vectorByte)
|
||||||
|
{
|
||||||
|
// Vector<byte> .ctor doesn't become an intrinsic due to detection issue
|
||||||
|
// However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
|
||||||
|
// https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
|
||||||
|
return Vector.AsVectorByte(new Vector<uint>(vectorByte * 0x01010101u));
|
||||||
|
}
|
||||||
|
|
||||||
private enum HeaderState
|
private enum HeaderState
|
||||||
{
|
{
|
||||||
Name,
|
Name,
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_context.TakeMessageHeaders(buffer, _requestHeaders, out consumed, out examined))
|
if (_context.TakeMessageHeaders(buffer, out consumed, out examined))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
|
|
@ -47,8 +46,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
var result = await readingTask;
|
var result = await readingTask;
|
||||||
|
|
||||||
await AwaitableThreadPool.Yield();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!result.Buffer.IsEmpty)
|
if (!result.Buffer.IsEmpty)
|
||||||
|
|
@ -70,13 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ReadResult> ReadAsyncDispatchedAwaited(ReadableBufferAwaitable awaitable)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
{
|
|
||||||
var result = await awaitable;
|
|
||||||
await AwaitableThreadPool.Yield();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Span<byte> ToSpan(this ReadableBuffer buffer)
|
public static Span<byte> ToSpan(this ReadableBuffer buffer)
|
||||||
{
|
{
|
||||||
if (buffer.IsSingleSpan)
|
if (buffer.IsSingleSpan)
|
||||||
|
|
@ -86,15 +77,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
return buffer.ToArray();
|
return buffer.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArraySegment<byte> ToArraySegment(this ReadableBuffer buffer)
|
|
||||||
{
|
|
||||||
if (buffer.IsSingleSpan)
|
|
||||||
{
|
|
||||||
return buffer.First.GetArray();
|
|
||||||
}
|
|
||||||
return new ArraySegment<byte>(buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
|
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
|
||||||
{
|
{
|
||||||
ArraySegment<byte> result;
|
ArraySegment<byte> result;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
{
|
||||||
|
public enum RequestProcessingStatus
|
||||||
|
{
|
||||||
|
RequestPending,
|
||||||
|
ParsingRequestLine,
|
||||||
|
ParsingHeaders,
|
||||||
|
AppStarted,
|
||||||
|
ResponseStarted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
UnrecognizedHTTPVersion,
|
UnrecognizedHTTPVersion,
|
||||||
HeadersCorruptedInvalidHeaderSequence,
|
HeadersCorruptedInvalidHeaderSequence,
|
||||||
HeaderLineMustNotStartWithWhitespace,
|
|
||||||
NoColonCharacterFoundInHeaderLine,
|
NoColonCharacterFoundInHeaderLine,
|
||||||
WhitespaceIsNotAllowedInHeaderName,
|
WhitespaceIsNotAllowedInHeaderName,
|
||||||
HeaderValueMustNotContainCR,
|
HeaderValueMustNotContainCR,
|
||||||
|
|
|
||||||
|
|
@ -1,312 +0,0 @@
|
||||||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|
||||||
{
|
|
||||||
public class UrlPathDecoder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unescapes the string between given memory iterators in place.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="start">The iterator points to the beginning of the sequence.</param>
|
|
||||||
/// <param name="end">The iterator points to the byte behind the end of the sequence.</param>
|
|
||||||
/// <returns>The iterator points to the byte behind the end of the processed sequence.</returns>
|
|
||||||
public static MemoryPoolIterator Unescape(MemoryPoolIterator start, MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
// the slot to read the input
|
|
||||||
var reader = start;
|
|
||||||
|
|
||||||
// the slot to write the unescaped byte
|
|
||||||
var writer = reader;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (CompareIterators(ref reader, ref end))
|
|
||||||
{
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader.Peek() == '%')
|
|
||||||
{
|
|
||||||
var decodeReader = reader;
|
|
||||||
|
|
||||||
// If decoding process succeeds, the writer iterator will be moved
|
|
||||||
// to the next write-ready location. On the other hand if the scanned
|
|
||||||
// percent-encodings cannot be interpreted as sequence of UTF-8 octets,
|
|
||||||
// these bytes should be copied to output as is.
|
|
||||||
// The decodeReader iterator is always moved to the first byte not yet
|
|
||||||
// be scanned after the process. A failed decoding means the chars
|
|
||||||
// between the reader and decodeReader can be copied to output untouched.
|
|
||||||
if (!DecodeCore(ref decodeReader, ref writer, end))
|
|
||||||
{
|
|
||||||
Copy(reader, decodeReader, ref writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
reader = decodeReader;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writer.Put((byte)reader.Take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unescape the percent-encodings
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The iterator point to the first % char</param>
|
|
||||||
/// <param name="writer">The place to write to</param>
|
|
||||||
/// <param name="end">The end of the sequence</param>
|
|
||||||
private static bool DecodeCore(ref MemoryPoolIterator reader, ref MemoryPoolIterator writer, MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
// preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets,
|
|
||||||
// bytes from this till the last scanned one will be copied to the memory pointed by writer.
|
|
||||||
var byte1 = UnescapePercentEncoding(ref reader, end);
|
|
||||||
|
|
||||||
if (byte1 == 0)
|
|
||||||
{
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.PathContainsNullCharacters);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte1 == -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte1 <= 0x7F)
|
|
||||||
{
|
|
||||||
// first byte < U+007f, it is a single byte ASCII
|
|
||||||
writer.Put((byte)byte1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int byte2 = 0, byte3 = 0, byte4 = 0;
|
|
||||||
|
|
||||||
// anticipate more bytes
|
|
||||||
var currentDecodeBits = 0;
|
|
||||||
var byteCount = 1;
|
|
||||||
var expectValueMin = 0;
|
|
||||||
if ((byte1 & 0xE0) == 0xC0)
|
|
||||||
{
|
|
||||||
// 110x xxxx, expect one more byte
|
|
||||||
currentDecodeBits = byte1 & 0x1F;
|
|
||||||
byteCount = 2;
|
|
||||||
expectValueMin = 0x80;
|
|
||||||
}
|
|
||||||
else if ((byte1 & 0xF0) == 0xE0)
|
|
||||||
{
|
|
||||||
// 1110 xxxx, expect two more bytes
|
|
||||||
currentDecodeBits = byte1 & 0x0F;
|
|
||||||
byteCount = 3;
|
|
||||||
expectValueMin = 0x800;
|
|
||||||
}
|
|
||||||
else if ((byte1 & 0xF8) == 0xF0)
|
|
||||||
{
|
|
||||||
// 1111 0xxx, expect three more bytes
|
|
||||||
currentDecodeBits = byte1 & 0x07;
|
|
||||||
byteCount = 4;
|
|
||||||
expectValueMin = 0x10000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// invalid first byte
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remainingBytes = byteCount - 1;
|
|
||||||
while (remainingBytes > 0)
|
|
||||||
{
|
|
||||||
// read following three chars
|
|
||||||
if (CompareIterators(ref reader, ref end))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextItr = reader;
|
|
||||||
var nextByte = UnescapePercentEncoding(ref nextItr, end);
|
|
||||||
if (nextByte == -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((nextByte & 0xC0) != 0x80)
|
|
||||||
{
|
|
||||||
// the follow up byte is not in form of 10xx xxxx
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDecodeBits = (currentDecodeBits << 6) | (nextByte & 0x3F);
|
|
||||||
remainingBytes--;
|
|
||||||
|
|
||||||
if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F)
|
|
||||||
{
|
|
||||||
// this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that
|
|
||||||
// are not allowed in UTF-8;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingBytes == 2 && currentDecodeBits >= 0x110)
|
|
||||||
{
|
|
||||||
// this is going to be out of the upper Unicode bound 0x10FFFF.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader = nextItr;
|
|
||||||
if (byteCount - remainingBytes == 2)
|
|
||||||
{
|
|
||||||
byte2 = nextByte;
|
|
||||||
}
|
|
||||||
else if (byteCount - remainingBytes == 3)
|
|
||||||
{
|
|
||||||
byte3 = nextByte;
|
|
||||||
}
|
|
||||||
else if (byteCount - remainingBytes == 4)
|
|
||||||
{
|
|
||||||
byte4 = nextByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentDecodeBits < expectValueMin)
|
|
||||||
{
|
|
||||||
// overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// all bytes are verified, write to the output
|
|
||||||
if (byteCount > 0)
|
|
||||||
{
|
|
||||||
writer.Put((byte)byte1);
|
|
||||||
}
|
|
||||||
if (byteCount > 1)
|
|
||||||
{
|
|
||||||
writer.Put((byte)byte2);
|
|
||||||
}
|
|
||||||
if (byteCount > 2)
|
|
||||||
{
|
|
||||||
writer.Put((byte)byte3);
|
|
||||||
}
|
|
||||||
if (byteCount > 3)
|
|
||||||
{
|
|
||||||
writer.Put((byte)byte4);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Copy(MemoryPoolIterator head, MemoryPoolIterator tail, ref MemoryPoolIterator writer)
|
|
||||||
{
|
|
||||||
while (!CompareIterators(ref head, ref tail))
|
|
||||||
{
|
|
||||||
writer.Put((byte)head.Take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the percent-encoding and try unescape it.
|
|
||||||
///
|
|
||||||
/// The operation first peek at the character the <paramref name="scan"/>
|
|
||||||
/// iterator points at. If it is % the <paramref name="scan"/> is then
|
|
||||||
/// moved on to scan the following to characters. If the two following
|
|
||||||
/// characters are hexadecimal literals they will be unescaped and the
|
|
||||||
/// value will be returned.
|
|
||||||
///
|
|
||||||
/// If the first character is not % the <paramref name="scan"/> iterator
|
|
||||||
/// will be removed beyond the location of % and -1 will be returned.
|
|
||||||
///
|
|
||||||
/// If the following two characters can't be successfully unescaped the
|
|
||||||
/// <paramref name="scan"/> iterator will be move behind the % and -1
|
|
||||||
/// will be returned.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scan">The value to read</param>
|
|
||||||
/// <param name="end">The end of the sequence</param>
|
|
||||||
/// <returns>The unescaped byte if success. Otherwise return -1.</returns>
|
|
||||||
private static int UnescapePercentEncoding(ref MemoryPoolIterator scan, MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
if (scan.Take() != '%')
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var probe = scan;
|
|
||||||
|
|
||||||
int value1 = ReadHex(ref probe, end);
|
|
||||||
if (value1 == -1)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int value2 = ReadHex(ref probe, end);
|
|
||||||
if (value2 == -1)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SkipUnescape(value1, value2))
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
scan = probe;
|
|
||||||
return (value1 << 4) + value2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the next char and convert it into hexadecimal value.
|
|
||||||
///
|
|
||||||
/// The <paramref name="scan"/> iterator will be moved to the next
|
|
||||||
/// byte no matter no matter whether the operation successes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scan">The value to read</param>
|
|
||||||
/// <param name="end">The end of the sequence</param>
|
|
||||||
/// <returns>The hexadecimal value if successes, otherwise -1.</returns>
|
|
||||||
private static int ReadHex(ref MemoryPoolIterator scan, MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
if (CompareIterators(ref scan, ref end))
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = scan.Take();
|
|
||||||
var isHead = (((value >= '0') && (value <= '9')) ||
|
|
||||||
((value >= 'A') && (value <= 'F')) ||
|
|
||||||
((value >= 'a') && (value <= 'f')));
|
|
||||||
|
|
||||||
if (!isHead)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value <= '9')
|
|
||||||
{
|
|
||||||
return value - '0';
|
|
||||||
}
|
|
||||||
else if (value <= 'F')
|
|
||||||
{
|
|
||||||
return (value - 'A') + 10;
|
|
||||||
}
|
|
||||||
else // a - f
|
|
||||||
{
|
|
||||||
return (value - 'a') + 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool SkipUnescape(int value1, int value2)
|
|
||||||
{
|
|
||||||
// skip %2F
|
|
||||||
if (value1 == 2 && value2 == 15)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool CompareIterators(ref MemoryPoolIterator lhs, ref MemoryPoolIterator rhs)
|
|
||||||
{
|
|
||||||
// uses ref parameter to save cost of copying
|
|
||||||
return (lhs.Block == rhs.Block) && (lhs.Index == rhs.Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// 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.Runtime.CompilerServices;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|
||||||
{
|
|
||||||
internal static class AwaitableThreadPool
|
|
||||||
{
|
|
||||||
internal static Awaitable Yield()
|
|
||||||
{
|
|
||||||
return new Awaitable();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct Awaitable : ICriticalNotifyCompletion
|
|
||||||
{
|
|
||||||
public void GetResult()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Awaitable GetAwaiter() => this;
|
|
||||||
|
|
||||||
public bool IsCompleted => false;
|
|
||||||
|
|
||||||
public void OnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
Task.Run(continuation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnsafeOnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
OnCompleted(continuation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IPEndPoint Kestrel will bind to if nothing else is specified.
|
/// The IPEndPoint Kestrel will bind to if nothing else is specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly IPEndPoint DefaultIPEndPoint = new IPEndPoint(IPAddress.Loopback, 5000);
|
public static readonly string DefaultServerAddress = "http://localhost:5000";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prefix of host name used to specify Unix sockets in the configuration.
|
/// Prefix of host name used to specify Unix sockets in the configuration.
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe static string GetAsciiStringNonNullCharacters(this Span<byte> span)
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var asciiString = new string('\0', span.Length);
|
||||||
|
|
||||||
|
fixed (char* output = asciiString)
|
||||||
|
fixed (byte* buffer = &span.DangerousGetPinnableReference())
|
||||||
|
{
|
||||||
|
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
|
||||||
|
// in the string
|
||||||
|
if (!AsciiUtilities.TryGetAsciiString(buffer, output, span.Length))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asciiString;
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetAsciiStringEscaped(this Span<byte> span, int maxChars)
|
public static string GetAsciiStringEscaped(this Span<byte> span, int maxChars)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
public struct MemoryPoolIterator
|
public struct MemoryPoolIterator
|
||||||
{
|
{
|
||||||
private const int _maxULongByteLength = 20;
|
private const int _maxULongByteLength = 20;
|
||||||
private const ulong _xorPowerOfTwoToHighByte = (0x07ul |
|
|
||||||
0x06ul << 8 |
|
|
||||||
0x05ul << 16 |
|
|
||||||
0x04ul << 24 |
|
|
||||||
0x03ul << 32 |
|
|
||||||
0x02ul << 40 |
|
|
||||||
0x01ul << 48 ) + 1;
|
|
||||||
|
|
||||||
private static readonly int _vectorSpan = Vector<byte>.Count;
|
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static byte[] _numericBytesScratch;
|
private static byte[] _numericBytesScratch;
|
||||||
|
|
@ -136,667 +127,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
} while (true);
|
} while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void Skip(int bytesToSkip)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null && bytesToSkip > 0)
|
|
||||||
{
|
|
||||||
ThrowInvalidOperationException_SkipMoreThanAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always set wasLastBlock before checking .End to avoid race which may cause data loss
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - _index;
|
|
||||||
|
|
||||||
if (following >= bytesToSkip)
|
|
||||||
{
|
|
||||||
_index += bytesToSkip;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasLastBlock)
|
|
||||||
{
|
|
||||||
ThrowInvalidOperationException_SkipMoreThanAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
SkipMultiBlock(bytesToSkip, following);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private void SkipMultiBlock(int bytesToSkip, int following)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
bytesToSkip -= following;
|
|
||||||
block = block.Next;
|
|
||||||
var index = block.Start;
|
|
||||||
|
|
||||||
// Always set wasLastBlock before checking .End to avoid race which may cause data loss
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
following = block.End - index;
|
|
||||||
|
|
||||||
if (following >= bytesToSkip)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index + bytesToSkip;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasLastBlock)
|
|
||||||
{
|
|
||||||
ThrowInvalidOperationException_SkipMoreThanAvailable();
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ThrowInvalidOperationException_SkipMoreThanAvailable()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Attempted to skip more bytes than available.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public int Peek()
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
|
|
||||||
// Always set wasLastBlock before checking .End to avoid race which may cause data loss
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
if (index < block.End)
|
|
||||||
{
|
|
||||||
return block.Array[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return wasLastBlock ? -1 : PeekMultiBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private int PeekMultiBlock()
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
block = block.Next;
|
|
||||||
var index = block.Start;
|
|
||||||
|
|
||||||
// Always set wasLastBlock before checking .End to avoid race which may cause data loss
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
|
|
||||||
if (index < block.End)
|
|
||||||
{
|
|
||||||
return block.Array[index];
|
|
||||||
}
|
|
||||||
if (wasLastBlock)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Little-endian only!
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public unsafe bool TryPeekLong(out ulong longValue)
|
|
||||||
{
|
|
||||||
longValue = 0;
|
|
||||||
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always set wasLastBlock before checking .End to avoid race which may cause data loss
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var blockBytes = block.End - _index;
|
|
||||||
|
|
||||||
if (blockBytes >= sizeof(ulong))
|
|
||||||
{
|
|
||||||
longValue = *(ulong*)(block.DataFixedPtr + _index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wasLastBlock ? false : TryPeekLongMultiBlock(ref longValue, blockBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private unsafe bool TryPeekLongMultiBlock(ref ulong longValue, int blockBytes)
|
|
||||||
{
|
|
||||||
// Each block will be filled with at least 2048 bytes before the Next pointer is set, so a long
|
|
||||||
// will cross at most one block boundary assuming there are at least 8 bytes following the iterator.
|
|
||||||
var nextBytes = sizeof(ulong) - blockBytes;
|
|
||||||
|
|
||||||
var block = _block;
|
|
||||||
if (block.Next.End - block.Next.Start < nextBytes)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextLong = *(ulong*)(block.Next.DataFixedPtr + block.Next.Start);
|
|
||||||
|
|
||||||
if (blockBytes == 0)
|
|
||||||
{
|
|
||||||
// This case can not fall through to the else block since that would cause a 64-bit right shift
|
|
||||||
// on blockLong which is equivalent to no shift at all instead of shifting in all zeros.
|
|
||||||
// https://msdn.microsoft.com/en-us/library/xt18et0d.aspx
|
|
||||||
longValue = nextLong;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var blockLong = *(ulong*)(block.DataFixedPtr + block.End - sizeof(ulong));
|
|
||||||
|
|
||||||
// Ensure that the right shift has a ulong operand so a logical shift is performed.
|
|
||||||
longValue = (blockLong >> nextBytes * 8) | (nextLong << blockBytes * 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Seek(byte byte0)
|
|
||||||
{
|
|
||||||
int bytesScanned;
|
|
||||||
return Seek(byte0, out bytesScanned);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe int Seek(
|
|
||||||
byte byte0,
|
|
||||||
out int bytesScanned,
|
|
||||||
int limit = int.MaxValue)
|
|
||||||
{
|
|
||||||
bytesScanned = 0;
|
|
||||||
|
|
||||||
var block = _block;
|
|
||||||
if (block == null || limit <= 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - index;
|
|
||||||
byte[] array;
|
|
||||||
var byte0Vector = GetVector(byte0);
|
|
||||||
|
|
||||||
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 = LocateFirstFoundByte(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(
|
|
||||||
byte byte0,
|
|
||||||
ref MemoryPoolIterator limit)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - index;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
while (following == 0)
|
|
||||||
{
|
|
||||||
if ((block == limit.Block && index > limit.Index) ||
|
|
||||||
wasLastBlock)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
block = block.Next;
|
|
||||||
index = block.Start;
|
|
||||||
wasLastBlock = block.Next == null;
|
|
||||||
following = block.End - index;
|
|
||||||
}
|
|
||||||
var 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), GetVector(byte0));
|
|
||||||
|
|
||||||
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;
|
|
||||||
index += _vectorSpan;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_block = block;
|
|
||||||
|
|
||||||
var firstEqualByteIndex = LocateFirstFoundByte(byte0Equals);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
// Need unit tests to test Vector path
|
|
||||||
#if !DEBUG
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
|
||||||
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (*pCurrent == byte0)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte0;
|
|
||||||
}
|
|
||||||
pCurrent++;
|
|
||||||
index++;
|
|
||||||
} while (pCurrent < pEnd);
|
|
||||||
|
|
||||||
following = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Seek(byte byte0, byte byte1)
|
|
||||||
{
|
|
||||||
var limit = new MemoryPoolIterator();
|
|
||||||
return Seek(byte0, byte1, ref limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe int Seek(
|
|
||||||
byte byte0,
|
|
||||||
byte byte1,
|
|
||||||
ref MemoryPoolIterator limit)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - index;
|
|
||||||
int byteIndex = int.MaxValue;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
while (following == 0)
|
|
||||||
{
|
|
||||||
if ((block == limit.Block && index > limit.Index) ||
|
|
||||||
wasLastBlock)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
block = block.Next;
|
|
||||||
index = block.Start;
|
|
||||||
wasLastBlock = block.Next == null;
|
|
||||||
following = block.End - index;
|
|
||||||
}
|
|
||||||
var 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 data = new Vector<byte>(array, index);
|
|
||||||
|
|
||||||
var byteEquals = Vector.Equals(data, GetVector(byte0));
|
|
||||||
byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1)));
|
|
||||||
|
|
||||||
if (!byteEquals.Equals(Vector<byte>.Zero))
|
|
||||||
{
|
|
||||||
byteIndex = LocateFirstFoundByte(byteEquals);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byteIndex == int.MaxValue)
|
|
||||||
{
|
|
||||||
following -= _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;
|
|
||||||
}
|
|
||||||
|
|
||||||
_block = block;
|
|
||||||
|
|
||||||
_index = index + byteIndex;
|
|
||||||
|
|
||||||
if (block == limit.Block && _index > limit.Index)
|
|
||||||
{
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_index = index + byteIndex;
|
|
||||||
|
|
||||||
if (block == limit.Block && _index > limit.Index)
|
|
||||||
{
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return block.Array[index + byteIndex];
|
|
||||||
}
|
|
||||||
// Need unit tests to test Vector path
|
|
||||||
#if !DEBUG
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
|
||||||
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (*pCurrent == byte0)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte0;
|
|
||||||
}
|
|
||||||
if (*pCurrent == byte1)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte1;
|
|
||||||
}
|
|
||||||
pCurrent++;
|
|
||||||
index++;
|
|
||||||
} while (pCurrent != pEnd);
|
|
||||||
|
|
||||||
following = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Seek(byte byte0, byte byte1, byte byte2)
|
|
||||||
{
|
|
||||||
var limit = new MemoryPoolIterator();
|
|
||||||
return Seek(byte0, byte1, byte2, ref limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe int Seek(
|
|
||||||
byte byte0,
|
|
||||||
byte byte1,
|
|
||||||
byte byte2,
|
|
||||||
ref MemoryPoolIterator limit)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - index;
|
|
||||||
int byteIndex = int.MaxValue;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
while (following == 0)
|
|
||||||
{
|
|
||||||
if ((block == limit.Block && index > limit.Index) ||
|
|
||||||
wasLastBlock)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
block = block.Next;
|
|
||||||
index = block.Start;
|
|
||||||
wasLastBlock = block.Next == null;
|
|
||||||
following = block.End - index;
|
|
||||||
}
|
|
||||||
var 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 data = new Vector<byte>(array, index);
|
|
||||||
|
|
||||||
var byteEquals = Vector.Equals(data, GetVector(byte0));
|
|
||||||
byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte1)));
|
|
||||||
byteEquals = Vector.ConditionalSelect(byteEquals, byteEquals, Vector.Equals(data, GetVector(byte2)));
|
|
||||||
|
|
||||||
if (!byteEquals.Equals(Vector<byte>.Zero))
|
|
||||||
{
|
|
||||||
byteIndex = LocateFirstFoundByte(byteEquals);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byteIndex == int.MaxValue)
|
|
||||||
{
|
|
||||||
following -= _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;
|
|
||||||
}
|
|
||||||
|
|
||||||
_block = block;
|
|
||||||
|
|
||||||
_index = index + byteIndex;
|
|
||||||
|
|
||||||
if (block == limit.Block && _index > limit.Index)
|
|
||||||
{
|
|
||||||
// Ensure iterator is left at limit position
|
|
||||||
_index = limit.Index;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return block.Array[index + byteIndex];
|
|
||||||
}
|
|
||||||
// Need unit tests to test Vector path
|
|
||||||
#if !DEBUG
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
var pCurrent = (block.DataFixedPtr + index);
|
|
||||||
var pEnd = block == limit.Block ? block.DataFixedPtr + limit.Index + 1 : pCurrent + following;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (*pCurrent == byte0)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte0;
|
|
||||||
}
|
|
||||||
if (*pCurrent == byte1)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte1;
|
|
||||||
}
|
|
||||||
if (*pCurrent == byte2)
|
|
||||||
{
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return byte2;
|
|
||||||
}
|
|
||||||
pCurrent++;
|
|
||||||
index++;
|
|
||||||
} while (pCurrent != pEnd);
|
|
||||||
|
|
||||||
following = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Locate the first of the found bytes
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="byteEquals"></param >
|
|
||||||
/// <returns>The first index of the result vector</returns>
|
|
||||||
// Force inlining (64 IL bytes, 91 bytes asm) Issue: https://github.com/dotnet/coreclr/issues/7386
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal static int LocateFirstFoundByte(Vector<byte> byteEquals)
|
|
||||||
{
|
|
||||||
var vector64 = Vector.AsVectorUInt64(byteEquals);
|
|
||||||
ulong longValue = 0;
|
|
||||||
var i = 0;
|
|
||||||
// Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
|
|
||||||
for (; i < Vector<ulong>.Count; i++)
|
|
||||||
{
|
|
||||||
longValue = vector64[i];
|
|
||||||
if (longValue == 0) continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag least significant power of two bit
|
|
||||||
var powerOfTwoFlag = (longValue ^ (longValue - 1));
|
|
||||||
// Shift all powers of two into the high byte and extract
|
|
||||||
var foundByteIndex = (int)((powerOfTwoFlag * _xorPowerOfTwoToHighByte) >> 57);
|
|
||||||
// Single LEA instruction with jitted const (using function result)
|
|
||||||
return i * 8 + foundByteIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save the data at the current location then move to the next available space.
|
/// Save the data at the current location then move to the next available space.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -914,57 +244,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryPoolIterator CopyTo(byte[] array, int offset, int count, out int actual)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
actual = 0;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
var remaining = count;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Determine if we might attempt to copy data from block.Next before
|
|
||||||
// calculating "following" so we don't risk skipping data that could
|
|
||||||
// be added after block.End when we decide to copy from block.Next.
|
|
||||||
// block.End will always be advanced before block.Next is set.
|
|
||||||
var wasLastBlock = block.Next == null;
|
|
||||||
var following = block.End - index;
|
|
||||||
if (remaining <= following)
|
|
||||||
{
|
|
||||||
actual = count;
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
Buffer.BlockCopy(block.Array, index, array, offset, remaining);
|
|
||||||
}
|
|
||||||
return new MemoryPoolIterator(block, index + remaining);
|
|
||||||
}
|
|
||||||
else if (wasLastBlock)
|
|
||||||
{
|
|
||||||
actual = count - remaining + following;
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
Buffer.BlockCopy(block.Array, index, array, offset, following);
|
|
||||||
}
|
|
||||||
return new MemoryPoolIterator(block, index + following);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
Buffer.BlockCopy(block.Array, index, array, offset, following);
|
|
||||||
}
|
|
||||||
offset += following;
|
|
||||||
remaining -= following;
|
|
||||||
block = block.Next;
|
|
||||||
index = block.Start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyFrom(byte[] data)
|
public void CopyFrom(byte[] data)
|
||||||
{
|
{
|
||||||
CopyFrom(data, 0, data.Length);
|
CopyFrom(data, 0, data.Length);
|
||||||
|
|
@ -1180,177 +459,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
||||||
|
|
||||||
CopyFrom(byteBuffer, position, _maxULongByteLength - position);
|
CopyFrom(byteBuffer, position, _maxULongByteLength - position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe string GetAsciiString(ref MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null || end.IsDefault)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = GetLength(end);
|
|
||||||
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputOffset = _index;
|
|
||||||
|
|
||||||
var asciiString = new string('\0', length);
|
|
||||||
|
|
||||||
fixed (char* outputStart = asciiString)
|
|
||||||
{
|
|
||||||
var output = outputStart;
|
|
||||||
var remaining = length;
|
|
||||||
|
|
||||||
var endBlock = end.Block;
|
|
||||||
var endIndex = end.Index;
|
|
||||||
|
|
||||||
var outputOffset = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int following = (block != endBlock ? block.End : endIndex) - inputOffset;
|
|
||||||
|
|
||||||
if (following > 0)
|
|
||||||
{
|
|
||||||
if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following))
|
|
||||||
{
|
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.NonAsciiOrNullCharactersInInputString);
|
|
||||||
}
|
|
||||||
|
|
||||||
outputOffset += following;
|
|
||||||
remaining -= following;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remaining == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
block = block.Next;
|
|
||||||
inputOffset = block.Start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return asciiString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetUtf8String(ref MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null || end.IsDefault)
|
|
||||||
{
|
|
||||||
return default(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
if (end.Block == block)
|
|
||||||
{
|
|
||||||
return Encoding.UTF8.GetString(block.Array, index, end.Index - index);
|
|
||||||
}
|
|
||||||
|
|
||||||
var decoder = Encoding.UTF8.GetDecoder();
|
|
||||||
|
|
||||||
var length = GetLength(end);
|
|
||||||
var charLength = length;
|
|
||||||
// Worse case is 1 byte = 1 char
|
|
||||||
var chars = new char[charLength];
|
|
||||||
var charIndex = 0;
|
|
||||||
|
|
||||||
var remaining = length;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int bytesUsed;
|
|
||||||
int charsUsed;
|
|
||||||
bool completed;
|
|
||||||
var following = block.End - index;
|
|
||||||
if (remaining <= following)
|
|
||||||
{
|
|
||||||
decoder.Convert(
|
|
||||||
block.Array,
|
|
||||||
index,
|
|
||||||
remaining,
|
|
||||||
chars,
|
|
||||||
charIndex,
|
|
||||||
charLength - charIndex,
|
|
||||||
true,
|
|
||||||
out bytesUsed,
|
|
||||||
out charsUsed,
|
|
||||||
out completed);
|
|
||||||
return new string(chars, 0, charIndex + charsUsed);
|
|
||||||
}
|
|
||||||
else if (block.Next == null)
|
|
||||||
{
|
|
||||||
decoder.Convert(
|
|
||||||
block.Array,
|
|
||||||
index,
|
|
||||||
following,
|
|
||||||
chars,
|
|
||||||
charIndex,
|
|
||||||
charLength - charIndex,
|
|
||||||
true,
|
|
||||||
out bytesUsed,
|
|
||||||
out charsUsed,
|
|
||||||
out completed);
|
|
||||||
return new string(chars, 0, charIndex + charsUsed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
decoder.Convert(
|
|
||||||
block.Array,
|
|
||||||
index,
|
|
||||||
following,
|
|
||||||
chars,
|
|
||||||
charIndex,
|
|
||||||
charLength - charIndex,
|
|
||||||
false,
|
|
||||||
out bytesUsed,
|
|
||||||
out charsUsed,
|
|
||||||
out completed);
|
|
||||||
charIndex += charsUsed;
|
|
||||||
remaining -= following;
|
|
||||||
block = block.Next;
|
|
||||||
index = block.Start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public ArraySegment<byte> GetArraySegment(MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
if (block == null || end.IsDefault)
|
|
||||||
{
|
|
||||||
return default(ArraySegment<byte>);
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = _index;
|
|
||||||
if (end.Block == block)
|
|
||||||
{
|
|
||||||
return new ArraySegment<byte>(block.Array, index, end.Index - index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetArraySegmentMultiBlock(ref end);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private ArraySegment<byte> GetArraySegmentMultiBlock(ref MemoryPoolIterator end)
|
|
||||||
{
|
|
||||||
var length = GetLength(end);
|
|
||||||
var array = new byte[length];
|
|
||||||
CopyTo(array, 0, length, out length);
|
|
||||||
return new ArraySegment<byte>(array, 0, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Vector<byte> GetVector(byte vectorByte)
|
|
||||||
{
|
|
||||||
// Vector<byte> .ctor doesn't become an intrinsic due to detection issue
|
|
||||||
// However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
|
|
||||||
// https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
|
|
||||||
return Vector.AsVectorByte(new Vector<uint>(vectorByte * 0x01010101u));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
}
|
}
|
||||||
else if (!hasListenOptions && !hasServerAddresses)
|
else if (!hasListenOptions && !hasServerAddresses)
|
||||||
{
|
{
|
||||||
_logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultIPEndPoint} by default.");
|
_logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");
|
||||||
listenOptions.Add(new ListenOptions(Constants.DefaultIPEndPoint));
|
|
||||||
|
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
||||||
|
StartLocalhost(engine, ServerAddress.FromUrl(Constants.DefaultServerAddress));
|
||||||
|
|
||||||
|
// If StartLocalhost doesn't throw, there is at least one listener.
|
||||||
|
// The port cannot change for "localhost".
|
||||||
|
_serverAddresses.Addresses.Add(Constants.DefaultServerAddress);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (!hasListenOptions)
|
else if (!hasListenOptions)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,9 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" />
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="$(CoreFxVersion)" />
|
<PackageReference Include="System.Numerics.Vectors" Version="$(CoreFxVersion)" />
|
||||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(CoreFxVersion)" />
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(CoreFxVersion)" />
|
||||||
|
<PackageReference Include="System.IO.Pipelines" Version="$(CoreFxLabsPipelinesVersion)" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="0.1.0-*" />
|
<PackageReference Include="System.IO.Pipelines.Text.Primitives" Version="$(CoreFxLabsVersion)" />
|
||||||
<PackageReference Include="System.IO.Pipelines.Text.Primitives" Version="0.1.0-*" />
|
<PackageReference Include="System.Text.Encodings.Web.Utf8" Version="$(CoreFxLabsVersion)" />
|
||||||
<PackageReference Include="System.Text.Encodings.Web.Utf8" Version="0.1.0-*" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -129,31 +130,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalFact(Skip = "Waiting on https://github.com/aspnet/Hosting/issues/917")]
|
[ConditionalFact]
|
||||||
[PortSupportedCondition(5000)]
|
[PortSupportedCondition(5000)]
|
||||||
public async Task DefaultsToPort5000()
|
public Task DefaultsServerAddress_BindsToIPv4()
|
||||||
|
{
|
||||||
|
return RegisterDefaultServerAddresses_Success(new[] { "http://127.0.0.1:5000" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[IPv6SupportedCondition]
|
||||||
|
[PortSupportedCondition(5000)]
|
||||||
|
public Task DefaultsServerAddress_BindsToIPv6()
|
||||||
|
{
|
||||||
|
return RegisterDefaultServerAddresses_Success(new[] { "http://127.0.0.1:5000", "http://[::1]:5000" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RegisterDefaultServerAddresses_Success(IEnumerable<string> addresses)
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger();
|
var testLogger = new TestApplicationErrorLogger();
|
||||||
|
|
||||||
var hostBuilder = new WebHostBuilder()
|
var hostBuilder = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<ILoggerFactory>(new KestrelTestLoggerFactory(testLogger));
|
services.AddSingleton<ILoggerFactory>(new KestrelTestLoggerFactory(testLogger));
|
||||||
})
|
})
|
||||||
.Configure(ConfigureEchoAddress);
|
.Configure(ConfigureEchoAddress);
|
||||||
|
|
||||||
using (var host = hostBuilder.Build())
|
using (var host = hostBuilder.Build())
|
||||||
{
|
{
|
||||||
host.Start();
|
host.Start();
|
||||||
|
|
||||||
var debugLog = testLogger.Messages.Single(log => log.LogLevel == LogLevel.Debug);
|
Assert.Equal(5000, host.GetPort());
|
||||||
Assert.True(debugLog.Message.Contains("default"));
|
Assert.Single(testLogger.Messages, log => log.LogLevel == LogLevel.Debug &&
|
||||||
|
string.Equals($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.",
|
||||||
|
log.Message, StringComparison.Ordinal));
|
||||||
|
|
||||||
foreach (var testUrl in new[] { "http://127.0.0.1:5000", "http://localhost:5000" })
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
var response = await HttpClientSlim.GetStringAsync(testUrl);
|
Assert.Equal(new Uri(address).ToString(), await HttpClientSlim.GetStringAsync(address));
|
||||||
Assert.Equal(new Uri(testUrl).ToString(), response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
|
@ -14,106 +16,102 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(InvalidRequestLineData))]
|
[MemberData(nameof(InvalidRequestLineData))]
|
||||||
public async Task TestInvalidRequestLines(string request)
|
public Task TestInvalidRequestLines(string request, string expectedExceptionMessage)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => TaskCache.CompletedTask))
|
return TestBadRequest(
|
||||||
{
|
request,
|
||||||
using (var connection = server.CreateConnection())
|
"400 Bad Request",
|
||||||
{
|
expectedExceptionMessage);
|
||||||
await connection.SendAll(request);
|
|
||||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
||||||
public async Task TestInvalidRequestLinesWithUnrecognizedVersion(string httpVersion)
|
public Task TestInvalidRequestLinesWithUnrecognizedVersion(string httpVersion)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => TaskCache.CompletedTask))
|
return TestBadRequest(
|
||||||
{
|
$"GET / {httpVersion}\r\n",
|
||||||
using (var connection = server.CreateConnection())
|
"505 HTTP Version Not Supported",
|
||||||
{
|
$"Unrecognized HTTP version: {httpVersion}");
|
||||||
await connection.SendAll($"GET / {httpVersion}\r\n");
|
|
||||||
await ReceiveBadRequestResponse(connection, "505 HTTP Version Not Supported", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(InvalidRequestHeaderData))]
|
[MemberData(nameof(InvalidRequestHeaderData))]
|
||||||
public async Task TestInvalidHeaders(string rawHeaders)
|
public Task TestInvalidHeaders(string rawHeaders, string expectedExceptionMessage)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => TaskCache.CompletedTask))
|
return TestBadRequest(
|
||||||
{
|
$"GET / HTTP/1.1\r\n{rawHeaders}",
|
||||||
using (var connection = server.CreateConnection())
|
"400 Bad Request",
|
||||||
{
|
expectedExceptionMessage);
|
||||||
await connection.SendAll($"GET / HTTP/1.1\r\n{rawHeaders}");
|
|
||||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public async Task BadRequestWhenHeaderNameContainsNonASCIICharacters()
|
[InlineData("Hea\0der: value", "Invalid characters in header name.")]
|
||||||
|
[InlineData("Header: va\0lue", "Malformed request: invalid headers.")]
|
||||||
|
[InlineData("Head\x80r: value", "Invalid characters in header name.")]
|
||||||
|
[InlineData("Header: valu\x80", "Malformed request: invalid headers.")]
|
||||||
|
public Task BadRequestWhenHeaderNameContainsNonASCIIOrNullCharacters(string header, string expectedExceptionMessage)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
return TestBadRequest(
|
||||||
{
|
$"GET / HTTP/1.1\r\n{header}\r\n\r\n",
|
||||||
using (var connection = server.CreateConnection())
|
"400 Bad Request",
|
||||||
{
|
expectedExceptionMessage);
|
||||||
await connection.SendAll(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"H\u00eb\u00e4d\u00ebr: value",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("POST")]
|
[InlineData("POST")]
|
||||||
[InlineData("PUT")]
|
[InlineData("PUT")]
|
||||||
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
|
public Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
return TestBadRequest(
|
||||||
{
|
$"{method} / HTTP/1.1\r\n\r\n",
|
||||||
using (var connection = server.CreateConnection())
|
"411 Length Required",
|
||||||
{
|
$"{method} request contains no Content-Length or Transfer-Encoding header");
|
||||||
await connection.Send($"{method} / HTTP/1.1\r\n\r\n");
|
|
||||||
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("POST")]
|
[InlineData("POST")]
|
||||||
[InlineData("PUT")]
|
[InlineData("PUT")]
|
||||||
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
|
public Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
return TestBadRequest(
|
||||||
{
|
$"{method} / HTTP/1.0\r\n\r\n",
|
||||||
using (var connection = server.CreateConnection())
|
"400 Bad Request",
|
||||||
{
|
$"{method} request contains no Content-Length header");
|
||||||
await connection.Send($"{method} / HTTP/1.0\r\n\r\n");
|
|
||||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("NaN")]
|
[InlineData("NaN")]
|
||||||
[InlineData("-1")]
|
[InlineData("-1")]
|
||||||
public async Task BadRequestIfContentLengthInvalid(string contentLength)
|
public Task BadRequestIfContentLengthInvalid(string contentLength)
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
return TestBadRequest(
|
||||||
|
$"POST / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n",
|
||||||
|
"400 Bad Request",
|
||||||
|
$"Invalid content length: {contentLength}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TestBadRequest(string request, string expectedResponseStatusCode, string expectedExceptionMessage)
|
||||||
|
{
|
||||||
|
BadHttpRequestException loggedException = null;
|
||||||
|
var mockKestrelTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockKestrelTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(true);
|
||||||
|
mockKestrelTrace
|
||||||
|
.Setup(trace => trace.ConnectionBadRequest(It.IsAny<string>(), It.IsAny<BadHttpRequestException>()))
|
||||||
|
.Callback<string, BadHttpRequestException>((connectionId, exception) => loggedException = exception);
|
||||||
|
|
||||||
|
using (var server = new TestServer(context => TaskCache.CompletedTask, new TestServiceContext { Log = mockKestrelTrace.Object }))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
await connection.SendAll($"GET / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n");
|
await connection.SendAll(request);
|
||||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
await ReceiveBadRequestResponse(connection, expectedResponseStatusCode, server.Context.DateHeaderValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockKestrelTrace.Verify(trace => trace.ConnectionBadRequest(It.IsAny<string>(), It.IsAny<BadHttpRequestException>()));
|
||||||
|
Assert.Equal(expectedExceptionMessage, loggedException.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
|
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
|
||||||
|
|
@ -127,10 +125,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData.Select(data => new[] { data[0] });
|
public static TheoryData<string, string> InvalidRequestLineData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = new TheoryData<string, string>();
|
||||||
|
|
||||||
|
foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
|
||||||
|
{
|
||||||
|
data.Add(requestLine, $"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
||||||
|
{
|
||||||
|
data.Add(requestLine, "Invalid request line.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
||||||
|
{
|
||||||
|
data.Add(requestLine, "Invalid request line.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||||
|
|
||||||
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData.Select(data => new[] { data[0] });
|
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.RequestHeaderInvalidData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,8 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Ensure that dotnet is added to the PATH.
|
|
||||||
# build.sh should always be run before this script to create the .build/ directory and restore packages.
|
|
||||||
scriptDir=$(dirname "${BASH_SOURCE[0]}")
|
scriptDir=$(dirname "${BASH_SOURCE[0]}")
|
||||||
repoDir=$(cd $scriptDir/../../.. && pwd)
|
~/.dotnet/dotnet publish -f netcoreapp1.1 ./samples/SampleApp/
|
||||||
source ./.build/KoreBuild.sh -r $repoDir --quiet
|
|
||||||
|
|
||||||
dotnet publish -f netcoreapp1.1 ./samples/SampleApp/
|
|
||||||
cp -R ./samples/SampleApp/bin/Debug/netcoreapp1.1/publish/ $scriptDir
|
cp -R ./samples/SampleApp/bin/Debug/netcoreapp1.1/publish/ $scriptDir
|
||||||
cp -R ~/.dotnet/ $scriptDir
|
cp -R ~/.dotnet/ $scriptDir
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// 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.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
[Config(typeof(CoreConfig))]
|
||||||
|
public class FrameParsingOverhead
|
||||||
|
{
|
||||||
|
private const int InnerLoopCount = 512;
|
||||||
|
|
||||||
|
public ReadableBuffer _buffer;
|
||||||
|
public Frame<object> _frame;
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var connectionContext = new MockConnection(new KestrelServerOptions());
|
||||||
|
connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => NullParser.Instance;
|
||||||
|
|
||||||
|
_frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)]
|
||||||
|
public void FrameOverheadTotal()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
ParseRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
||||||
|
public void FrameOverheadRequestLine()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
ParseRequestLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
||||||
|
public void FrameOverheadRequestHeaders()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
ParseRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseRequest()
|
||||||
|
{
|
||||||
|
_frame.Reset();
|
||||||
|
|
||||||
|
if (!_frame.TakeStartLine(_buffer, out var consumed, out var examined))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
_frame.InitializeHeaders();
|
||||||
|
|
||||||
|
if (!_frame.TakeMessageHeaders(_buffer, out consumed, out examined))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseRequestLine()
|
||||||
|
{
|
||||||
|
_frame.Reset();
|
||||||
|
|
||||||
|
if (!_frame.TakeStartLine(_buffer, out var consumed, out var examined))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseRequestHeaders()
|
||||||
|
{
|
||||||
|
_frame.Reset();
|
||||||
|
_frame.InitializeHeaders();
|
||||||
|
|
||||||
|
if (!_frame.TakeMessageHeaders(_buffer, out var consumed, out var examined))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullParser : IHttpParser
|
||||||
|
{
|
||||||
|
private readonly byte[] _target = Encoding.ASCII.GetBytes("/plaintext");
|
||||||
|
private readonly byte[] _hostHeaderName = Encoding.ASCII.GetBytes("Host");
|
||||||
|
private readonly byte[] _hostHeaderValue = Encoding.ASCII.GetBytes("www.example.com");
|
||||||
|
private readonly byte[] _acceptHeaderName = Encoding.ASCII.GetBytes("Accept");
|
||||||
|
private readonly byte[] _acceptHeaderValue = Encoding.ASCII.GetBytes("text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7\r\n\r\n");
|
||||||
|
private readonly byte[] _connectionHeaderName = Encoding.ASCII.GetBytes("Connection");
|
||||||
|
private readonly byte[] _connectionHeaderValue = Encoding.ASCII.GetBytes("keep-alive");
|
||||||
|
|
||||||
|
public static readonly NullParser Instance = new NullParser();
|
||||||
|
|
||||||
|
public bool ParseHeaders<T>(T handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes) where T : IHttpHeadersHandler
|
||||||
|
{
|
||||||
|
handler.OnHeader(new Span<byte>(_hostHeaderName), new Span<byte>(_hostHeaderValue));
|
||||||
|
handler.OnHeader(new Span<byte>(_acceptHeaderName), new Span<byte>(_acceptHeaderValue));
|
||||||
|
handler.OnHeader(new Span<byte>(_connectionHeaderName), new Span<byte>(_connectionHeaderValue));
|
||||||
|
|
||||||
|
consumedBytes = 0;
|
||||||
|
consumed = buffer.Start;
|
||||||
|
examined = buffer.End;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ParseRequestLine<T>(T handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) where T : IHttpRequestLineHandler
|
||||||
|
{
|
||||||
|
handler.OnStartLine(HttpMethod.Get, HttpVersion.Http11, new Span<byte>(_target), new Span<byte>(_target), Span<byte>.Empty, Span<byte>.Empty);
|
||||||
|
|
||||||
|
consumed = buffer.Start;
|
||||||
|
examined = buffer.End;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
// 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.IO.Pipelines;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
[Config(typeof(CoreConfig))]
|
||||||
|
|
||||||
|
public class KestrelHttpParser : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||||
|
{
|
||||||
|
private readonly Internal.Http.KestrelHttpParser _parser = new Internal.Http.KestrelHttpParser(log: null);
|
||||||
|
|
||||||
|
private ReadableBuffer _buffer;
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
|
public void PlaintextTechEmpower()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
|
||||||
|
ParseData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
|
public void LiveAspNet()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
InsertData(RequestParsingData.LiveaspnetRequest);
|
||||||
|
ParseData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
|
public void Unicode()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
|
{
|
||||||
|
InsertData(RequestParsingData.UnicodeRequest);
|
||||||
|
ParseData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertData(byte[] data)
|
||||||
|
{
|
||||||
|
_buffer = ReadableBuffer.Create(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseData()
|
||||||
|
{
|
||||||
|
if (!_parser.ParseRequestLine(this, _buffer, out var consumed, out var examined))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer = _buffer.Slice(consumed, _buffer.End);
|
||||||
|
|
||||||
|
if (!_parser.ParseHeaders(this, _buffer, out consumed, out examined, out var consumedBytes))
|
||||||
|
{
|
||||||
|
RequestParsing.ThrowInvalidRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Import Project="..\..\build\common.props" />
|
<Import Project="..\..\build\common.props" />
|
||||||
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.2.*" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.10.3" />
|
||||||
<PackageReference Include="Moq" Version="4.6.36-*" />
|
<PackageReference Include="Moq" Version="4.6.36-*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
{
|
{
|
||||||
BenchmarkRunner.Run<KnownStrings>();
|
BenchmarkRunner.Run<KnownStrings>();
|
||||||
}
|
}
|
||||||
|
if (type.HasFlag(BenchmarkType.KestrelHttpParser))
|
||||||
|
{
|
||||||
|
BenchmarkRunner.Run<KestrelHttpParser>();
|
||||||
|
}
|
||||||
|
if (type.HasFlag(BenchmarkType.FrameParsingOverhead))
|
||||||
|
{
|
||||||
|
BenchmarkRunner.Run<FrameParsingOverhead>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +62,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
Writing = 2,
|
Writing = 2,
|
||||||
Throughput = 4,
|
Throughput = 4,
|
||||||
KnownStrings = 8,
|
KnownStrings = 8,
|
||||||
|
KestrelHttpParser = 16,
|
||||||
|
FrameParsingOverhead = 32,
|
||||||
// add new ones in powers of two - e.g. 2,4,8,16...
|
// add new ones in powers of two - e.g. 2,4,8,16...
|
||||||
|
|
||||||
All = uint.MaxValue
|
All = uint.MaxValue
|
||||||
|
|
|
||||||
|
|
@ -3,128 +3,101 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
|
|
||||||
using RequestLineStatus = Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.RequestLineStatus;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
{
|
{
|
||||||
[Config(typeof(CoreConfig))]
|
[Config(typeof(CoreConfig))]
|
||||||
public class RequestParsing
|
public class RequestParsing
|
||||||
{
|
{
|
||||||
private const int InnerLoopCount = 512;
|
[Params(typeof(Internal.Http.KestrelHttpParser))]
|
||||||
private const int Pipelining = 16;
|
|
||||||
|
|
||||||
private const string plaintextRequest = "GET /plaintext HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
|
|
||||||
|
|
||||||
private const string liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" +
|
|
||||||
"Host: live.asp.net\r\n" +
|
|
||||||
"Connection: keep-alive\r\n" +
|
|
||||||
"Upgrade-Insecure-Requests: 1\r\n" +
|
|
||||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" +
|
|
||||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
|
|
||||||
"DNT: 1\r\n" +
|
|
||||||
"Accept-Encoding: gzip, deflate, sdch, br\r\n" +
|
|
||||||
"Accept-Language: en-US,en;q=0.8\r\n" +
|
|
||||||
"Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n\r\n";
|
|
||||||
|
|
||||||
private const string unicodeRequest =
|
|
||||||
"GET http://stackoverflow.com/questions/40148683/why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric HTTP/1.1\r\n" +
|
|
||||||
"Accept: text/html, application/xhtml+xml, image/jxr, */*\r\n" +
|
|
||||||
"Accept-Language: en-US,en-GB;q=0.7,en;q=0.3\r\n" +
|
|
||||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.14965\r\n" +
|
|
||||||
"Accept-Encoding: gzip, deflate\r\n" +
|
|
||||||
"Host: stackoverflow.com\r\n" +
|
|
||||||
"Connection: Keep-Alive\r\n" +
|
|
||||||
"Cache-Control: max-age=0\r\n" +
|
|
||||||
"Upgrade-Insecure-Requests: 1\r\n" +
|
|
||||||
"DNT: 1\r\n" +
|
|
||||||
"Referer: http://stackoverflow.com/?tab=month\r\n" +
|
|
||||||
"Pragma: no-cache\r\n" +
|
|
||||||
"Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n\r\n";
|
|
||||||
|
|
||||||
private static readonly byte[] _plaintextPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(plaintextRequest, Pipelining)));
|
|
||||||
private static readonly byte[] _plaintextRequest = Encoding.ASCII.GetBytes(plaintextRequest);
|
|
||||||
|
|
||||||
private static readonly byte[] _liveaspnentPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(liveaspnetRequest, Pipelining)));
|
|
||||||
private static readonly byte[] _liveaspnentRequest = Encoding.ASCII.GetBytes(liveaspnetRequest);
|
|
||||||
|
|
||||||
private static readonly byte[] _unicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(unicodeRequest, Pipelining)));
|
|
||||||
private static readonly byte[] _unicodeRequest = Encoding.ASCII.GetBytes(unicodeRequest);
|
|
||||||
|
|
||||||
[Params(typeof(KestrelHttpParser))]
|
|
||||||
public Type ParserType { get; set; }
|
public Type ParserType { get; set; }
|
||||||
|
|
||||||
[Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)]
|
public IPipe Pipe { get; set; }
|
||||||
public void ParsePlaintext()
|
|
||||||
|
public Frame<object> Frame { get; set; }
|
||||||
|
|
||||||
|
public PipeFactory PipelineFactory { get; set; }
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
public void Setup()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
var connectionContext = new MockConnection(new KestrelServerOptions());
|
||||||
|
connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => (IHttpParser)Activator.CreateInstance(ParserType, frame.ConnectionContext.ListenerContext.ServiceContext.Log);
|
||||||
|
|
||||||
|
Frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
PipelineFactory = new PipeFactory();
|
||||||
|
Pipe = PipelineFactory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
|
public void PlaintextTechEmpower()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_plaintextRequest);
|
InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
|
||||||
public void ParsePipelinedPlaintext()
|
public void PipelinedPlaintextTechEmpower()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_plaintextPipelinedRequests);
|
InsertData(RequestParsingData.PlaintextTechEmpowerPipelinedRequests);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
public void ParseLiveAspNet()
|
public void LiveAspNet()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_liveaspnentRequest);
|
InsertData(RequestParsingData.LiveaspnetRequest);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
|
||||||
public void ParsePipelinedLiveAspNet()
|
public void PipelinedLiveAspNet()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_liveaspnentPipelinedRequests);
|
InsertData(RequestParsingData.LiveaspnetPipelinedRequests);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||||
public void ParseUnicode()
|
public void Unicode()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_unicodeRequest);
|
InsertData(RequestParsingData.UnicodeRequest);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = InnerLoopCount * Pipelining)]
|
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount * RequestParsingData.Pipelining)]
|
||||||
public void ParseUnicodePipelined()
|
public void UnicodePipelined()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InnerLoopCount; i++)
|
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||||
{
|
{
|
||||||
InsertData(_unicodePipelinedRequests);
|
InsertData(RequestParsingData.UnicodePipelinedRequests);
|
||||||
ParseData();
|
ParseData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InsertData(byte[] bytes)
|
private void InsertData(byte[] bytes)
|
||||||
{
|
{
|
||||||
|
var buffer = Pipe.Writer.Alloc(2048);
|
||||||
|
buffer.WriteFast(bytes);
|
||||||
// There should not be any backpressure and task completes immediately
|
// There should not be any backpressure and task completes immediately
|
||||||
Pipe.Writer.WriteAsync(bytes).GetAwaiter().GetResult();
|
buffer.FlushAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseData()
|
private void ParseData()
|
||||||
|
|
@ -143,11 +116,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
||||||
Frame.Reset();
|
Frame.Reset();
|
||||||
|
|
||||||
ReadCursor consumed;
|
if (!Frame.TakeStartLine(readableBuffer, out var consumed, out var examined))
|
||||||
ReadCursor examined;
|
|
||||||
if (!Frame.TakeStartLine(readableBuffer, out consumed, out examined))
|
|
||||||
{
|
{
|
||||||
ThrowInvalidStartLine();
|
ThrowInvalidRequestLine();
|
||||||
}
|
}
|
||||||
Pipe.Reader.Advance(consumed, examined);
|
Pipe.Reader.Advance(consumed, examined);
|
||||||
|
|
||||||
|
|
@ -156,40 +127,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
||||||
Frame.InitializeHeaders();
|
Frame.InitializeHeaders();
|
||||||
|
|
||||||
if (!Frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)Frame.RequestHeaders, out consumed, out examined))
|
if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined))
|
||||||
{
|
{
|
||||||
ThrowInvalidMessageHeaders();
|
ThrowInvalidRequestHeaders();
|
||||||
}
|
}
|
||||||
Pipe.Reader.Advance(consumed, examined);
|
Pipe.Reader.Advance(consumed, examined);
|
||||||
}
|
}
|
||||||
while (true);
|
while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThrowInvalidStartLine()
|
public static void ThrowInvalidRequestLine()
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid StartLine");
|
throw new InvalidOperationException("Invalid request line");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThrowInvalidMessageHeaders()
|
public static void ThrowInvalidRequestHeaders()
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid MessageHeaders");
|
throw new InvalidOperationException("Invalid request headers");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Setup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var connectionContext = new MockConnection(new KestrelServerOptions());
|
|
||||||
connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => (IHttpParser)Activator.CreateInstance(ParserType, frame.ConnectionContext.ListenerContext.ServiceContext.Log);
|
|
||||||
|
|
||||||
Frame = new Frame<object>(application: null, context: connectionContext);
|
|
||||||
PipelineFactory = new PipeFactory();
|
|
||||||
Pipe = PipelineFactory.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPipe Pipe { get; set; }
|
|
||||||
|
|
||||||
public Frame<object> Frame { get; set; }
|
|
||||||
|
|
||||||
public PipeFactory PipelineFactory { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
[Config(typeof(CoreConfig))]
|
||||||
|
public class RequestParsingData
|
||||||
|
{
|
||||||
|
public const int InnerLoopCount = 512;
|
||||||
|
|
||||||
|
public const int Pipelining = 16;
|
||||||
|
|
||||||
|
private const string _plaintextTechEmpowerRequest =
|
||||||
|
"GET /plaintext HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7\r\n" +
|
||||||
|
"Connection: keep-alive\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
private const string _liveaspnetRequest =
|
||||||
|
"GET https://live.asp.net/ HTTP/1.1\r\n" +
|
||||||
|
"Host: live.asp.net\r\n" +
|
||||||
|
"Connection: keep-alive\r\n" +
|
||||||
|
"Upgrade-Insecure-Requests: 1\r\n" +
|
||||||
|
"User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" +
|
||||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
|
||||||
|
"DNT: 1\r\n" +
|
||||||
|
"Accept-Encoding: gzip, deflate, sdch, br\r\n" +
|
||||||
|
"Accept-Language: en-US,en;q=0.8\r\n" +
|
||||||
|
"Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
private const string _unicodeRequest =
|
||||||
|
"GET http://stackoverflow.com/questions/40148683/why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric HTTP/1.1\r\n" +
|
||||||
|
"Accept: text/html, application/xhtml+xml, image/jxr, */*\r\n" +
|
||||||
|
"Accept-Language: en-US,en-GB;q=0.7,en;q=0.3\r\n" +
|
||||||
|
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.14965\r\n" +
|
||||||
|
"Accept-Encoding: gzip, deflate\r\n" +
|
||||||
|
"Host: stackoverflow.com\r\n" +
|
||||||
|
"Connection: Keep-Alive\r\n" +
|
||||||
|
"Cache-Control: max-age=0\r\n" +
|
||||||
|
"Upgrade-Insecure-Requests: 1\r\n" +
|
||||||
|
"DNT: 1\r\n" +
|
||||||
|
"Referer: http://stackoverflow.com/?tab=month\r\n" +
|
||||||
|
"Pragma: no-cache\r\n" +
|
||||||
|
"Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
public static readonly byte[] PlaintextTechEmpowerPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_plaintextTechEmpowerRequest, Pipelining)));
|
||||||
|
public static readonly byte[] PlaintextTechEmpowerRequest = Encoding.ASCII.GetBytes(_plaintextTechEmpowerRequest);
|
||||||
|
|
||||||
|
public static readonly byte[] LiveaspnetPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_liveaspnetRequest, Pipelining)));
|
||||||
|
public static readonly byte[] LiveaspnetRequest = Encoding.ASCII.GetBytes(_liveaspnetRequest);
|
||||||
|
|
||||||
|
public static readonly byte[] UnicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(_unicodeRequest, Pipelining)));
|
||||||
|
public static readonly byte[] UnicodeRequest = Encoding.ASCII.GetBytes(_unicodeRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
internal static class WritableBufferExtensions
|
||||||
|
{
|
||||||
|
public static void WriteFast(this WritableBuffer buffer, ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
if (buffer.Memory.IsEmpty)
|
||||||
|
{
|
||||||
|
buffer.Ensure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path, try copying to the available memory directly
|
||||||
|
if (source.Length <= buffer.Memory.Length)
|
||||||
|
{
|
||||||
|
source.CopyToFast(buffer.Memory.Span);
|
||||||
|
buffer.Advance(source.Length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remaining = source.Length;
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
var writable = Math.Min(remaining, buffer.Memory.Length);
|
||||||
|
|
||||||
|
buffer.Ensure(writable);
|
||||||
|
|
||||||
|
if (writable == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.Slice(offset, writable).CopyToFast(buffer.Memory.Span);
|
||||||
|
|
||||||
|
remaining -= writable;
|
||||||
|
offset += writable;
|
||||||
|
|
||||||
|
buffer.Advance(writable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe static void CopyToFast(this ReadOnlySpan<byte> source, Span<byte> destination)
|
||||||
|
{
|
||||||
|
if (destination.Length < source.Length)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume it fits
|
||||||
|
fixed (byte* pSource = &source.DangerousGetPinnableReference())
|
||||||
|
fixed (byte* pDest = &destination.DangerousGetPinnableReference())
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(pSource, pDest, destination.Length, source.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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 BenchmarkDotNet.Configs;
|
using BenchmarkDotNet.Configs;
|
||||||
|
using BenchmarkDotNet.Diagnosers;
|
||||||
using BenchmarkDotNet.Engines;
|
using BenchmarkDotNet.Engines;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using BenchmarkDotNet.Validators;
|
using BenchmarkDotNet.Validators;
|
||||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
public CoreConfig()
|
public CoreConfig()
|
||||||
{
|
{
|
||||||
Add(JitOptimizationsValidator.FailOnError);
|
Add(JitOptimizationsValidator.FailOnError);
|
||||||
|
Add(MemoryDiagnoser.Default);
|
||||||
Add(new RpsColumn());
|
Add(new RpsColumn());
|
||||||
|
|
||||||
Add(Job.Default
|
Add(Job.Default
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
|
|
@ -14,27 +15,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
private void FullAsciiRangeSupported()
|
private void FullAsciiRangeSupported()
|
||||||
{
|
{
|
||||||
var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
|
var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
|
||||||
using (var pool = new MemoryPool())
|
var s = new Span<byte>(byteRange).GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
|
Assert.Equal(s.Length, byteRange.Length);
|
||||||
|
|
||||||
|
for (var i = 1; i < byteRange.Length; i++)
|
||||||
{
|
{
|
||||||
var mem = pool.Lease();
|
var sb = (byte)s[i];
|
||||||
mem.GetIterator().CopyFrom(byteRange);
|
var b = byteRange[i];
|
||||||
|
|
||||||
var begin = mem.GetIterator();
|
Assert.Equal(sb, b);
|
||||||
var end = GetIterator(begin, byteRange.Length);
|
|
||||||
|
|
||||||
var s = begin.GetAsciiString(ref end);
|
|
||||||
|
|
||||||
Assert.Equal(s.Length, byteRange.Length);
|
|
||||||
|
|
||||||
for (var i = 1; i < byteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var sb = (byte)s[i];
|
|
||||||
var b = byteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(sb, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,122 +40,28 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray();
|
var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray();
|
||||||
byteRange[position] = b;
|
byteRange[position] = b;
|
||||||
|
|
||||||
using (var pool = new MemoryPool())
|
Assert.Throws<InvalidOperationException>(() => new Span<byte>(byteRange).GetAsciiStringNonNullCharacters());
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
mem.GetIterator().CopyFrom(byteRange);
|
|
||||||
|
|
||||||
var begin = mem.GetIterator();
|
|
||||||
var end = GetIterator(begin, byteRange.Length);
|
|
||||||
|
|
||||||
Assert.Throws<BadHttpRequestException>(() => begin.GetAsciiString(ref end));
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
private void MultiBlockProducesCorrectResults()
|
|
||||||
{
|
|
||||||
var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
|
|
||||||
var expectedByteRange = byteRange
|
|
||||||
.Concat(byteRange)
|
|
||||||
.Concat(byteRange)
|
|
||||||
.Concat(byteRange)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem0 = pool.Lease();
|
|
||||||
var mem1 = pool.Lease();
|
|
||||||
var mem2 = pool.Lease();
|
|
||||||
var mem3 = pool.Lease();
|
|
||||||
mem0.GetIterator().CopyFrom(byteRange);
|
|
||||||
mem1.GetIterator().CopyFrom(byteRange);
|
|
||||||
mem2.GetIterator().CopyFrom(byteRange);
|
|
||||||
mem3.GetIterator().CopyFrom(byteRange);
|
|
||||||
|
|
||||||
mem0.Next = mem1;
|
|
||||||
mem1.Next = mem2;
|
|
||||||
mem2.Next = mem3;
|
|
||||||
|
|
||||||
var begin = mem0.GetIterator();
|
|
||||||
var end = GetIterator(begin, expectedByteRange.Length);
|
|
||||||
|
|
||||||
var s = begin.GetAsciiString(ref end);
|
|
||||||
|
|
||||||
Assert.Equal(s.Length, expectedByteRange.Length);
|
|
||||||
|
|
||||||
for (var i = 0; i < expectedByteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var sb = (byte)((s[i] & 0x7f) | 0x01);
|
|
||||||
var b = expectedByteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(sb, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.Return(mem0);
|
|
||||||
pool.Return(mem1);
|
|
||||||
pool.Return(mem2);
|
|
||||||
pool.Return(mem3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
private void LargeAllocationProducesCorrectResults()
|
private void LargeAllocationProducesCorrectResults()
|
||||||
{
|
{
|
||||||
var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
|
var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
|
||||||
var expectedByteRange = byteRange.Concat(byteRange).ToArray();
|
var expectedByteRange = byteRange.Concat(byteRange).ToArray();
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
|
var s = new Span<byte>(expectedByteRange).GetAsciiStringNonNullCharacters();
|
||||||
|
|
||||||
|
Assert.Equal(expectedByteRange.Length, s.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < expectedByteRange.Length; i++)
|
||||||
{
|
{
|
||||||
var mem0 = pool.Lease();
|
var sb = (byte)((s[i] & 0x7f) | 0x01);
|
||||||
var mem1 = pool.Lease();
|
var b = expectedByteRange[i];
|
||||||
mem0.GetIterator().CopyFrom(byteRange);
|
|
||||||
mem1.GetIterator().CopyFrom(byteRange);
|
|
||||||
|
|
||||||
var lastBlock = mem0;
|
Assert.Equal(sb, b);
|
||||||
while (lastBlock.Next != null)
|
|
||||||
{
|
|
||||||
lastBlock = lastBlock.Next;
|
|
||||||
}
|
|
||||||
lastBlock.Next = mem1;
|
|
||||||
|
|
||||||
var begin = mem0.GetIterator();
|
|
||||||
var end = GetIterator(begin, expectedByteRange.Length);
|
|
||||||
|
|
||||||
var s = begin.GetAsciiString(ref end);
|
|
||||||
|
|
||||||
Assert.Equal(expectedByteRange.Length, s.Length);
|
|
||||||
|
|
||||||
for (var i = 0; i < expectedByteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var sb = (byte)((s[i] & 0x7f) | 0x01);
|
|
||||||
var b = expectedByteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(sb, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
var block = mem0;
|
|
||||||
while (block != null)
|
|
||||||
{
|
|
||||||
var returnBlock = block;
|
|
||||||
block = block.Next;
|
|
||||||
pool.Return(returnBlock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement)
|
|
||||||
{
|
|
||||||
var result = begin;
|
|
||||||
for (int i = 0; i < displacement; ++i)
|
|
||||||
{
|
|
||||||
result.Take();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -304,14 +304,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AppendThrowsWhenHeaderValueContainsNonASCIICharacters()
|
public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters()
|
||||||
{
|
{
|
||||||
var headers = new FrameRequestHeaders();
|
var headers = new FrameRequestHeaders();
|
||||||
const string key = "\u00141ód\017c";
|
const string key = "\u00141ód\017c";
|
||||||
|
|
||||||
var encoding = Encoding.GetEncoding("iso-8859-1");
|
var encoding = Encoding.GetEncoding("iso-8859-1");
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(
|
var exception = Assert.Throws<BadHttpRequestException>(
|
||||||
() => headers.Append(encoding.GetBytes(key), key));
|
() => headers.Append(encoding.GetBytes(key), "value"));
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class FrameTests : IDisposable
|
public class FrameTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IPipe _socketInput;
|
private readonly IPipe _input;
|
||||||
private readonly TestFrame<object> _frame;
|
private readonly TestFrame<object> _frame;
|
||||||
private readonly ServiceContext _serviceContext;
|
private readonly ServiceContext _serviceContext;
|
||||||
private readonly ConnectionContext _connectionContext;
|
private readonly ConnectionContext _connectionContext;
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
_pipelineFactory = new PipeFactory();
|
_pipelineFactory = new PipeFactory();
|
||||||
_socketInput = _pipelineFactory.Create();
|
_input = _pipelineFactory.Create();
|
||||||
|
|
||||||
_serviceContext = new ServiceContext
|
_serviceContext = new ServiceContext
|
||||||
{
|
{
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
};
|
};
|
||||||
_connectionContext = new ConnectionContext(listenerContext)
|
_connectionContext = new ConnectionContext(listenerContext)
|
||||||
{
|
{
|
||||||
Input = _socketInput,
|
Input = _input,
|
||||||
Output = new MockSocketOutput(),
|
Output = new MockSocketOutput(),
|
||||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||||
};
|
};
|
||||||
|
|
@ -76,130 +76,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_socketInput.Reader.Complete();
|
_input.Reader.Complete();
|
||||||
_socketInput.Writer.Complete();
|
_input.Writer.Complete();
|
||||||
_pipelineFactory.Dispose();
|
_pipelineFactory.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CanReadHeaderValueWithoutLeadingWhitespace()
|
|
||||||
{
|
|
||||||
_frame.InitializeHeaders();
|
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header:value\r\n\r\n"));
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.True(success);
|
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
|
||||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Header: value\r\n\r\n")]
|
|
||||||
[InlineData("Header: value\r\n\r\n")]
|
|
||||||
[InlineData("Header:\tvalue\r\n\r\n")]
|
|
||||||
[InlineData("Header: \tvalue\r\n\r\n")]
|
|
||||||
[InlineData("Header:\t value\r\n\r\n")]
|
|
||||||
[InlineData("Header:\t\tvalue\r\n\r\n")]
|
|
||||||
[InlineData("Header:\t\t value\r\n\r\n")]
|
|
||||||
[InlineData("Header: \t\tvalue\r\n\r\n")]
|
|
||||||
[InlineData("Header: \t\t value\r\n\r\n")]
|
|
||||||
[InlineData("Header: \t \t value\r\n\r\n")]
|
|
||||||
public async Task LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.True(success);
|
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
|
||||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Header: value \r\n\r\n")]
|
|
||||||
[InlineData("Header: value\t\r\n\r\n")]
|
|
||||||
[InlineData("Header: value \t\r\n\r\n")]
|
|
||||||
[InlineData("Header: value\t \r\n\r\n")]
|
|
||||||
[InlineData("Header: value\t\t\r\n\r\n")]
|
|
||||||
[InlineData("Header: value\t\t \r\n\r\n")]
|
|
||||||
[InlineData("Header: value \t\t\r\n\r\n")]
|
|
||||||
[InlineData("Header: value \t\t \r\n\r\n")]
|
|
||||||
[InlineData("Header: value \t \t \r\n\r\n")]
|
|
||||||
public async Task TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.True(success);
|
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
|
||||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Header: one two three\r\n\r\n", "one two three")]
|
|
||||||
[InlineData("Header: one two three\r\n\r\n", "one two three")]
|
|
||||||
[InlineData("Header: one\ttwo\tthree\r\n\r\n", "one\ttwo\tthree")]
|
|
||||||
[InlineData("Header: one two\tthree\r\n\r\n", "one two\tthree")]
|
|
||||||
[InlineData("Header: one\ttwo three\r\n\r\n", "one\ttwo three")]
|
|
||||||
[InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")]
|
|
||||||
[InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")]
|
|
||||||
[InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")]
|
|
||||||
public async Task WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.True(success);
|
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
|
||||||
Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]);
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(InvalidRequestHeaderData))]
|
|
||||||
public async Task TakeMessageHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
|
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
|
||||||
{
|
{
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
Assert.False(_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
|
Assert.False(_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
|
||||||
|
|
||||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal("Header value line folding not supported.", exception.Message);
|
Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,11 +107,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
|
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
|
||||||
_frame.Reset();
|
_frame.Reset();
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal("Request headers too long.", exception.Message);
|
Assert.Equal("Request headers too long.", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||||
|
|
@ -226,36 +123,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
|
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
|
||||||
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
|
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal("Request contains too many headers.", exception.Message);
|
Assert.Equal("Request contains too many headers.", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Cookie: \r\n\r\n", 1)]
|
|
||||||
[InlineData("Cookie:\r\n\r\n", 1)]
|
|
||||||
[InlineData("Cookie: \r\nConnection: close\r\n\r\n", 2)]
|
|
||||||
[InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)]
|
|
||||||
[InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)]
|
|
||||||
[InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)]
|
|
||||||
public async Task EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
|
|
||||||
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.True(success);
|
|
||||||
Assert.Equal(numHeaders, _frame.RequestHeaders.Count);
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ResetResetsScheme()
|
public void ResetResetsScheme()
|
||||||
{
|
{
|
||||||
|
|
@ -279,11 +156,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
options.Limits.MaxRequestHeaderCount = 1;
|
options.Limits.MaxRequestHeaderCount = 1;
|
||||||
_serviceContext.ServerOptions = options;
|
_serviceContext.ServerOptions = options;
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.True(takeMessageHeaders);
|
Assert.True(takeMessageHeaders);
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
Assert.Equal(1, _frame.RequestHeaders.Count);
|
||||||
|
|
@ -291,11 +168,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
_frame.Reset();
|
_frame.Reset();
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.True(takeMessageHeaders);
|
Assert.True(takeMessageHeaders);
|
||||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
Assert.Equal(1, _frame.RequestHeaders.Count);
|
||||||
|
|
@ -390,86 +267,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
public async Task TakeStartLineSetsFrameProperties(
|
public async Task TakeStartLineSetsFrameProperties(
|
||||||
string requestLine,
|
string requestLine,
|
||||||
string expectedMethod,
|
string expectedMethod,
|
||||||
string expectedPath,
|
string expectedRawTarget,
|
||||||
|
string expectedRawPath,
|
||||||
|
string expectedDecodedPath,
|
||||||
string expectedQueryString,
|
string expectedQueryString,
|
||||||
string expectedHttpVersion)
|
string expectedHttpVersion)
|
||||||
{
|
{
|
||||||
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
||||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
await _input.Writer.WriteAsync(requestLineBytes);
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.True(returnValue);
|
Assert.True(returnValue);
|
||||||
Assert.Equal(expectedMethod, _frame.Method);
|
Assert.Equal(expectedMethod, _frame.Method);
|
||||||
Assert.Equal(expectedPath, _frame.Path);
|
Assert.Equal(expectedRawTarget, _frame.RawTarget);
|
||||||
|
Assert.Equal(expectedDecodedPath, _frame.Path);
|
||||||
Assert.Equal(expectedQueryString, _frame.QueryString);
|
Assert.Equal(expectedQueryString, _frame.QueryString);
|
||||||
Assert.Equal(expectedHttpVersion, _frame.HttpVersion);
|
Assert.Equal(expectedHttpVersion, _frame.HttpVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
|
public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||||
{
|
|
||||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET / ");
|
|
||||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
|
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.Equal(readableBuffer.Start, _consumed);
|
|
||||||
Assert.Equal(readableBuffer.End, _examined);
|
|
||||||
|
|
||||||
requestLineBytes = Encoding.ASCII.GetBytes("HTTP/1.1\r\n");
|
|
||||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
|
||||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
|
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.Equal(readableBuffer.End, _consumed);
|
|
||||||
Assert.Equal(readableBuffer.End, _examined);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("G")]
|
|
||||||
[InlineData("GE")]
|
|
||||||
[InlineData("GET")]
|
|
||||||
[InlineData("GET ")]
|
|
||||||
[InlineData("GET /")]
|
|
||||||
[InlineData("GET / ")]
|
|
||||||
[InlineData("GET / H")]
|
|
||||||
[InlineData("GET / HT")]
|
|
||||||
[InlineData("GET / HTT")]
|
|
||||||
[InlineData("GET / HTTP")]
|
|
||||||
[InlineData("GET / HTTP/")]
|
|
||||||
[InlineData("GET / HTTP/1")]
|
|
||||||
[InlineData("GET / HTTP/1.")]
|
|
||||||
[InlineData("GET / HTTP/1.1")]
|
|
||||||
[InlineData("GET / HTTP/1.1\r")]
|
|
||||||
public async Task TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine)
|
|
||||||
{
|
|
||||||
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
|
||||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.False(returnValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
|
||||||
{
|
{
|
||||||
var connectionControl = new Mock<IConnectionControl>();
|
var connectionControl = new Mock<IConnectionControl>();
|
||||||
_connectionContext.ConnectionControl = connectionControl.Object;
|
_connectionContext.ConnectionControl = connectionControl.Object;
|
||||||
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||||
|
|
||||||
_frame.TakeStartLine((await _socketInput.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
_frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||||
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||||
|
|
@ -481,93 +309,42 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
|
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
|
||||||
|
|
||||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
|
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
|
||||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
await _input.Writer.WriteAsync(requestLineBytes);
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal("Request line too long.", exception.Message);
|
Assert.Equal("Request line too long.", exception.Message);
|
||||||
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
|
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(InvalidRequestLineData))]
|
[MemberData(nameof(RequestLineWithEncodedNullCharInTargetData))]
|
||||||
public async Task TakeStartLineThrowsOnInvalidRequestLine(string requestLine, Type expectedExceptionType, string expectedExceptionMessage)
|
public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string requestLine)
|
||||||
{
|
{
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var exception = Assert.Throws(expectedExceptionType, () =>
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
Assert.Equal("The path contains null characters.", exception.Message);
|
||||||
|
|
||||||
if (expectedExceptionType == typeof(BadHttpRequestException))
|
|
||||||
{
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
[MemberData(nameof(RequestLineWithNullCharInTargetData))]
|
||||||
public async Task TakeStartLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
|
public async Task TakeStartLineThrowsOnNullCharInTarget(string requestLine)
|
||||||
{
|
{
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET / {httpVersion}\r\n"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||||
|
_input.Reader.Advance(_consumed, _examined);
|
||||||
|
|
||||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
Assert.Equal(new InvalidOperationException().Message, exception.Message);
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
|
|
||||||
Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
|
|
||||||
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
|
|
||||||
{
|
|
||||||
foreach (var rawHeader in new[] { "Header: ", "value\r\n", "\r\n" })
|
|
||||||
{
|
|
||||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
|
|
||||||
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
|
|
||||||
_socketInput.Reader.Advance(_consumed, _examined);
|
|
||||||
Assert.Equal(readableBuffer.End, _examined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("\r")]
|
|
||||||
[InlineData("H")]
|
|
||||||
[InlineData("He")]
|
|
||||||
[InlineData("Hea")]
|
|
||||||
[InlineData("Head")]
|
|
||||||
[InlineData("Heade")]
|
|
||||||
[InlineData("Header")]
|
|
||||||
[InlineData("Header:")]
|
|
||||||
[InlineData("Header: ")]
|
|
||||||
[InlineData("Header: v")]
|
|
||||||
[InlineData("Header: va")]
|
|
||||||
[InlineData("Header: val")]
|
|
||||||
[InlineData("Header: valu")]
|
|
||||||
[InlineData("Header: value")]
|
|
||||||
[InlineData("Header: value\r")]
|
|
||||||
[InlineData("Header: value\r\n")]
|
|
||||||
[InlineData("Header: value\r\n\r")]
|
|
||||||
public async Task TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers)
|
|
||||||
{
|
|
||||||
var headerBytes = Encoding.ASCII.GetBytes(headers);
|
|
||||||
await _socketInput.Writer.WriteAsync(headerBytes);
|
|
||||||
|
|
||||||
ReadCursor consumed;
|
|
||||||
ReadCursor examined;
|
|
||||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
|
||||||
|
|
||||||
Assert.Equal(false, _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
|
|
||||||
_socketInput.Reader.Advance(consumed, examined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -582,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||||
|
|
||||||
_frame.StopAsync();
|
_frame.StopAsync();
|
||||||
_socketInput.Writer.Complete();
|
_input.Writer.Complete();
|
||||||
|
|
||||||
requestProcessingTask.Wait();
|
requestProcessingTask.Wait();
|
||||||
}
|
}
|
||||||
|
|
@ -664,13 +441,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
_frame.Start();
|
_frame.Start();
|
||||||
|
|
||||||
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
|
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
|
||||||
await _socketInput.Writer.WriteAsync(data);
|
await _input.Writer.WriteAsync(data);
|
||||||
|
|
||||||
var requestProcessingTask = _frame.StopAsync();
|
var requestProcessingTask = _frame.StopAsync();
|
||||||
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
|
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
|
||||||
|
|
||||||
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||||
_socketInput.Writer.Complete();
|
_input.Writer.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -741,12 +518,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
|
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.ValidRequestLineData;
|
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.RequestLineValidData;
|
||||||
|
|
||||||
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData;
|
public static TheoryData<string> RequestLineWithEncodedNullCharInTargetData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
||||||
|
{
|
||||||
|
data.Add(requestLine);
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData;
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<string> RequestLineWithNullCharInTargetData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = new TheoryData<string>();
|
||||||
|
|
||||||
|
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
||||||
|
{
|
||||||
|
data.Add(requestLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public class HttpParserTests
|
||||||
|
{
|
||||||
|
// Returns true when all headers parsed
|
||||||
|
// Return false otherwise
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestLineValidData))]
|
||||||
|
public void ParsesRequestLine(
|
||||||
|
string requestLine,
|
||||||
|
string expectedMethod,
|
||||||
|
string expectedRawTarget,
|
||||||
|
string expectedRawPath,
|
||||||
|
string expectedDecodedPath,
|
||||||
|
string expectedQueryString,
|
||||||
|
string expectedVersion)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
string parsedMethod = null;
|
||||||
|
string parsedVersion = null;
|
||||||
|
string parsedRawTarget = null;
|
||||||
|
string parsedRawPath = null;
|
||||||
|
string parsedQuery = null;
|
||||||
|
var requestLineHandler = new Mock<IHttpRequestLineHandler>();
|
||||||
|
requestLineHandler
|
||||||
|
.Setup(handler => handler.OnStartLine(
|
||||||
|
It.IsAny<HttpMethod>(),
|
||||||
|
It.IsAny<HttpVersion>(),
|
||||||
|
It.IsAny<Span<byte>>(),
|
||||||
|
It.IsAny<Span<byte>>(),
|
||||||
|
It.IsAny<Span<byte>>(),
|
||||||
|
It.IsAny<Span<byte>>()))
|
||||||
|
.Callback<HttpMethod, HttpVersion, Span<byte>, Span<byte>, Span<byte>, Span<byte>>((method, version, target, path, query, customMethod) =>
|
||||||
|
{
|
||||||
|
parsedMethod = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
|
||||||
|
parsedVersion = HttpUtilities.VersionToString(version);
|
||||||
|
parsedRawTarget = target.GetAsciiStringNonNullCharacters();
|
||||||
|
parsedRawPath = path.GetAsciiStringNonNullCharacters();
|
||||||
|
parsedQuery = query.GetAsciiStringNonNullCharacters();
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.True(parser.ParseRequestLine(requestLineHandler.Object, buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal(parsedMethod, expectedMethod);
|
||||||
|
Assert.Equal(parsedVersion, expectedVersion);
|
||||||
|
Assert.Equal(parsedRawTarget, expectedRawTarget);
|
||||||
|
Assert.Equal(parsedRawPath, expectedRawPath);
|
||||||
|
Assert.Equal(parsedVersion, expectedVersion);
|
||||||
|
Assert.Equal(buffer.End, consumed);
|
||||||
|
Assert.Equal(buffer.End, examined);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestLineIncompleteData))]
|
||||||
|
public void ParseRequestLineReturnsFalseWhenGivenIncompleteRequestLines(string requestLine)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
Assert.False(parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestLineIncompleteData))]
|
||||||
|
public void ParseRequestLineDoesNotConsumeIncompleteRequestLine(string requestLine)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
Assert.False(parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal(buffer.Start, consumed);
|
||||||
|
Assert.Equal(buffer.End, examined);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestLineInvalidData))]
|
||||||
|
public void ParseRequestLineThrowsOnInvalidRequestLine(string requestLine)
|
||||||
|
{
|
||||||
|
var mockTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var parser = CreateParser(mockTrace.Object);
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal($"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
||||||
|
public void ParseRequestLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
|
||||||
|
{
|
||||||
|
var requestLine = $"GET / {httpVersion}\r\n";
|
||||||
|
|
||||||
|
var mockTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockTrace
|
||||||
|
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var parser = CreateParser(mockTrace.Object);
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||||
|
|
||||||
|
Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("\r")]
|
||||||
|
[InlineData("H")]
|
||||||
|
[InlineData("He")]
|
||||||
|
[InlineData("Hea")]
|
||||||
|
[InlineData("Head")]
|
||||||
|
[InlineData("Heade")]
|
||||||
|
[InlineData("Header")]
|
||||||
|
[InlineData("Header:")]
|
||||||
|
[InlineData("Header: ")]
|
||||||
|
[InlineData("Header: v")]
|
||||||
|
[InlineData("Header: va")]
|
||||||
|
[InlineData("Header: val")]
|
||||||
|
[InlineData("Header: valu")]
|
||||||
|
[InlineData("Header: value")]
|
||||||
|
[InlineData("Header: value\r")]
|
||||||
|
[InlineData("Header: value\r\n")]
|
||||||
|
[InlineData("Header: value\r\n\r")]
|
||||||
|
[InlineData("Header-1: value1\r\nH")]
|
||||||
|
[InlineData("Header-1: value1\r\nHe")]
|
||||||
|
[InlineData("Header-1: value1\r\nHea")]
|
||||||
|
[InlineData("Header-1: value1\r\nHead")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeade")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2:")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: ")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: v")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: va")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: val")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: valu")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: value")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: value2")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: value2\r")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n")]
|
||||||
|
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r")]
|
||||||
|
public void ParseHeadersReturnsFalseWhenGivenIncompleteHeaders(string rawHeaders)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||||
|
Assert.False(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("\r")]
|
||||||
|
[InlineData("H")]
|
||||||
|
[InlineData("He")]
|
||||||
|
[InlineData("Hea")]
|
||||||
|
[InlineData("Head")]
|
||||||
|
[InlineData("Heade")]
|
||||||
|
[InlineData("Header")]
|
||||||
|
[InlineData("Header:")]
|
||||||
|
[InlineData("Header: ")]
|
||||||
|
[InlineData("Header: v")]
|
||||||
|
[InlineData("Header: va")]
|
||||||
|
[InlineData("Header: val")]
|
||||||
|
[InlineData("Header: valu")]
|
||||||
|
[InlineData("Header: value")]
|
||||||
|
[InlineData("Header: value\r")]
|
||||||
|
public void ParseHeadersDoesNotConsumeIncompleteHeader(string rawHeaders)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||||
|
parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes);
|
||||||
|
|
||||||
|
Assert.Equal(buffer.Start, consumed);
|
||||||
|
Assert.Equal(buffer.End, examined);
|
||||||
|
Assert.Equal(0, consumedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParseHeadersCanReadHeaderValueWithoutLeadingWhitespace()
|
||||||
|
{
|
||||||
|
VerifyHeader("Header", "value", "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Cookie: \r\n\r\n", "Cookie", "", null, null)]
|
||||||
|
[InlineData("Cookie:\r\n\r\n", "Cookie", "", null, null)]
|
||||||
|
[InlineData("Cookie: \r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
|
||||||
|
[InlineData("Cookie:\r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
|
||||||
|
[InlineData("Connection: close\r\nCookie: \r\n\r\n", "Connection", "close", "Cookie", "")]
|
||||||
|
[InlineData("Connection: close\r\nCookie:\r\n\r\n", "Connection", "close", "Cookie", "")]
|
||||||
|
public void ParseHeadersCanParseEmptyHeaderValues(
|
||||||
|
string rawHeaders,
|
||||||
|
string expectedHeaderName1,
|
||||||
|
string expectedHeaderValue1,
|
||||||
|
string expectedHeaderName2,
|
||||||
|
string expectedHeaderValue2)
|
||||||
|
{
|
||||||
|
var expectedHeaderNames = expectedHeaderName2 == null
|
||||||
|
? new[] { expectedHeaderName1 }
|
||||||
|
: new[] { expectedHeaderName1, expectedHeaderName2 };
|
||||||
|
var expectedHeaderValues = expectedHeaderValue2 == null
|
||||||
|
? new[] { expectedHeaderValue1 }
|
||||||
|
: new[] { expectedHeaderValue1, expectedHeaderValue2 };
|
||||||
|
|
||||||
|
VerifyRawHeaders(rawHeaders, expectedHeaderNames, expectedHeaderValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(" value")]
|
||||||
|
[InlineData(" value")]
|
||||||
|
[InlineData("\tvalue")]
|
||||||
|
[InlineData(" \tvalue")]
|
||||||
|
[InlineData("\t value")]
|
||||||
|
[InlineData("\t\tvalue")]
|
||||||
|
[InlineData("\t\t value")]
|
||||||
|
[InlineData(" \t\tvalue")]
|
||||||
|
[InlineData(" \t\t value")]
|
||||||
|
[InlineData(" \t \t value")]
|
||||||
|
public void ParseHeadersDoesNotIncludeLeadingWhitespaceInHeaderValue(string rawHeaderValue)
|
||||||
|
{
|
||||||
|
VerifyHeader("Header", rawHeaderValue, "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("value ")]
|
||||||
|
[InlineData("value\t")]
|
||||||
|
[InlineData("value \t")]
|
||||||
|
[InlineData("value\t ")]
|
||||||
|
[InlineData("value\t\t")]
|
||||||
|
[InlineData("value\t\t ")]
|
||||||
|
[InlineData("value \t\t")]
|
||||||
|
[InlineData("value \t\t ")]
|
||||||
|
[InlineData("value \t \t ")]
|
||||||
|
public void ParseHeadersDoesNotIncludeTrailingWhitespaceInHeaderValue(string rawHeaderValue)
|
||||||
|
{
|
||||||
|
VerifyHeader("Header", rawHeaderValue, "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("one two three")]
|
||||||
|
[InlineData("one two three")]
|
||||||
|
[InlineData("one\ttwo\tthree")]
|
||||||
|
[InlineData("one two\tthree")]
|
||||||
|
[InlineData("one\ttwo three")]
|
||||||
|
[InlineData("one \ttwo \tthree")]
|
||||||
|
[InlineData("one\t two\t three")]
|
||||||
|
[InlineData("one \ttwo\t three")]
|
||||||
|
public void ParseHeadersPreservesWhitespaceWithinHeaderValue(string headerValue)
|
||||||
|
{
|
||||||
|
VerifyHeader("Header", headerValue, headerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParseHeadersConsumesBytesCorrectlyAtEnd()
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
|
||||||
|
const string headerLine = "Header: value\r\n\r";
|
||||||
|
var buffer1 = ReadableBuffer.Create(Encoding.ASCII.GetBytes(headerLine));
|
||||||
|
Assert.False(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer1, out var consumed, out var examined, out var consumedBytes));
|
||||||
|
|
||||||
|
Assert.Equal(buffer1.Move(buffer1.Start, headerLine.Length - 1), consumed);
|
||||||
|
Assert.Equal(buffer1.End, examined);
|
||||||
|
Assert.Equal(headerLine.Length - 1, consumedBytes);
|
||||||
|
|
||||||
|
var buffer2 = ReadableBuffer.Create(Encoding.ASCII.GetBytes("\r\n"));
|
||||||
|
Assert.True(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer2, out consumed, out examined, out consumedBytes));
|
||||||
|
|
||||||
|
Assert.Equal(buffer2.End, consumed);
|
||||||
|
Assert.Equal(buffer2.End, examined);
|
||||||
|
Assert.Equal(2, consumedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestHeaderInvalidData))]
|
||||||
|
public void ParseHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||||
|
|
||||||
|
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
|
parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes));
|
||||||
|
|
||||||
|
Assert.Equal(expectedExceptionMessage, exception.Message);
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyHeader(
|
||||||
|
string headerName,
|
||||||
|
string rawHeaderValue,
|
||||||
|
string expectedHeaderValue)
|
||||||
|
{
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes($"{headerName}:{rawHeaderValue}\r\n"));
|
||||||
|
|
||||||
|
string parsedHeaderName = "unexpected";
|
||||||
|
string parsedHeaderValue = "unexpected";
|
||||||
|
var headersHandler = new Mock<IHttpHeadersHandler>();
|
||||||
|
headersHandler
|
||||||
|
.Setup(handler => handler.OnHeader(It.IsAny<Span<byte>>(), It.IsAny<Span<byte>>()))
|
||||||
|
.Callback<Span<byte>, Span<byte>>((name, value) =>
|
||||||
|
{
|
||||||
|
parsedHeaderName = name.GetAsciiStringNonNullCharacters();
|
||||||
|
parsedHeaderValue = value.GetAsciiStringNonNullCharacters();
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
|
||||||
|
|
||||||
|
Assert.Equal(headerName, parsedHeaderName);
|
||||||
|
Assert.Equal(expectedHeaderValue, parsedHeaderValue);
|
||||||
|
Assert.Equal(buffer.End, consumed);
|
||||||
|
Assert.Equal(buffer.End, examined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyRawHeaders(string rawHeaders, IEnumerable<string> expectedHeaderNames, IEnumerable<string> expectedHeaderValues)
|
||||||
|
{
|
||||||
|
Assert.True(expectedHeaderNames.Count() == expectedHeaderValues.Count(), $"{nameof(expectedHeaderNames)} and {nameof(expectedHeaderValues)} sizes must match");
|
||||||
|
|
||||||
|
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||||
|
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||||
|
|
||||||
|
var parsedHeaders = new List<Tuple<string, string>>();
|
||||||
|
var headersHandler = new Mock<IHttpHeadersHandler>();
|
||||||
|
headersHandler
|
||||||
|
.Setup(handler => handler.OnHeader(It.IsAny<Span<byte>>(), It.IsAny<Span<byte>>()))
|
||||||
|
.Callback<Span<byte>, Span<byte>>((name, value) =>
|
||||||
|
{
|
||||||
|
parsedHeaders.Add(Tuple.Create(name.GetAsciiStringNonNullCharacters(), value.GetAsciiStringNonNullCharacters()));
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
|
||||||
|
|
||||||
|
Assert.Equal(expectedHeaderNames.Count(), parsedHeaders.Count);
|
||||||
|
Assert.Equal(expectedHeaderNames, parsedHeaders.Select(t => t.Item1));
|
||||||
|
Assert.Equal(expectedHeaderValues, parsedHeaders.Select(t => t.Item2));
|
||||||
|
Assert.Equal(buffer.End, consumed);
|
||||||
|
Assert.Equal(buffer.End, examined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IHttpParser CreateParser(IKestrelTrace log) => new KestrelHttpParser(log);
|
||||||
|
|
||||||
|
public static IEnumerable<string[]> RequestLineValidData => HttpParsingData.RequestLineValidData;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RequestLineIncompleteData => HttpParsingData.RequestLineIncompleteData.Select(requestLine => new[] { requestLine });
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RequestLineInvalidData => HttpParsingData.RequestLineInvalidData.Select(requestLine => new[] { requestLine });
|
||||||
|
|
||||||
|
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RequestHeaderInvalidData => HttpParsingData.RequestHeaderInvalidData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,104 +8,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class MemoryPoolBlockTests
|
public class MemoryPoolBlockTests
|
||||||
{
|
{
|
||||||
[Fact]
|
|
||||||
public void SeekWorks()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var block = pool.Lease();
|
|
||||||
foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
block.Array[block.End++] = ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iterator = block.GetIterator();
|
|
||||||
foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
var hit = iterator;
|
|
||||||
hit.Seek(ch);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(ch, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(byte.MaxValue, ch);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(ch, byte.MaxValue, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(byte.MaxValue, ch, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(ch, byte.MaxValue, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.Return(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SeekWorksAcrossBlocks()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var block1 = pool.Lease();
|
|
||||||
var block2 = block1.Next = pool.Lease();
|
|
||||||
var block3 = block2.Next = pool.Lease();
|
|
||||||
|
|
||||||
foreach (var ch in Enumerable.Range(0, 34).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
block1.Array[block1.End++] = ch;
|
|
||||||
}
|
|
||||||
foreach (var ch in Enumerable.Range(34, 25).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
block2.Array[block2.End++] = ch;
|
|
||||||
}
|
|
||||||
foreach (var ch in Enumerable.Range(59, 197).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
block3.Array[block3.End++] = ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iterator = block1.GetIterator();
|
|
||||||
foreach (var ch in Enumerable.Range(0, 256).Select(x => (byte)x))
|
|
||||||
{
|
|
||||||
var hit = iterator;
|
|
||||||
hit.Seek(ch);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(ch, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(byte.MaxValue, ch);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(ch, byte.MaxValue, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(byte.MaxValue, ch, byte.MaxValue);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
|
|
||||||
hit = iterator;
|
|
||||||
hit.Seek(byte.MaxValue, byte.MaxValue, ch);
|
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.Return(block1);
|
|
||||||
pool.Return(block2);
|
|
||||||
pool.Return(block3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetLengthBetweenIteratorsWorks()
|
public void GetLengthBetweenIteratorsWorks()
|
||||||
|
|
@ -193,86 +95,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CopyToCorrectlyTraversesBlocks()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var block1 = pool.Lease();
|
|
||||||
var block2 = block1.Next = pool.Lease();
|
|
||||||
|
|
||||||
for (int i = 0; i < 128; i++)
|
|
||||||
{
|
|
||||||
block1.Array[block1.End++] = (byte)i;
|
|
||||||
}
|
|
||||||
for (int i = 128; i < 256; i++)
|
|
||||||
{
|
|
||||||
block2.Array[block2.End++] = (byte)i;
|
|
||||||
}
|
|
||||||
|
|
||||||
var beginIterator = block1.GetIterator();
|
|
||||||
|
|
||||||
var array = new byte[256];
|
|
||||||
int actual;
|
|
||||||
var endIterator = beginIterator.CopyTo(array, 0, 256, out actual);
|
|
||||||
|
|
||||||
Assert.Equal(256, actual);
|
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
Assert.Equal(i, array[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
endIterator.CopyTo(array, 0, 256, out actual);
|
|
||||||
Assert.Equal(0, actual);
|
|
||||||
|
|
||||||
pool.Return(block1);
|
|
||||||
pool.Return(block2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CopyFromCorrectlyTraversesBlocks()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var block1 = pool.Lease();
|
|
||||||
var start = block1.GetIterator();
|
|
||||||
var end = start;
|
|
||||||
var bufferSize = block1.Data.Count * 3;
|
|
||||||
var buffer = new byte[bufferSize];
|
|
||||||
|
|
||||||
for (int i = 0; i < bufferSize; i++)
|
|
||||||
{
|
|
||||||
buffer[i] = (byte)(i % 73);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Null(block1.Next);
|
|
||||||
|
|
||||||
end.CopyFrom(new ArraySegment<byte>(buffer));
|
|
||||||
|
|
||||||
Assert.NotNull(block1.Next);
|
|
||||||
|
|
||||||
for (int i = 0; i < bufferSize; i++)
|
|
||||||
{
|
|
||||||
Assert.Equal(i % 73, start.Take());
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(-1, start.Take());
|
|
||||||
Assert.Equal(start.Block, end.Block);
|
|
||||||
Assert.Equal(start.Index, end.Index);
|
|
||||||
|
|
||||||
var block = block1;
|
|
||||||
while (block != null)
|
|
||||||
{
|
|
||||||
var returnBlock = block;
|
|
||||||
block = block.Next;
|
|
||||||
|
|
||||||
pool.Return(returnBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsEndCorrectlyTraversesBlocks()
|
public void IsEndCorrectlyTraversesBlocks()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public static MemoryPoolIterator Add(this MemoryPoolIterator iterator, int count)
|
public static MemoryPoolIterator Add(this MemoryPoolIterator iterator, int count)
|
||||||
{
|
{
|
||||||
int actual;
|
for (int i = 0; i < count; i++)
|
||||||
return iterator.CopyTo(new byte[count], 0, count, out actual);
|
{
|
||||||
|
iterator.Take();
|
||||||
|
}
|
||||||
|
return iterator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,80 +30,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
_pool.Dispose();
|
_pool.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestFindFirstEqualByte()
|
|
||||||
{
|
|
||||||
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
|
|
||||||
for (int i = 0; i < Vector<byte>.Count; i++)
|
|
||||||
{
|
|
||||||
Vector<byte> vector = new Vector<byte>(bytes);
|
|
||||||
Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector));
|
|
||||||
bytes[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < Vector<byte>.Count; i++)
|
|
||||||
{
|
|
||||||
bytes[i] = 1;
|
|
||||||
Vector<byte> vector = new Vector<byte>(bytes);
|
|
||||||
Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(vector));
|
|
||||||
bytes[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("a", "a", 'a', 0)]
|
|
||||||
[InlineData("ab", "a", 'a', 0)]
|
|
||||||
[InlineData("aab", "a", 'a', 0)]
|
|
||||||
[InlineData("acab", "a", 'a', 0)]
|
|
||||||
[InlineData("acab", "c", 'c', 1)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "lo", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "ol", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "ll", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "rml", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "mlr", 'l', 11)]
|
|
||||||
[InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
|
|
||||||
[InlineData("aaaaaaaaaaalmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'l', 11)]
|
|
||||||
[InlineData("aaaaaaaaaaacmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'm', 12)]
|
|
||||||
[InlineData("aaaaaaaaaaarmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'r', 11)]
|
|
||||||
[InlineData("/localhost:5000/PATH/%2FPATH2/ HTTP/1.1", " %?", '%', 21)]
|
|
||||||
[InlineData("/localhost:5000/PATH/%2FPATH2/?key=value HTTP/1.1", " %?", '%', 21)]
|
|
||||||
[InlineData("/localhost:5000/PATH/PATH2/?key=value HTTP/1.1", " %?", '?', 27)]
|
|
||||||
[InlineData("/localhost:5000/PATH/PATH2/ HTTP/1.1", " %?", ' ', 27)]
|
|
||||||
public void MemorySeek(string raw, string search, char expectResult, int expectIndex)
|
|
||||||
{
|
|
||||||
var block = _pool.Lease();
|
|
||||||
var chars = raw.ToCharArray().Select(c => (byte)c).ToArray();
|
|
||||||
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
|
|
||||||
block.End += chars.Length;
|
|
||||||
|
|
||||||
var begin = block.GetIterator();
|
|
||||||
var searchFor = search.ToCharArray();
|
|
||||||
|
|
||||||
int found = -1;
|
|
||||||
if (searchFor.Length == 1)
|
|
||||||
{
|
|
||||||
found = begin.Seek((byte)searchFor[0]);
|
|
||||||
}
|
|
||||||
else if (searchFor.Length == 2)
|
|
||||||
{
|
|
||||||
found = begin.Seek((byte)searchFor[0], (byte)searchFor[1]);
|
|
||||||
}
|
|
||||||
else if (searchFor.Length == 3)
|
|
||||||
{
|
|
||||||
found = begin.Seek((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.False(true, "Invalid test sample.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(expectResult, found);
|
|
||||||
Assert.Equal(expectIndex, begin.Index - block.Start);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Put()
|
public void Put()
|
||||||
{
|
{
|
||||||
|
|
@ -241,728 +167,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PeekLong()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var block = _pool.Lease();
|
|
||||||
var bytes = BitConverter.GetBytes(0x0102030405060708UL);
|
|
||||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length);
|
|
||||||
block.End += bytes.Length;
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
ulong result;
|
|
||||||
Assert.True(scan.TryPeekLong(out result));
|
|
||||||
Assert.Equal(0x0102030405060708UL, result);
|
|
||||||
Assert.Equal(originalIndex, scan.Index);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(1)]
|
|
||||||
[InlineData(2)]
|
|
||||||
[InlineData(3)]
|
|
||||||
[InlineData(4)]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(6)]
|
|
||||||
[InlineData(7)]
|
|
||||||
public void PeekLongNotEnoughBytes(int totalBytes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var block = _pool.Lease();
|
|
||||||
var bytes = BitConverter.GetBytes(0x0102030405060708UL);
|
|
||||||
var bytesLength = totalBytes;
|
|
||||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytesLength);
|
|
||||||
block.End += bytesLength;
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
ulong result;
|
|
||||||
Assert.False(scan.TryPeekLong(out result));
|
|
||||||
Assert.Equal(originalIndex, scan.Index);
|
|
||||||
_pool.Return(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(1)]
|
|
||||||
[InlineData(2)]
|
|
||||||
[InlineData(3)]
|
|
||||||
[InlineData(4)]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(6)]
|
|
||||||
[InlineData(7)]
|
|
||||||
public void PeekLongNotEnoughBytesAtBlockBoundary(int firstBlockBytes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedResult = 0x0102030405060708UL;
|
|
||||||
var nextBlockBytes = 7 - firstBlockBytes;
|
|
||||||
|
|
||||||
var block = _pool.Lease();
|
|
||||||
block.End += firstBlockBytes;
|
|
||||||
|
|
||||||
var nextBlock = _pool.Lease();
|
|
||||||
nextBlock.End += nextBlockBytes;
|
|
||||||
|
|
||||||
block.Next = nextBlock;
|
|
||||||
|
|
||||||
var bytes = BitConverter.GetBytes(expectedResult);
|
|
||||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes);
|
|
||||||
Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
|
|
||||||
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
ulong result;
|
|
||||||
Assert.False(scan.TryPeekLong(out result));
|
|
||||||
Assert.Equal(originalIndex, scan.Index);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
_pool.Return(nextBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(1)]
|
|
||||||
[InlineData(2)]
|
|
||||||
[InlineData(3)]
|
|
||||||
[InlineData(4)]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(6)]
|
|
||||||
[InlineData(7)]
|
|
||||||
[InlineData(8)]
|
|
||||||
public void PeekLongAtBlockBoundary(int firstBlockBytes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedResult = 0x0102030405060708UL;
|
|
||||||
var nonZeroData = 0xFF00FFFF0000FFFFUL;
|
|
||||||
var nextBlockBytes = 8 - firstBlockBytes;
|
|
||||||
|
|
||||||
var block = _pool.Lease();
|
|
||||||
block.Start += 8;
|
|
||||||
block.End = block.Start + firstBlockBytes;
|
|
||||||
|
|
||||||
var nextBlock = _pool.Lease();
|
|
||||||
nextBlock.Start += 8;
|
|
||||||
nextBlock.End = nextBlock.Start + nextBlockBytes;
|
|
||||||
|
|
||||||
block.Next = nextBlock;
|
|
||||||
|
|
||||||
var bytes = BitConverter.GetBytes(expectedResult);
|
|
||||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, firstBlockBytes);
|
|
||||||
Buffer.BlockCopy(bytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
|
|
||||||
|
|
||||||
// Fill in surrounding bytes with non-zero data
|
|
||||||
var nonZeroBytes = BitConverter.GetBytes(nonZeroData);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8);
|
|
||||||
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
ulong result;
|
|
||||||
Assert.True(scan.TryPeekLong(out result));
|
|
||||||
Assert.Equal(expectedResult, result);
|
|
||||||
Assert.Equal(originalIndex, scan.Index);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
_pool.Return(nextBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(1)]
|
|
||||||
[InlineData(2)]
|
|
||||||
[InlineData(3)]
|
|
||||||
[InlineData(4)]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(6)]
|
|
||||||
[InlineData(7)]
|
|
||||||
[InlineData(8)]
|
|
||||||
public void PeekLongAtBlockBoundarayWithMostSignificatBitsSet(int firstBlockBytes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedResult = 0xFF02030405060708UL;
|
|
||||||
var nonZeroData = 0xFF00FFFF0000FFFFUL;
|
|
||||||
var nextBlockBytes = 8 - firstBlockBytes;
|
|
||||||
|
|
||||||
var block = _pool.Lease();
|
|
||||||
block.Start += 8;
|
|
||||||
block.End = block.Start + firstBlockBytes;
|
|
||||||
|
|
||||||
var nextBlock = _pool.Lease();
|
|
||||||
nextBlock.Start += 8;
|
|
||||||
nextBlock.End = nextBlock.Start + nextBlockBytes;
|
|
||||||
|
|
||||||
block.Next = nextBlock;
|
|
||||||
|
|
||||||
var expectedBytes = BitConverter.GetBytes(expectedResult);
|
|
||||||
Buffer.BlockCopy(expectedBytes, 0, block.Array, block.Start, firstBlockBytes);
|
|
||||||
Buffer.BlockCopy(expectedBytes, firstBlockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
|
|
||||||
|
|
||||||
// Fill in surrounding bytes with non-zero data
|
|
||||||
var nonZeroBytes = BitConverter.GetBytes(nonZeroData);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.Start - 8, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, block.Array, block.End, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.Start - 8, 8);
|
|
||||||
Buffer.BlockCopy(nonZeroBytes, 0, nextBlock.Array, nextBlock.End, 8);
|
|
||||||
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
ulong result;
|
|
||||||
Assert.True(scan.TryPeekLong(out result));
|
|
||||||
Assert.Equal(expectedResult, result);
|
|
||||||
Assert.Equal(originalIndex, scan.Index);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
_pool.Return(nextBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(1)]
|
|
||||||
[InlineData(2)]
|
|
||||||
[InlineData(3)]
|
|
||||||
[InlineData(4)]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(6)]
|
|
||||||
[InlineData(7)]
|
|
||||||
[InlineData(8)]
|
|
||||||
[InlineData(9)]
|
|
||||||
public void SkipAtBlockBoundary(int blockBytes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var nextBlockBytes = 10 - blockBytes;
|
|
||||||
|
|
||||||
var block = _pool.Lease();
|
|
||||||
block.End += blockBytes;
|
|
||||||
|
|
||||||
var nextBlock = _pool.Lease();
|
|
||||||
nextBlock.End += nextBlockBytes;
|
|
||||||
|
|
||||||
block.Next = nextBlock;
|
|
||||||
|
|
||||||
var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
|
||||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, blockBytes);
|
|
||||||
Buffer.BlockCopy(bytes, blockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
|
|
||||||
|
|
||||||
var scan = block.GetIterator();
|
|
||||||
var originalIndex = scan.Index;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
scan.Skip(8);
|
|
||||||
var result = scan.Take();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(0x08, result);
|
|
||||||
Assert.NotEqual(originalIndex, scan.Index);
|
|
||||||
|
|
||||||
_pool.Return(block);
|
|
||||||
_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 firstBlock = _pool.Lease();
|
|
||||||
firstBlock.End += 3;
|
|
||||||
|
|
||||||
var middleBlock = _pool.Lease();
|
|
||||||
middleBlock.End += 1;
|
|
||||||
firstBlock.Next = middleBlock;
|
|
||||||
|
|
||||||
var finalBlock = _pool.Lease();
|
|
||||||
finalBlock.End += 2;
|
|
||||||
middleBlock.Next = finalBlock;
|
|
||||||
|
|
||||||
var scan = firstBlock.GetIterator();
|
|
||||||
|
|
||||||
// Act/Assert
|
|
||||||
Assert.ThrowsAny<InvalidOperationException>(() => scan.Skip(8));
|
|
||||||
|
|
||||||
_pool.Return(firstBlock);
|
|
||||||
_pool.Return(middleBlock);
|
|
||||||
_pool.Return(finalBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(SeekByteLimitData))]
|
|
||||||
public void TestSeekByteLimitWithinSameBlock(string input, char seek, int limit, int expectedBytesScanned, int expectedReturnValue)
|
|
||||||
{
|
|
||||||
MemoryPoolBlock block = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
|
|
||||||
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((byte)seek, 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 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((byte)seek, 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 afterSeek = (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((byte)limitAt);
|
|
||||||
var returnValue1 = scan1.Seek((byte)seek, ref end);
|
|
||||||
var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end);
|
|
||||||
var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end);
|
|
||||||
var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end);
|
|
||||||
var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end);
|
|
||||||
var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, 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 afterSeek = (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((byte)limitAt);
|
|
||||||
var returnValue1 = scan1.Seek((byte)seek, ref end);
|
|
||||||
var returnValue2_1 = scan2_1.Seek((byte)seek, afterSeek, ref end);
|
|
||||||
var returnValue2_2 = scan2_2.Seek(afterSeek, (byte)seek, ref end);
|
|
||||||
var returnValue3_1 = scan3_1.Seek((byte)seek, afterSeek, afterSeek, ref end);
|
|
||||||
var returnValue3_2 = scan3_2.Seek(afterSeek, (byte)seek, afterSeek, ref end);
|
|
||||||
var returnValue3_3 = scan3_3.Seek(afterSeek, afterSeek, (byte)seek, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EmptyIteratorBehaviourIsValid()
|
public void EmptyIteratorBehaviourIsValid()
|
||||||
{
|
{
|
||||||
const byte byteCr = (byte)'\n';
|
const byte byteCr = (byte)'\n';
|
||||||
ulong longValue;
|
|
||||||
var end = default(MemoryPoolIterator);
|
var end = default(MemoryPoolIterator);
|
||||||
|
|
||||||
Assert.False(default(MemoryPoolIterator).TryPeekLong(out longValue));
|
|
||||||
Assert.Null(default(MemoryPoolIterator).GetAsciiString(ref end));
|
|
||||||
Assert.Null(default(MemoryPoolIterator).GetUtf8String(ref end));
|
|
||||||
// Assert.Equal doesn't work for default(ArraySegments)
|
|
||||||
Assert.True(default(MemoryPoolIterator).GetArraySegment(end).Equals(default(ArraySegment<byte>)));
|
|
||||||
Assert.True(default(MemoryPoolIterator).IsDefault);
|
Assert.True(default(MemoryPoolIterator).IsDefault);
|
||||||
Assert.True(default(MemoryPoolIterator).IsEnd);
|
Assert.True(default(MemoryPoolIterator).IsEnd);
|
||||||
Assert.Equal(default(MemoryPoolIterator).Take(), -1);
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Peek(), -1);
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Seek(byteCr), -1);
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, ref end), -1);
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr), -1);
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Seek(byteCr, byteCr, byteCr), -1);
|
|
||||||
|
|
||||||
default(MemoryPoolIterator).CopyFrom(default(ArraySegment<byte>));
|
default(MemoryPoolIterator).CopyFrom(default(ArraySegment<byte>));
|
||||||
default(MemoryPoolIterator).CopyFromAscii("");
|
default(MemoryPoolIterator).CopyFromAscii("");
|
||||||
Assert.ThrowsAny<InvalidOperationException>(() => default(MemoryPoolIterator).Put(byteCr));
|
Assert.ThrowsAny<InvalidOperationException>(() => default(MemoryPoolIterator).Put(byteCr));
|
||||||
Assert.ThrowsAny<InvalidOperationException>(() => default(MemoryPoolIterator).GetLength(end));
|
Assert.ThrowsAny<InvalidOperationException>(() => default(MemoryPoolIterator).GetLength(end));
|
||||||
Assert.ThrowsAny<InvalidOperationException>(() => default(MemoryPoolIterator).Skip(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestGetArraySegment()
|
|
||||||
{
|
|
||||||
MemoryPoolBlock block0 = null;
|
|
||||||
MemoryPoolBlock block1 = null;
|
|
||||||
|
|
||||||
var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
block0 = _pool.Lease();
|
|
||||||
block1 = _pool.Lease();
|
|
||||||
|
|
||||||
block0.GetIterator().CopyFrom(byteRange);
|
|
||||||
block1.GetIterator().CopyFrom(byteRange);
|
|
||||||
|
|
||||||
block0.Next = block1;
|
|
||||||
|
|
||||||
var begin = block0.GetIterator();
|
|
||||||
var end0 = begin;
|
|
||||||
var end1 = begin;
|
|
||||||
|
|
||||||
end0.Skip(byteRange.Length);
|
|
||||||
end1.Skip(byteRange.Length * 2);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var as0 = begin.GetArraySegment(end0);
|
|
||||||
var as1 = begin.GetArraySegment(end1);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(as0.Count, byteRange.Length);
|
|
||||||
Assert.Equal(as1.Count, byteRange.Length * 2);
|
|
||||||
|
|
||||||
for (var i = 1; i < byteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var asb0 = as0.Array[i + as0.Offset];
|
|
||||||
var asb1 = as1.Array[i + as1.Offset];
|
|
||||||
var b = byteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(asb0, b);
|
|
||||||
Assert.Equal(asb1, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 1 + byteRange.Length; i < byteRange.Length * 2; i++)
|
|
||||||
{
|
|
||||||
var asb1 = as1.Array[i + as1.Offset];
|
|
||||||
var b = byteRange[i - byteRange.Length];
|
|
||||||
|
|
||||||
Assert.Equal(asb1, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (block0 != null) _pool.Return(block0);
|
|
||||||
if (block1 != null) _pool.Return(block1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestTake()
|
|
||||||
{
|
|
||||||
MemoryPoolBlock block0 = null;
|
|
||||||
MemoryPoolBlock block1 = null;
|
|
||||||
MemoryPoolBlock block2 = null;
|
|
||||||
MemoryPoolBlock emptyBlock0 = null;
|
|
||||||
MemoryPoolBlock emptyBlock1 = null;
|
|
||||||
|
|
||||||
var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
block0 = _pool.Lease();
|
|
||||||
block1 = _pool.Lease();
|
|
||||||
block2 = _pool.Lease();
|
|
||||||
emptyBlock0 = _pool.Lease();
|
|
||||||
emptyBlock1 = _pool.Lease();
|
|
||||||
|
|
||||||
block0.GetIterator().CopyFrom(byteRange);
|
|
||||||
block1.GetIterator().CopyFrom(byteRange);
|
|
||||||
block2.GetIterator().CopyFrom(byteRange);
|
|
||||||
|
|
||||||
var begin = block0.GetIterator();
|
|
||||||
|
|
||||||
// Single block
|
|
||||||
for (var i = 0; i < byteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var t = begin.Take();
|
|
||||||
var b = byteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(t, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(begin.Take(), -1);
|
|
||||||
|
|
||||||
// Dual block
|
|
||||||
block0.Next = block1;
|
|
||||||
begin = block0.GetIterator();
|
|
||||||
|
|
||||||
for (var block = 0; block < 2; block++)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < byteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var t = begin.Take();
|
|
||||||
var b = byteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(t, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(begin.Take(), -1);
|
|
||||||
|
|
||||||
// Multi block
|
|
||||||
block1.Next = emptyBlock0;
|
|
||||||
emptyBlock0.Next = emptyBlock1;
|
|
||||||
emptyBlock1.Next = block2;
|
|
||||||
begin = block0.GetIterator();
|
|
||||||
|
|
||||||
for (var block = 0; block < 3; block++)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < byteRange.Length; i++)
|
|
||||||
{
|
|
||||||
var t = begin.Take();
|
|
||||||
var b = byteRange[i];
|
|
||||||
|
|
||||||
Assert.Equal(t, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(begin.Take(), -1);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (block0 != null) _pool.Return(block0);
|
|
||||||
if (block1 != null) _pool.Return(block1);
|
|
||||||
if (block2 != null) _pool.Return(block2);
|
|
||||||
if (emptyBlock0 != null) _pool.Return(emptyBlock0);
|
|
||||||
if (emptyBlock1 != null) _pool.Return(emptyBlock1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestTakeEmptyBlocks()
|
|
||||||
{
|
|
||||||
MemoryPoolBlock emptyBlock0 = null;
|
|
||||||
MemoryPoolBlock emptyBlock1 = null;
|
|
||||||
MemoryPoolBlock emptyBlock2 = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
emptyBlock0 = _pool.Lease();
|
|
||||||
emptyBlock1 = _pool.Lease();
|
|
||||||
emptyBlock2 = _pool.Lease();
|
|
||||||
|
|
||||||
var beginEmpty = emptyBlock0.GetIterator();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
|
|
||||||
// No blocks
|
|
||||||
Assert.Equal(default(MemoryPoolIterator).Take(), -1);
|
|
||||||
|
|
||||||
// Single empty block
|
|
||||||
Assert.Equal(beginEmpty.Take(), -1);
|
|
||||||
|
|
||||||
// Dual empty block
|
|
||||||
emptyBlock0.Next = emptyBlock1;
|
|
||||||
beginEmpty = emptyBlock0.GetIterator();
|
|
||||||
Assert.Equal(beginEmpty.Take(), -1);
|
|
||||||
|
|
||||||
// Multi empty block
|
|
||||||
emptyBlock1.Next = emptyBlock2;
|
|
||||||
beginEmpty = emptyBlock0.GetIterator();
|
|
||||||
Assert.Equal(beginEmpty.Take(), -1);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (emptyBlock0 != null) _pool.Return(emptyBlock0);
|
|
||||||
if (emptyBlock1 != null) _pool.Return(emptyBlock1);
|
|
||||||
if (emptyBlock2 != null) _pool.Return(emptyBlock2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -973,23 +191,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[InlineData("abcde", "abcde", 6)]
|
[InlineData("abcde", "abcde", 6)]
|
||||||
public void TestGetAsciiStringEscaped(string input, string expected, int maxChars)
|
public void TestGetAsciiStringEscaped(string input, string expected, int maxChars)
|
||||||
{
|
{
|
||||||
MemoryPoolBlock block = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Arrange
|
// Arrange
|
||||||
var buffer = new Span<byte>(Encoding.ASCII.GetBytes(input));
|
var buffer = new Span<byte>(Encoding.ASCII.GetBytes(input));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = buffer.GetAsciiStringEscaped(maxChars);
|
var result = buffer.GetAsciiStringEscaped(maxChars);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (block != null) _pool.Return(block);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
|
||||||
{
|
|
||||||
public class UrlPathDecoderTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Empty()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, string.Empty, string.Empty);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void WhiteSpace()
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, " ", " ");
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("/foo/bar", "/foo/bar")]
|
|
||||||
[InlineData("/foo/BAR", "/foo/BAR")]
|
|
||||||
[InlineData("/foo/", "/foo/")]
|
|
||||||
[InlineData("/", "/")]
|
|
||||||
public void NormalCases(string raw, string expect)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, raw, expect);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("%2F", "%2F")]
|
|
||||||
[InlineData("/foo%2Fbar", "/foo%2Fbar")]
|
|
||||||
[InlineData("/foo%2F%20bar", "/foo%2F bar")]
|
|
||||||
public void SkipForwardSlash(string raw, string expect)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, raw, expect);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("%D0%A4", "Ф")]
|
|
||||||
[InlineData("%d0%a4", "Ф")]
|
|
||||||
[InlineData("%E0%A4%AD", "भ")]
|
|
||||||
[InlineData("%e0%A4%Ad", "भ")]
|
|
||||||
[InlineData("%F0%A4%AD%A2", "𤭢")]
|
|
||||||
[InlineData("%F0%a4%Ad%a2", "𤭢")]
|
|
||||||
[InlineData("%48%65%6C%6C%6F%20%57%6F%72%6C%64", "Hello World")]
|
|
||||||
[InlineData("%48%65%6C%6C%6F%2D%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", "Hello-µ@ßöäüàá")]
|
|
||||||
// Test the borderline cases of overlong UTF8.
|
|
||||||
[InlineData("%C2%80", "\u0080")]
|
|
||||||
[InlineData("%E0%A0%80", "\u0800")]
|
|
||||||
[InlineData("%F0%90%80%80", "\U00010000")]
|
|
||||||
[InlineData("%63", "c")]
|
|
||||||
[InlineData("%32", "2")]
|
|
||||||
[InlineData("%20", " ")]
|
|
||||||
public void ValidUTF8(string raw, string expect)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, raw, expect);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("%C3%84ra%20Benetton", "Ära Benetton")]
|
|
||||||
[InlineData("%E6%88%91%E8%87%AA%E6%A8%AA%E5%88%80%E5%90%91%E5%A4%A9%E7%AC%91%E5%8E%BB%E7%95%99%E8%82%9D%E8%83%86%E4%B8%A4%E6%98%86%E4%BB%91", "我自横刀向天笑去留肝胆两昆仑")]
|
|
||||||
public void Internationalized(string raw, string expect)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, raw, expect);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// Overlong ASCII
|
|
||||||
[InlineData("%C0%A4", "%C0%A4")]
|
|
||||||
[InlineData("%C1%BF", "%C1%BF")]
|
|
||||||
[InlineData("%E0%80%AF", "%E0%80%AF")]
|
|
||||||
[InlineData("%E0%9F%BF", "%E0%9F%BF")]
|
|
||||||
[InlineData("%F0%80%80%AF", "%F0%80%80%AF")]
|
|
||||||
[InlineData("%F0%8F%8F%BF", "%F0%8F%8F%BF")]
|
|
||||||
// Incomplete
|
|
||||||
[InlineData("%", "%")]
|
|
||||||
[InlineData("%%", "%%")]
|
|
||||||
[InlineData("%A", "%A")]
|
|
||||||
[InlineData("%Y", "%Y")]
|
|
||||||
// Mixed
|
|
||||||
[InlineData("%%32", "%2")]
|
|
||||||
[InlineData("%%20", "% ")]
|
|
||||||
[InlineData("%C0%A4%32", "%C0%A42")]
|
|
||||||
[InlineData("%32%C0%A4%32", "2%C0%A42")]
|
|
||||||
[InlineData("%C0%32%A4", "%C02%A4")]
|
|
||||||
public void InvalidUTF8(string raw, string expect)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
PositiveAssert(mem, raw, expect);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("/foo%2Fbar", 10, "/foo%2Fbar", 10)]
|
|
||||||
[InlineData("/foo%2Fbar", 9, "/foo%2Fba", 9)]
|
|
||||||
[InlineData("/foo%2Fbar", 8, "/foo%2Fb", 8)]
|
|
||||||
[InlineData("%D0%A4", 6, "Ф", 1)]
|
|
||||||
[InlineData("%D0%A4", 5, "%D0%A", 5)]
|
|
||||||
[InlineData("%D0%A4", 4, "%D0%", 4)]
|
|
||||||
[InlineData("%D0%A4", 3, "%D0", 3)]
|
|
||||||
[InlineData("%D0%A4", 2, "%D", 2)]
|
|
||||||
[InlineData("%D0%A4", 1, "%", 1)]
|
|
||||||
[InlineData("%D0%A4", 0, "", 0)]
|
|
||||||
[InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 45, "µ@ßöäüàá", 8)]
|
|
||||||
[InlineData("%C2%B5%40%C3%9F%C3%B6%C3%A4%C3%BC%C3%A0%C3%A1", 44, "µ@ßöäüà%C3%A", 12)]
|
|
||||||
public void DecodeWithBoundary(string raw, int rawLength, string expect, int expectLength)
|
|
||||||
{
|
|
||||||
using (var pool = new MemoryPool())
|
|
||||||
{
|
|
||||||
var mem = pool.Lease();
|
|
||||||
|
|
||||||
var begin = BuildSample(mem, raw);
|
|
||||||
var end = GetIterator(begin, rawLength);
|
|
||||||
|
|
||||||
var end2 = UrlPathDecoder.Unescape(begin, end);
|
|
||||||
var result = begin.GetUtf8String(ref end2);
|
|
||||||
|
|
||||||
Assert.Equal(expectLength, result.Length);
|
|
||||||
Assert.Equal(expect, result);
|
|
||||||
|
|
||||||
pool.Return(mem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MemoryPoolIterator BuildSample(MemoryPoolBlock mem, string data)
|
|
||||||
{
|
|
||||||
var store = data.Select(c => (byte)c).ToArray();
|
|
||||||
mem.GetIterator().CopyFrom(new ArraySegment<byte>(store));
|
|
||||||
|
|
||||||
return mem.GetIterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MemoryPoolIterator GetIterator(MemoryPoolIterator begin, int displacement)
|
|
||||||
{
|
|
||||||
var result = begin;
|
|
||||||
for (int i = 0; i < displacement; ++i)
|
|
||||||
{
|
|
||||||
result.Take();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PositiveAssert(MemoryPoolBlock mem, string raw, string expect)
|
|
||||||
{
|
|
||||||
var begin = BuildSample(mem, raw);
|
|
||||||
var end = GetIterator(begin, raw.Length);
|
|
||||||
|
|
||||||
var result = UrlPathDecoder.Unescape(begin, end);
|
|
||||||
Assert.Equal(expect, begin.GetUtf8String(ref result));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PositiveAssert(MemoryPoolBlock mem, string raw)
|
|
||||||
{
|
|
||||||
var begin = BuildSample(mem, raw);
|
|
||||||
var end = GetIterator(begin, raw.Length);
|
|
||||||
|
|
||||||
var result = UrlPathDecoder.Unescape(begin, end);
|
|
||||||
Assert.NotEqual(raw.Length, begin.GetUtf8String(ref result).Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NegativeAssert(MemoryPoolBlock mem, string raw)
|
|
||||||
{
|
|
||||||
var begin = BuildSample(mem, raw);
|
|
||||||
var end = GetIterator(begin, raw.Length);
|
|
||||||
|
|
||||||
var resultEnd = UrlPathDecoder.Unescape(begin, end);
|
|
||||||
var result = begin.GetUtf8String(ref resultEnd);
|
|
||||||
Assert.Equal(raw, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,14 +4,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Testing
|
namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
public class HttpParsingData
|
public class HttpParsingData
|
||||||
{
|
{
|
||||||
public static IEnumerable<string[]> ValidRequestLineData
|
public static IEnumerable<string[]> RequestLineValidData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +19,7 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
"GET",
|
"GET",
|
||||||
"CUSTOM",
|
"CUSTOM",
|
||||||
};
|
};
|
||||||
var targets = new[]
|
var paths = new[]
|
||||||
{
|
{
|
||||||
Tuple.Create("/", "/"),
|
Tuple.Create("/", "/"),
|
||||||
Tuple.Create("/abc", "/abc"),
|
Tuple.Create("/abc", "/abc"),
|
||||||
|
|
@ -57,166 +56,154 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
};
|
};
|
||||||
|
|
||||||
return from method in methods
|
return from method in methods
|
||||||
from target in targets
|
from path in paths
|
||||||
from queryString in queryStrings
|
from queryString in queryStrings
|
||||||
from httpVersion in httpVersions
|
from httpVersion in httpVersions
|
||||||
select new[]
|
select new[]
|
||||||
{
|
{
|
||||||
$"{method} {target.Item1}{queryString} {httpVersion}\r\n",
|
$"{method} {path.Item1}{queryString} {httpVersion}\r\n",
|
||||||
method,
|
method,
|
||||||
$"{target.Item2}",
|
$"{path.Item1}{queryString}",
|
||||||
|
$"{path.Item1}",
|
||||||
|
$"{path.Item2}",
|
||||||
queryString,
|
queryString,
|
||||||
httpVersion
|
httpVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All these test cases must end in '\n', otherwise the server will spin forever
|
public static IEnumerable<string> RequestLineIncompleteData => new[]
|
||||||
public static IEnumerable<object[]> InvalidRequestLineData
|
|
||||||
{
|
{
|
||||||
get
|
"G",
|
||||||
{
|
"GE",
|
||||||
var invalidRequestLines = new[]
|
"GET",
|
||||||
{
|
"GET ",
|
||||||
"G\r\n",
|
"GET /",
|
||||||
"GE\r\n",
|
"GET / ",
|
||||||
"GET\r\n",
|
"GET / H",
|
||||||
"GET \r\n",
|
"GET / HT",
|
||||||
"GET /\r\n",
|
"GET / HTT",
|
||||||
"GET / \r\n",
|
"GET / HTTP",
|
||||||
"GET/HTTP/1.1\r\n",
|
"GET / HTTP/",
|
||||||
"GET /HTTP/1.1\r\n",
|
"GET / HTTP/1",
|
||||||
" \r\n",
|
"GET / HTTP/1.",
|
||||||
" \r\n",
|
"GET / HTTP/1.1",
|
||||||
"/ HTTP/1.1\r\n",
|
"GET / HTTP/1.1\r",
|
||||||
" / HTTP/1.1\r\n",
|
};
|
||||||
"/ \r\n",
|
|
||||||
"GET \r\n",
|
|
||||||
"GET HTTP/1.0\r\n",
|
|
||||||
"GET HTTP/1.1\r\n",
|
|
||||||
"GET / \n",
|
|
||||||
"GET / HTTP/1.0\n",
|
|
||||||
"GET / HTTP/1.1\n",
|
|
||||||
"GET / HTTP/1.0\rA\n",
|
|
||||||
"GET / HTTP/1.1\ra\n",
|
|
||||||
"GET? / HTTP/1.1\r\n",
|
|
||||||
"GET ? HTTP/1.1\r\n",
|
|
||||||
"GET /a?b=cHTTP/1.1\r\n",
|
|
||||||
"GET /a%20bHTTP/1.1\r\n",
|
|
||||||
"GET /a%20b?c=dHTTP/1.1\r\n",
|
|
||||||
"GET %2F HTTP/1.1\r\n",
|
|
||||||
"GET %00 HTTP/1.1\r\n",
|
|
||||||
"CUSTOM \r\n",
|
|
||||||
"CUSTOM /\r\n",
|
|
||||||
"CUSTOM / \r\n",
|
|
||||||
"CUSTOM /HTTP/1.1\r\n",
|
|
||||||
"CUSTOM \r\n",
|
|
||||||
"CUSTOM HTTP/1.0\r\n",
|
|
||||||
"CUSTOM HTTP/1.1\r\n",
|
|
||||||
"CUSTOM / \n",
|
|
||||||
"CUSTOM / HTTP/1.0\n",
|
|
||||||
"CUSTOM / HTTP/1.1\n",
|
|
||||||
"CUSTOM / HTTP/1.0\rA\n",
|
|
||||||
"CUSTOM / HTTP/1.1\ra\n",
|
|
||||||
"CUSTOM ? HTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a?b=cHTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a%20bHTTP/1.1\r\n",
|
|
||||||
"CUSTOM /a%20b?c=dHTTP/1.1\r\n",
|
|
||||||
"CUSTOM %2F HTTP/1.1\r\n",
|
|
||||||
"CUSTOM %00 HTTP/1.1\r\n",
|
|
||||||
// Bad HTTP Methods (invalid according to RFC)
|
|
||||||
"( / HTTP/1.0\r\n",
|
|
||||||
") / HTTP/1.0\r\n",
|
|
||||||
"< / HTTP/1.0\r\n",
|
|
||||||
"> / HTTP/1.0\r\n",
|
|
||||||
"@ / HTTP/1.0\r\n",
|
|
||||||
", / HTTP/1.0\r\n",
|
|
||||||
"; / HTTP/1.0\r\n",
|
|
||||||
": / HTTP/1.0\r\n",
|
|
||||||
"\\ / HTTP/1.0\r\n",
|
|
||||||
"\" / HTTP/1.0\r\n",
|
|
||||||
"/ / HTTP/1.0\r\n",
|
|
||||||
"[ / HTTP/1.0\r\n",
|
|
||||||
"] / HTTP/1.0\r\n",
|
|
||||||
"? / HTTP/1.0\r\n",
|
|
||||||
"= / HTTP/1.0\r\n",
|
|
||||||
"{ / HTTP/1.0\r\n",
|
|
||||||
"} / HTTP/1.0\r\n",
|
|
||||||
"get@ / HTTP/1.0\r\n",
|
|
||||||
"post= / HTTP/1.0\r\n",
|
|
||||||
};
|
|
||||||
|
|
||||||
var encodedNullCharInTargetRequestLines = new[]
|
public static IEnumerable<string> RequestLineInvalidData => new[]
|
||||||
{
|
|
||||||
"GET /%00 HTTP/1.1\r\n",
|
|
||||||
"GET /%00%00 HTTP/1.1\r\n",
|
|
||||||
"GET /%E8%00%84 HTTP/1.1\r\n",
|
|
||||||
"GET /%E8%85%00 HTTP/1.1\r\n",
|
|
||||||
"GET /%F3%00%82%86 HTTP/1.1\r\n",
|
|
||||||
"GET /%F3%85%00%82 HTTP/1.1\r\n",
|
|
||||||
"GET /%F3%85%82%00 HTTP/1.1\r\n",
|
|
||||||
"GET /%E8%85%00 HTTP/1.1\r\n",
|
|
||||||
"GET /%E8%01%00 HTTP/1.1\r\n",
|
|
||||||
};
|
|
||||||
|
|
||||||
var nullCharInTargetRequestLines = new[]
|
|
||||||
{
|
|
||||||
"GET \0 HTTP/1.1\r\n",
|
|
||||||
"GET /\0 HTTP/1.1\r\n",
|
|
||||||
"GET /\0\0 HTTP/1.1\r\n",
|
|
||||||
"GET /%C8\0 HTTP/1.1\r\n",
|
|
||||||
};
|
|
||||||
|
|
||||||
return invalidRequestLines.Select(requestLine => new object[]
|
|
||||||
{
|
|
||||||
requestLine,
|
|
||||||
typeof(BadHttpRequestException),
|
|
||||||
$"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}"
|
|
||||||
})
|
|
||||||
.Concat(encodedNullCharInTargetRequestLines.Select(requestLine => new object[]
|
|
||||||
{
|
|
||||||
requestLine,
|
|
||||||
typeof(InvalidOperationException),
|
|
||||||
$"The path contains null characters."
|
|
||||||
}))
|
|
||||||
.Concat(nullCharInTargetRequestLines.Select(requestLine => new object[]
|
|
||||||
{
|
|
||||||
requestLine,
|
|
||||||
typeof(InvalidOperationException),
|
|
||||||
new InvalidOperationException().Message
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData<string> UnrecognizedHttpVersionData
|
|
||||||
{
|
{
|
||||||
get
|
"G\r\n",
|
||||||
{
|
"GE\r\n",
|
||||||
return new TheoryData<string>
|
"GET\r\n",
|
||||||
{
|
"GET \r\n",
|
||||||
"H",
|
"GET /\r\n",
|
||||||
"HT",
|
"GET / \r\n",
|
||||||
"HTT",
|
"GET/HTTP/1.1\r\n",
|
||||||
"HTTP",
|
"GET /HTTP/1.1\r\n",
|
||||||
"HTTP/",
|
" \r\n",
|
||||||
"HTTP/1",
|
" \r\n",
|
||||||
"HTTP/1.",
|
"/ HTTP/1.1\r\n",
|
||||||
"http/1.0",
|
" / HTTP/1.1\r\n",
|
||||||
"http/1.1",
|
"/ \r\n",
|
||||||
"HTTP/1.1 ",
|
"GET \r\n",
|
||||||
"HTTP/1.0a",
|
"GET HTTP/1.0\r\n",
|
||||||
"HTTP/1.0ab",
|
"GET HTTP/1.1\r\n",
|
||||||
"HTTP/1.1a",
|
"GET / \n",
|
||||||
"HTTP/1.1ab",
|
"GET / HTTP/1.0\n",
|
||||||
"HTTP/1.2",
|
"GET / HTTP/1.1\n",
|
||||||
"HTTP/3.0",
|
"GET / HTTP/1.0\rA\n",
|
||||||
"hello",
|
"GET / HTTP/1.1\ra\n",
|
||||||
"8charact",
|
"GET? / HTTP/1.1\r\n",
|
||||||
};
|
"GET ? HTTP/1.1\r\n",
|
||||||
}
|
"GET /a?b=cHTTP/1.1\r\n",
|
||||||
}
|
"GET /a%20bHTTP/1.1\r\n",
|
||||||
|
"GET /a%20b?c=dHTTP/1.1\r\n",
|
||||||
|
"GET %2F HTTP/1.1\r\n",
|
||||||
|
"GET %00 HTTP/1.1\r\n",
|
||||||
|
"CUSTOM \r\n",
|
||||||
|
"CUSTOM /\r\n",
|
||||||
|
"CUSTOM / \r\n",
|
||||||
|
"CUSTOM /HTTP/1.1\r\n",
|
||||||
|
"CUSTOM \r\n",
|
||||||
|
"CUSTOM HTTP/1.0\r\n",
|
||||||
|
"CUSTOM HTTP/1.1\r\n",
|
||||||
|
"CUSTOM / \n",
|
||||||
|
"CUSTOM / HTTP/1.0\n",
|
||||||
|
"CUSTOM / HTTP/1.1\n",
|
||||||
|
"CUSTOM / HTTP/1.0\rA\n",
|
||||||
|
"CUSTOM / HTTP/1.1\ra\n",
|
||||||
|
"CUSTOM ? HTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a?b=cHTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a%20bHTTP/1.1\r\n",
|
||||||
|
"CUSTOM /a%20b?c=dHTTP/1.1\r\n",
|
||||||
|
"CUSTOM %2F HTTP/1.1\r\n",
|
||||||
|
"CUSTOM %00 HTTP/1.1\r\n",
|
||||||
|
// Bad HTTP Methods (invalid according to RFC)
|
||||||
|
"( / HTTP/1.0\r\n",
|
||||||
|
") / HTTP/1.0\r\n",
|
||||||
|
"< / HTTP/1.0\r\n",
|
||||||
|
"> / HTTP/1.0\r\n",
|
||||||
|
"@ / HTTP/1.0\r\n",
|
||||||
|
", / HTTP/1.0\r\n",
|
||||||
|
"; / HTTP/1.0\r\n",
|
||||||
|
": / HTTP/1.0\r\n",
|
||||||
|
"\\ / HTTP/1.0\r\n",
|
||||||
|
"\" / HTTP/1.0\r\n",
|
||||||
|
"/ / HTTP/1.0\r\n",
|
||||||
|
"[ / HTTP/1.0\r\n",
|
||||||
|
"] / HTTP/1.0\r\n",
|
||||||
|
"? / HTTP/1.0\r\n",
|
||||||
|
"= / HTTP/1.0\r\n",
|
||||||
|
"{ / HTTP/1.0\r\n",
|
||||||
|
"} / HTTP/1.0\r\n",
|
||||||
|
"get@ / HTTP/1.0\r\n",
|
||||||
|
"post= / HTTP/1.0\r\n",
|
||||||
|
};
|
||||||
|
|
||||||
public static IEnumerable<object[]> InvalidRequestHeaderData
|
public static IEnumerable<string> RequestLineWithEncodedNullCharInTargetData => new[]
|
||||||
|
{
|
||||||
|
"GET /%00 HTTP/1.1\r\n",
|
||||||
|
"GET /%00%00 HTTP/1.1\r\n",
|
||||||
|
"GET /%E8%00%84 HTTP/1.1\r\n",
|
||||||
|
"GET /%E8%85%00 HTTP/1.1\r\n",
|
||||||
|
"GET /%F3%00%82%86 HTTP/1.1\r\n",
|
||||||
|
"GET /%F3%85%00%82 HTTP/1.1\r\n",
|
||||||
|
"GET /%F3%85%82%00 HTTP/1.1\r\n",
|
||||||
|
"GET /%E8%01%00 HTTP/1.1\r\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IEnumerable<string> RequestLineWithNullCharInTargetData => new[]
|
||||||
|
{
|
||||||
|
"GET \0 HTTP/1.1\r\n",
|
||||||
|
"GET /\0 HTTP/1.1\r\n",
|
||||||
|
"GET /\0\0 HTTP/1.1\r\n",
|
||||||
|
"GET /%C8\0 HTTP/1.1\r\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<string> UnrecognizedHttpVersionData => new TheoryData<string>
|
||||||
|
{
|
||||||
|
"H",
|
||||||
|
"HT",
|
||||||
|
"HTT",
|
||||||
|
"HTTP",
|
||||||
|
"HTTP/",
|
||||||
|
"HTTP/1",
|
||||||
|
"HTTP/1.",
|
||||||
|
"http/1.0",
|
||||||
|
"http/1.1",
|
||||||
|
"HTTP/1.1 ",
|
||||||
|
"HTTP/1.0a",
|
||||||
|
"HTTP/1.0ab",
|
||||||
|
"HTTP/1.1a",
|
||||||
|
"HTTP/1.1ab",
|
||||||
|
"HTTP/1.2",
|
||||||
|
"HTTP/3.0",
|
||||||
|
"hello",
|
||||||
|
"8charact",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RequestHeaderInvalidData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -245,6 +232,14 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
"Header-1: value1\rHeader-2: value2\r\n\r\n",
|
"Header-1: value1\rHeader-2: value2\r\n\r\n",
|
||||||
"Header-1: value1\r\nHeader-2: value2\r\r\n",
|
"Header-1: value1\r\nHeader-2: value2\r\r\n",
|
||||||
"Header-1: value1\r\nHeader-2: v\ralue2\r\n",
|
"Header-1: value1\r\nHeader-2: v\ralue2\r\n",
|
||||||
|
"Header-1: Value__\rVector16________Vector32\r\n",
|
||||||
|
"Header-1: Value___Vector16\r________Vector32\r\n",
|
||||||
|
"Header-1: Value___Vector16_______\rVector32\r\n",
|
||||||
|
"Header-1: Value___Vector16________Vector32\r\r\n",
|
||||||
|
"Header-1: Value___Vector16________Vector32_\r\r\n",
|
||||||
|
"Header-1: Value___Vector16________Vector32Value___Vector16_______\rVector32\r\n",
|
||||||
|
"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\r\r\n",
|
||||||
|
"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\r\r\n",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Missing colon
|
// Missing colon
|
||||||
|
|
@ -253,6 +248,7 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
"Header-1 value1\r\n\r\n",
|
"Header-1 value1\r\n\r\n",
|
||||||
"Header-1 value1\r\nHeader-2: value2\r\n\r\n",
|
"Header-1 value1\r\nHeader-2: value2\r\n\r\n",
|
||||||
"Header-1: value1\r\nHeader-2 value2\r\n\r\n",
|
"Header-1: value1\r\nHeader-2 value2\r\n\r\n",
|
||||||
|
"\n"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Starting with whitespace
|
// Starting with whitespace
|
||||||
|
|
@ -269,9 +265,20 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
"Header : value\r\n\r\n",
|
"Header : value\r\n\r\n",
|
||||||
"Header\t: value\r\n\r\n",
|
"Header\t: value\r\n\r\n",
|
||||||
|
"Header\r: value\r\n\r\n",
|
||||||
|
"Header_\rVector16: value\r\n\r\n",
|
||||||
|
"Header__Vector16\r: value\r\n\r\n",
|
||||||
|
"Header__Vector16_\r: value\r\n\r\n",
|
||||||
|
"Header_\rVector16________Vector32: value\r\n\r\n",
|
||||||
|
"Header__Vector16________Vector32\r: value\r\n\r\n",
|
||||||
|
"Header__Vector16________Vector32_\r: value\r\n\r\n",
|
||||||
|
"Header__Vector16________Vector32Header_\rVector16________Vector32: value\r\n\r\n",
|
||||||
|
"Header__Vector16________Vector32Header__Vector16________Vector32\r: value\r\n\r\n",
|
||||||
|
"Header__Vector16________Vector32Header__Vector16________Vector32_\r: value\r\n\r\n",
|
||||||
"Header 1: value1\r\nHeader-2: value2\r\n\r\n",
|
"Header 1: value1\r\nHeader-2: value2\r\n\r\n",
|
||||||
"Header 1 : value1\r\nHeader-2: value2\r\n\r\n",
|
"Header 1 : value1\r\nHeader-2: value2\r\n\r\n",
|
||||||
"Header 1\t: value1\r\nHeader-2: value2\r\n\r\n",
|
"Header 1\t: value1\r\nHeader-2: value2\r\n\r\n",
|
||||||
|
"Header 1\r: value1\r\nHeader-2: value2\r\n\r\n",
|
||||||
"Header-1: value1\r\nHeader 2: value2\r\n\r\n",
|
"Header-1: value1\r\nHeader 2: value2\r\n\r\n",
|
||||||
"Header-1: value1\r\nHeader-2 : value2\r\n\r\n",
|
"Header-1: value1\r\nHeader-2 : value2\r\n\r\n",
|
||||||
"Header-1: value1\r\nHeader-2\t: value2\r\n\r\n",
|
"Header-1: value1\r\nHeader-2\t: value2\r\n\r\n",
|
||||||
|
|
@ -287,11 +294,11 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
|
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
Tuple.Create(headersWithLineFolding,"Header value line folding not supported."),
|
Tuple.Create(headersWithLineFolding, "Whitespace is not allowed in header name."),
|
||||||
Tuple.Create(headersWithCRInValue,"Header value must not contain CR characters."),
|
Tuple.Create(headersWithCRInValue, "Header value must not contain CR characters."),
|
||||||
Tuple.Create(headersWithMissingColon,"No ':' character found in header line."),
|
Tuple.Create(headersWithMissingColon, "No ':' character found in header line."),
|
||||||
Tuple.Create(headersStartingWithWhitespace, "Header line must not start with whitespace."),
|
Tuple.Create(headersStartingWithWhitespace, "Whitespace is not allowed in header name."),
|
||||||
Tuple.Create(headersWithWithspaceInName,"Whitespace is not allowed in header name."),
|
Tuple.Create(headersWithWithspaceInName, "Whitespace is not allowed in header name."),
|
||||||
Tuple.Create(headersNotEndingInCrLfLine, "Headers corrupted, invalid header sequence.")
|
Tuple.Create(headersNotEndingInCrLfLine, "Headers corrupted, invalid header sequence.")
|
||||||
}
|
}
|
||||||
.SelectMany(t => t.Item1.Select(headers => new[] { headers, t.Item2 }));
|
.SelectMany(t => t.Item1.Select(headers => new[] { headers, t.Item2 }));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue