Make HTTP/1.1 startline parsing "safe" (#20885)
This commit is contained in:
parent
351359f56d
commit
f5fa16e998
|
|
@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
public HttpParser() { }
|
||||
public HttpParser(bool showErrorDetails) { }
|
||||
public bool ParseHeaders(TRequestHandler handler, ref System.Buffers.SequenceReader<byte> reader) { throw null; }
|
||||
public bool ParseRequestLine(TRequestHandler handler, in System.Buffers.ReadOnlySequence<byte> buffer, out System.SequencePosition consumed, out System.SequencePosition examined) { throw null; }
|
||||
public bool ParseRequestLine(TRequestHandler handler, ref System.Buffers.SequenceReader<byte> reader) { throw null; }
|
||||
}
|
||||
public enum HttpScheme
|
||||
{
|
||||
|
|
@ -235,13 +235,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
Http = 0,
|
||||
Https = 1,
|
||||
}
|
||||
public enum HttpVersion
|
||||
public enum HttpVersion : sbyte
|
||||
{
|
||||
Unknown = -1,
|
||||
Http10 = 0,
|
||||
Http11 = 1,
|
||||
Http2 = 2,
|
||||
Http3 = 3,
|
||||
Unknown = (sbyte)-1,
|
||||
Http10 = (sbyte)0,
|
||||
Http11 = (sbyte)1,
|
||||
Http2 = (sbyte)2,
|
||||
Http3 = (sbyte)3,
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public partial struct HttpVersionAndMethod
|
||||
{
|
||||
private int _dummyPrimitive;
|
||||
public HttpVersionAndMethod(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, int methodEnd) { throw null; }
|
||||
public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod Method { get { throw null; } }
|
||||
public int MethodEnd { get { throw null; } }
|
||||
public Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion Version { get { throw null; } set { } }
|
||||
}
|
||||
public partial interface IHttpHeadersHandler
|
||||
{
|
||||
|
|
@ -252,7 +261,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
public partial interface IHttpRequestLineHandler
|
||||
{
|
||||
void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersion version, System.Span<byte> target, System.Span<byte> path, System.Span<byte> query, System.Span<byte> customMethod, bool pathEncoded);
|
||||
void OnStartLine(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpVersionAndMethod versionAndMethod, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.TargetOffsetPathLength targetPath, System.Span<byte> startLine);
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct TargetOffsetPathLength
|
||||
{
|
||||
private readonly int _dummyPrimitive;
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public TargetOffsetPathLength(int offset, int length, bool isEncoded) { throw null; }
|
||||
public bool IsEncoded { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
|
||||
public int Length { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
|
||||
public int Offset { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]get { throw null; } }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||
|
|
|
|||
|
|
@ -310,10 +310,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// _consumedBytes aren't tracked for trailer headers, since headers have separate limits.
|
||||
if (_mode == Mode.TrailerHeaders)
|
||||
{
|
||||
if (_context.TakeMessageHeaders(readableBuffer, trailers: true, out consumed, out examined))
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
if (_context.TakeMessageHeaders(ref reader, trailers: true))
|
||||
{
|
||||
examined = reader.Position;
|
||||
_mode = Mode.Complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = readableBuffer.End;
|
||||
}
|
||||
|
||||
consumed = reader.Position;
|
||||
}
|
||||
|
||||
return _mode == Mode.Complete;
|
||||
|
|
|
|||
|
|
@ -139,15 +139,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
SendTimeoutResponse();
|
||||
}
|
||||
|
||||
public void ParseRequest(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
public bool ParseRequest(ref SequenceReader<byte> reader)
|
||||
{
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.End;
|
||||
|
||||
switch (_requestProcessingStatus)
|
||||
{
|
||||
case RequestProcessingStatus.RequestPending:
|
||||
if (buffer.IsEmpty)
|
||||
if (reader.End)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -157,75 +154,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
|
||||
goto case RequestProcessingStatus.ParsingRequestLine;
|
||||
case RequestProcessingStatus.ParsingRequestLine:
|
||||
if (TakeStartLine(buffer, out consumed, out examined))
|
||||
if (TakeStartLine(ref reader))
|
||||
{
|
||||
TrimAndParseHeaders(buffer, ref consumed, out examined);
|
||||
return;
|
||||
_requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
|
||||
goto case RequestProcessingStatus.ParsingHeaders;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
case RequestProcessingStatus.ParsingHeaders:
|
||||
if (TakeMessageHeaders(buffer, trailers: false, out consumed, out examined))
|
||||
if (TakeMessageHeaders(ref reader, trailers: false))
|
||||
{
|
||||
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||
// Consumed preamble
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
void TrimAndParseHeaders(in ReadOnlySequence<byte> buffer, ref SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
var trimmedBuffer = buffer.Slice(consumed, buffer.End);
|
||||
_requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
|
||||
|
||||
if (TakeMessageHeaders(trimmedBuffer, trailers: false, out consumed, out examined))
|
||||
{
|
||||
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
|
||||
}
|
||||
}
|
||||
// Haven't completed consuming preamble
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TakeStartLine(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
public bool TakeStartLine(ref SequenceReader<byte> reader)
|
||||
{
|
||||
// Make sure the buffer is limited
|
||||
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
|
||||
if (reader.Remaining >= ServerOptions.Limits.MaxRequestLineSize)
|
||||
{
|
||||
// Input oversize, cap amount checked
|
||||
return TrimAndTakeStartLine(buffer, out consumed, out examined);
|
||||
return TrimAndTakeStartLine(ref reader);
|
||||
}
|
||||
|
||||
return _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined);
|
||||
return _parser.ParseRequestLine(new Http1ParsingHandler(this), ref reader);
|
||||
|
||||
bool TrimAndTakeStartLine(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
bool TrimAndTakeStartLine(ref SequenceReader<byte> reader)
|
||||
{
|
||||
var trimmedBuffer = buffer.Slice(buffer.Start, ServerOptions.Limits.MaxRequestLineSize);
|
||||
var trimmedBuffer = reader.Sequence.Slice(reader.Position, ServerOptions.Limits.MaxRequestLineSize);
|
||||
var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
|
||||
|
||||
if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), trimmedBuffer, out consumed, out examined))
|
||||
if (!_parser.ParseRequestLine(new Http1ParsingHandler(this), ref trimmedReader))
|
||||
{
|
||||
// We read the maximum allowed but didn't complete the start line.
|
||||
KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
|
||||
}
|
||||
|
||||
reader.Advance(trimmedReader.Consumed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined)
|
||||
public bool TakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
|
||||
{
|
||||
// Make sure the buffer is limited
|
||||
if (buffer.Length > _remainingRequestHeadersBytesAllowed)
|
||||
if (reader.Remaining > _remainingRequestHeadersBytesAllowed)
|
||||
{
|
||||
// Input oversize, cap amount checked
|
||||
return TrimAndTakeMessageHeaders(buffer, trailers, out consumed, out examined);
|
||||
return TrimAndTakeMessageHeaders(ref reader, trailers);
|
||||
}
|
||||
|
||||
var reader = new SequenceReader<byte>(buffer);
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader);
|
||||
|
||||
var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader);
|
||||
if (result)
|
||||
{
|
||||
TimeoutControl.CancelTimeout();
|
||||
|
|
@ -235,30 +225,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
finally
|
||||
{
|
||||
consumed = reader.Position;
|
||||
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed;
|
||||
|
||||
if (result)
|
||||
{
|
||||
examined = consumed;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = buffer.End;
|
||||
}
|
||||
}
|
||||
|
||||
bool TrimAndTakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined)
|
||||
bool TrimAndTakeMessageHeaders(ref SequenceReader<byte> reader, bool trailers)
|
||||
{
|
||||
var trimmedBuffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed);
|
||||
|
||||
var reader = new SequenceReader<byte>(trimmedBuffer);
|
||||
var result = false;
|
||||
var trimmedBuffer = reader.Sequence.Slice(reader.Position, _remainingRequestHeadersBytesAllowed);
|
||||
var trimmedReader = new SequenceReader<byte>(trimmedBuffer);
|
||||
try
|
||||
{
|
||||
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader);
|
||||
|
||||
if (!result)
|
||||
if (!_parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref trimmedReader))
|
||||
{
|
||||
// We read the maximum allowed but didn't complete the headers.
|
||||
KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
|
||||
|
|
@ -266,44 +242,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
TimeoutControl.CancelTimeout();
|
||||
|
||||
return result;
|
||||
reader.Advance(trimmedReader.Consumed);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
consumed = reader.Position;
|
||||
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed;
|
||||
|
||||
if (result)
|
||||
{
|
||||
examined = consumed;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = trimmedBuffer.End;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
{
|
||||
var targetStart = targetPath.Offset;
|
||||
// Slice out target
|
||||
var target = startLine[targetStart..];
|
||||
Debug.Assert(target.Length != 0, "Request target must be non-zero length");
|
||||
|
||||
var method = versionAndMethod.Method;
|
||||
var ch = target[0];
|
||||
if (ch == ByteForwardSlash)
|
||||
{
|
||||
// origin-form.
|
||||
// The most common form of request-target.
|
||||
// https://tools.ietf.org/html/rfc7230#section-5.3.1
|
||||
OnOriginFormTarget(pathEncoded, target, path, query);
|
||||
OnOriginFormTarget(targetPath, target);
|
||||
}
|
||||
else if (ch == ByteAsterisk && target.Length == 1)
|
||||
{
|
||||
OnAsteriskFormTarget(method);
|
||||
}
|
||||
else if (target.GetKnownHttpScheme(out _))
|
||||
else if (startLine[targetStart..].GetKnownHttpScheme(out _))
|
||||
{
|
||||
OnAbsoluteFormTarget(target, query);
|
||||
OnAbsoluteFormTarget(targetPath, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -316,10 +287,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
Method = method;
|
||||
if (method == HttpMethod.Custom)
|
||||
{
|
||||
_methodText = customMethod.GetAsciiStringNonNullCharacters();
|
||||
_methodText = startLine[..versionAndMethod.MethodEnd].GetAsciiStringNonNullCharacters();
|
||||
}
|
||||
|
||||
_httpVersion = version;
|
||||
_httpVersion = versionAndMethod.Version;
|
||||
|
||||
Debug.Assert(RawTarget != null, "RawTarget was not set");
|
||||
Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
|
||||
|
|
@ -329,7 +300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
// Compare with Http2Stream.TryValidatePseudoHeaders
|
||||
private void OnOriginFormTarget(bool pathEncoded, Span<byte> target, Span<byte> path, Span<byte> query)
|
||||
private void OnOriginFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
|
||||
{
|
||||
Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");
|
||||
|
||||
|
|
@ -349,59 +320,69 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return;
|
||||
}
|
||||
|
||||
// Read raw target before mutating memory.
|
||||
var previousValue = _parsedRawTarget;
|
||||
if (ServerOptions.DisableStringReuse ||
|
||||
previousValue == null || previousValue.Length != target.Length ||
|
||||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
|
||||
{
|
||||
ParseTarget(targetPath, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
// As RawTarget is the same we can reuse the previous parsed values.
|
||||
RawTarget = previousValue;
|
||||
Path = _parsedPath;
|
||||
QueryString = _parsedQueryString;
|
||||
}
|
||||
|
||||
// Clear parsedData for absolute target as we won't check it if we come via this path again,
|
||||
// an setting to null is fast as it doesn't need to use a GC write barrier.
|
||||
_parsedAbsoluteRequestTarget = null;
|
||||
}
|
||||
|
||||
private void ParseTarget(TargetOffsetPathLength targetPath, Span<byte> target)
|
||||
{
|
||||
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
|
||||
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
|
||||
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
|
||||
|
||||
try
|
||||
{
|
||||
var disableStringReuse = ServerOptions.DisableStringReuse;
|
||||
// Read raw target before mutating memory.
|
||||
var previousValue = _parsedRawTarget;
|
||||
if (disableStringReuse ||
|
||||
previousValue == null || previousValue.Length != target.Length ||
|
||||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
|
||||
{
|
||||
// The previous string does not match what the bytes would convert to,
|
||||
// so we will need to generate a new string.
|
||||
RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();
|
||||
// The previous string does not match what the bytes would convert to,
|
||||
// so we will need to generate a new string.
|
||||
RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();
|
||||
|
||||
previousValue = _parsedQueryString;
|
||||
if (disableStringReuse ||
|
||||
previousValue == null || previousValue.Length != query.Length ||
|
||||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
|
||||
var queryLength = 0;
|
||||
if (target.Length == targetPath.Length)
|
||||
{
|
||||
// No query string
|
||||
if (ReferenceEquals(_parsedQueryString, string.Empty))
|
||||
{
|
||||
// The previous string does not match what the bytes would convert to,
|
||||
// so we will need to generate a new string.
|
||||
QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same as previous
|
||||
QueryString = _parsedQueryString;
|
||||
}
|
||||
|
||||
if (path.Length == 1)
|
||||
{
|
||||
// If path.Length == 1 it can only be a forward slash (e.g. home page)
|
||||
Path = _parsedPath = ForwardSlash;
|
||||
}
|
||||
else
|
||||
{
|
||||
Path = _parsedPath = PathNormalizer.DecodePath(path, pathEncoded, RawTarget, query.Length);
|
||||
QueryString = string.Empty;
|
||||
_parsedQueryString = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// As RawTarget is the same we can reuse the previous parsed values.
|
||||
RawTarget = _parsedRawTarget;
|
||||
Path = _parsedPath;
|
||||
QueryString = _parsedQueryString;
|
||||
queryLength = ParseQuery(targetPath, target);
|
||||
}
|
||||
|
||||
// Clear parsedData for absolute target as we won't check it if we come via this path again,
|
||||
// an setting to null is fast as it doesn't need to use a GC write barrier.
|
||||
_parsedAbsoluteRequestTarget = null;
|
||||
var pathLength = targetPath.Length;
|
||||
if (pathLength == 1)
|
||||
{
|
||||
// If path.Length == 1 it can only be a forward slash (e.g. home page)
|
||||
Path = _parsedPath = ForwardSlash;
|
||||
}
|
||||
else
|
||||
{
|
||||
var path = target[..pathLength];
|
||||
Path = _parsedPath = PathNormalizer.DecodePath(path, targetPath.IsEncoded, RawTarget, queryLength);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
|
|
@ -409,6 +390,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
private int ParseQuery(TargetOffsetPathLength targetPath, Span<byte> target)
|
||||
{
|
||||
var previousValue = _parsedQueryString;
|
||||
var query = target[targetPath.Length..];
|
||||
var queryLength = query.Length;
|
||||
if (ServerOptions.DisableStringReuse ||
|
||||
previousValue == null || previousValue.Length != queryLength ||
|
||||
!StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
|
||||
{
|
||||
// The previous string does not match what the bytes would convert to,
|
||||
// so we will need to generate a new string.
|
||||
QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same as previous
|
||||
QueryString = _parsedQueryString;
|
||||
}
|
||||
|
||||
return queryLength;
|
||||
}
|
||||
|
||||
private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
|
||||
{
|
||||
_requestTargetForm = HttpRequestTarget.AuthorityForm;
|
||||
|
|
@ -480,8 +483,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_parsedAbsoluteRequestTarget = null;
|
||||
}
|
||||
|
||||
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
|
||||
private void OnAbsoluteFormTarget(TargetOffsetPathLength targetPath, Span<byte> target)
|
||||
{
|
||||
Span<byte> query = target[targetPath.Length..];
|
||||
_requestTargetForm = HttpRequestTarget.AbsoluteForm;
|
||||
|
||||
// absolute-form
|
||||
|
|
@ -645,12 +649,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected override bool TryParseRequest(ReadResult result, out bool endConnection)
|
||||
{
|
||||
var examined = result.Buffer.End;
|
||||
var consumed = result.Buffer.End;
|
||||
|
||||
var reader = new SequenceReader<byte>(result.Buffer);
|
||||
var isConsumed = false;
|
||||
try
|
||||
{
|
||||
ParseRequest(result.Buffer, out consumed, out examined);
|
||||
isConsumed = ParseRequest(ref reader);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
|
|
@ -662,7 +665,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
finally
|
||||
{
|
||||
Input.AdvanceTo(consumed, examined);
|
||||
Input.AdvanceTo(reader.Position, isConsumed ? reader.Position : result.Buffer.End);
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
=> Connection.OnStartLine(versionAndMethod, targetPath, startLine);
|
||||
|
||||
public void OnStaticIndexedHeader(int index)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
|
|
@ -34,84 +35,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private const byte BytePercentage = (byte)'%';
|
||||
private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line
|
||||
|
||||
public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
{
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.End;
|
||||
|
||||
// Prepare the first span
|
||||
var span = buffer.FirstSpan;
|
||||
var lineIndex = span.IndexOf(ByteLF);
|
||||
if (lineIndex >= 0)
|
||||
if (reader.TryReadTo(out ReadOnlySpan<byte> requestLine, ByteLF, advancePastDelimiter: true))
|
||||
{
|
||||
consumed = buffer.GetPosition(lineIndex + 1, consumed);
|
||||
span = span.Slice(0, lineIndex + 1);
|
||||
}
|
||||
else if (buffer.IsSingleSegment)
|
||||
{
|
||||
// No request line end
|
||||
return false;
|
||||
}
|
||||
else if (TryGetNewLine(buffer, out var found))
|
||||
{
|
||||
span = buffer.Slice(consumed, found).ToSpan();
|
||||
consumed = found;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No request line end
|
||||
return false;
|
||||
ParseRequestLine(handler, requestLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix and parse the span
|
||||
fixed (byte* data = span)
|
||||
{
|
||||
ParseRequestLine(handler, data, span.Length);
|
||||
}
|
||||
|
||||
examined = consumed;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe void ParseRequestLine(TRequestHandler handler, byte* data, int length)
|
||||
private void ParseRequestLine(TRequestHandler handler, ReadOnlySpan<byte> requestLine)
|
||||
{
|
||||
// Get Method and set the offset
|
||||
var method = HttpUtilities.GetKnownMethod(data, length, out var pathStartOffset);
|
||||
|
||||
Span<byte> customMethod = default;
|
||||
var method = requestLine.GetKnownMethod(out var methodEnd);
|
||||
if (method == HttpMethod.Custom)
|
||||
{
|
||||
customMethod = GetUnknownMethod(data, length, out pathStartOffset);
|
||||
methodEnd = GetUnknownMethodLength(requestLine);
|
||||
}
|
||||
|
||||
// Use a new offset var as pathStartOffset needs to be on stack
|
||||
var versionAndMethod = new HttpVersionAndMethod(method, methodEnd);
|
||||
|
||||
// Use a new offset var as methodEnd needs to be on stack
|
||||
// as its passed by reference above so can't be in register.
|
||||
// Skip space
|
||||
var offset = pathStartOffset + 1;
|
||||
if (offset >= length)
|
||||
var offset = methodEnd + 1;
|
||||
if ((uint)offset >= (uint)requestLine.Length)
|
||||
{
|
||||
// Start of path not found
|
||||
RejectRequestLine(data, length);
|
||||
RejectRequestLine(requestLine);
|
||||
}
|
||||
|
||||
byte ch = data[offset];
|
||||
var ch = requestLine[offset];
|
||||
if (ch == ByteSpace || ch == ByteQuestionMark || ch == BytePercentage)
|
||||
{
|
||||
// Empty path is illegal, or path starting with percentage
|
||||
RejectRequestLine(data, length);
|
||||
RejectRequestLine(requestLine);
|
||||
}
|
||||
|
||||
// Target = Path and Query
|
||||
var targetStart = offset;
|
||||
var pathEncoded = false;
|
||||
var pathStart = offset;
|
||||
|
||||
// Skip first char (just checked)
|
||||
offset++;
|
||||
|
||||
// Find end of path and if path is encoded
|
||||
for (; offset < length; offset++)
|
||||
for (; (uint)offset < (uint)requestLine.Length; offset++)
|
||||
{
|
||||
ch = data[offset];
|
||||
ch = requestLine[offset];
|
||||
if (ch == ByteSpace || ch == ByteQuestionMark)
|
||||
{
|
||||
// End of path
|
||||
|
|
@ -123,16 +95,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
var pathBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
||||
var path = new TargetOffsetPathLength(targetStart, length: offset - targetStart, pathEncoded);
|
||||
|
||||
// Query string
|
||||
var queryStart = offset;
|
||||
if (ch == ByteQuestionMark)
|
||||
{
|
||||
// We have a query string
|
||||
for (; offset < length; offset++)
|
||||
for (; (uint)offset < (uint)requestLine.Length; offset++)
|
||||
{
|
||||
ch = data[offset];
|
||||
ch = requestLine[offset];
|
||||
if (ch == ByteSpace)
|
||||
{
|
||||
break;
|
||||
|
|
@ -140,41 +111,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
// End of query string not found
|
||||
if (offset == length)
|
||||
{
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
|
||||
var targetBuffer = new Span<byte>(data + pathStart, offset - pathStart);
|
||||
var query = new Span<byte>(data + queryStart, offset - queryStart);
|
||||
|
||||
var queryEnd = offset;
|
||||
// Consume space
|
||||
offset++;
|
||||
|
||||
// If offset has overshot length, end of query string wasn't not found
|
||||
if ((uint)offset > (uint)requestLine.Length)
|
||||
{
|
||||
RejectRequestLine(requestLine);
|
||||
}
|
||||
|
||||
// Version
|
||||
var httpVersion = HttpUtilities.GetKnownVersion(data + offset, length - offset);
|
||||
var remaining = requestLine.Slice(offset);
|
||||
var httpVersion = remaining.GetKnownVersionAndConfirmCR();
|
||||
versionAndMethod.Version = httpVersion;
|
||||
if (httpVersion == HttpVersion.Unknown)
|
||||
{
|
||||
if (data[offset] == ByteCR || data[length - 2] != ByteCR)
|
||||
{
|
||||
// If missing delimiter or CR before LF, reject and log entire line
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else inform HTTP version is unsupported.
|
||||
RejectUnknownVersion(data + offset, length - offset - 2);
|
||||
}
|
||||
// HTTP version is unsupported or incorrectly terminated.
|
||||
RejectUnknownVersion(offset, requestLine);
|
||||
}
|
||||
|
||||
// After version's 8 bytes and CR, expect LF
|
||||
if (data[offset + 8 + 1] != ByteLF)
|
||||
// Version + CR is 8 bytes; adding 9 should take us to .Length
|
||||
offset += 9;
|
||||
// LF should have been dropped prior to method call, so offset should now be length
|
||||
if ((uint)offset != (uint)requestLine.Length)
|
||||
{
|
||||
RejectRequestLine(data, length);
|
||||
RejectRequestLine(requestLine);
|
||||
}
|
||||
|
||||
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded);
|
||||
// We need to reinterpret from ReadOnlySpan into Span to allow path mutation for
|
||||
// in-place normalization and decoding to transform into a canonical path
|
||||
var startLine = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(requestLine), queryEnd);
|
||||
handler.OnStartLine(versionAndMethod, path, startLine);
|
||||
}
|
||||
|
||||
public bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
|
|
@ -460,56 +428,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool TryGetNewLine(in ReadOnlySequence<byte> buffer, out SequencePosition found)
|
||||
private int GetUnknownMethodLength(ReadOnlySpan<byte> span)
|
||||
{
|
||||
var byteLfPosition = buffer.PositionOf(ByteLF);
|
||||
if (byteLfPosition != null)
|
||||
var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(span);
|
||||
|
||||
if (invalidIndex <= 0 || span[invalidIndex] != ByteSpace)
|
||||
{
|
||||
// Move 1 byte past the \n
|
||||
found = buffer.GetPosition(1, byteLfPosition.Value);
|
||||
return true;
|
||||
RejectRequestLine(span);
|
||||
}
|
||||
|
||||
found = default;
|
||||
return false;
|
||||
return invalidIndex;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
|
||||
{
|
||||
var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(data, length);
|
||||
|
||||
if (invalidIndex <= 0 || data[invalidIndex] != ByteSpace)
|
||||
{
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
|
||||
methodLength = invalidIndex;
|
||||
return new Span<byte>(data, methodLength);
|
||||
}
|
||||
|
||||
private unsafe bool IsTlsHandshake(byte* data, int length)
|
||||
private bool IsTlsHandshake(ReadOnlySpan<byte> requestLine)
|
||||
{
|
||||
const byte SslRecordTypeHandshake = (byte)0x16;
|
||||
|
||||
// Make sure we can check at least for the existence of a TLS handshake - we check the first byte
|
||||
// See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/
|
||||
|
||||
return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake);
|
||||
return (requestLine.Length >= MinTlsRequestSize && requestLine[0] == SslRecordTypeHandshake);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
private unsafe void RejectRequestLine(byte* requestLine, int length)
|
||||
private void RejectRequestLine(ReadOnlySpan<byte> requestLine)
|
||||
{
|
||||
// Check for incoming TLS handshake over HTTP
|
||||
if (IsTlsHandshake(requestLine, length))
|
||||
{
|
||||
throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
|
||||
}
|
||||
throw GetInvalidRequestException(
|
||||
IsTlsHandshake(requestLine) ?
|
||||
RequestRejectionReason.TlsOverHttpError :
|
||||
RequestRejectionReason.InvalidRequestLine,
|
||||
requestLine);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
|
|
@ -517,12 +465,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine);
|
||||
|
||||
[StackTraceHidden]
|
||||
private unsafe void RejectUnknownVersion(byte* version, int length)
|
||||
=> throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
|
||||
=> GetInvalidRequestException(reason, new ReadOnlySpan<byte>(detail, length));
|
||||
private void RejectUnknownVersion(int offset, ReadOnlySpan<byte> requestLine)
|
||||
// If CR before LF, reject and log entire line
|
||||
=> throw (((uint)offset >= (uint)requestLine.Length || requestLine[offset] == ByteCR || requestLine[^1] != ByteCR) ?
|
||||
GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine) :
|
||||
GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, requestLine[offset..^1]));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, ReadOnlySpan<byte> headerLine)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
public enum HttpVersion
|
||||
public enum HttpVersion : sbyte
|
||||
{
|
||||
Unknown = -1,
|
||||
Http10 = 0,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
internal interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
|
||||
{
|
||||
bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined);
|
||||
bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader);
|
||||
|
||||
bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,85 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
public interface IHttpRequestLineHandler
|
||||
{
|
||||
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded);
|
||||
void OnStartLine(
|
||||
HttpVersionAndMethod versionAndMethod,
|
||||
TargetOffsetPathLength targetPath,
|
||||
Span<byte> startLine);
|
||||
}
|
||||
|
||||
public struct HttpVersionAndMethod
|
||||
{
|
||||
private ulong _versionAndMethod;
|
||||
|
||||
public HttpVersionAndMethod(HttpMethod method, int methodEnd)
|
||||
{
|
||||
_versionAndMethod = ((ulong)(uint)methodEnd << 32) | ((ulong)method << 8);
|
||||
}
|
||||
|
||||
public HttpVersion Version
|
||||
{
|
||||
get => (HttpVersion)(sbyte)(byte)_versionAndMethod;
|
||||
set => _versionAndMethod = (_versionAndMethod & ~0xFFul) | (byte)value;
|
||||
}
|
||||
|
||||
public HttpMethod Method => (HttpMethod)(byte)(_versionAndMethod >> 8);
|
||||
|
||||
public int MethodEnd => (int)(uint)(_versionAndMethod >> 32);
|
||||
}
|
||||
|
||||
public readonly struct TargetOffsetPathLength
|
||||
{
|
||||
private readonly ulong _targetOffsetPathLength;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TargetOffsetPathLength(int offset, int length, bool isEncoded)
|
||||
{
|
||||
if (isEncoded)
|
||||
{
|
||||
length = -length;
|
||||
}
|
||||
|
||||
_targetOffsetPathLength = ((ulong)offset << 32) | (uint)length;
|
||||
}
|
||||
|
||||
public int Offset
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return (int)(_targetOffsetPathLength >> 32);
|
||||
}
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var length = (int)_targetOffsetPathLength;
|
||||
if (length < 0)
|
||||
{
|
||||
length = -length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEncoded
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return (int)_targetOffsetPathLength < 0 ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,13 +166,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe static int IndexOfInvalidTokenChar(byte* s, int length)
|
||||
public static int IndexOfInvalidTokenChar(ReadOnlySpan<byte> span)
|
||||
{
|
||||
var token = _token;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
for (var i = 0; i < span.Length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
var c = span[i];
|
||||
if (c >= (uint)token.Length || !token[c])
|
||||
{
|
||||
return i;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -50,38 +51,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe ulong GetAsciiStringAsLong(string str)
|
||||
private static ulong GetAsciiStringAsLong(string str)
|
||||
{
|
||||
Debug.Assert(str.Length == 8, "String must be exactly 8 (ASCII) characters long.");
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(str);
|
||||
|
||||
fixed (byte* ptr = &bytes[0])
|
||||
{
|
||||
return *(ulong*)ptr;
|
||||
}
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
|
||||
}
|
||||
|
||||
private static unsafe uint GetAsciiStringAsInt(string str)
|
||||
private static uint GetAsciiStringAsInt(string str)
|
||||
{
|
||||
Debug.Assert(str.Length == 4, "String must be exactly 4 (ASCII) characters long.");
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(str);
|
||||
|
||||
fixed (byte* ptr = &bytes[0])
|
||||
{
|
||||
return *(uint*)ptr;
|
||||
}
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||
}
|
||||
|
||||
private static unsafe ulong GetMaskAsLong(byte[] bytes)
|
||||
private static ulong GetMaskAsLong(byte[] bytes)
|
||||
{
|
||||
Debug.Assert(bytes.Length == 8, "Mask must be exactly 8 bytes long.");
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
return *(ulong*)ptr;
|
||||
}
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
|
||||
}
|
||||
|
||||
// The same as GetAsciiStringNonNullCharacters but throws BadRequest
|
||||
|
|
@ -142,7 +133,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span)
|
||||
private static string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span)
|
||||
=> GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span);
|
||||
|
||||
public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span)
|
||||
|
|
@ -251,43 +242,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
/// To optimize performance the GET method will be checked first.
|
||||
/// </remarks>
|
||||
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool GetKnownMethod(this Span<byte> span, out HttpMethod method, out int length)
|
||||
public static bool GetKnownMethod(this ReadOnlySpan<byte> span, out HttpMethod method, out int length)
|
||||
{
|
||||
fixed (byte* data = span)
|
||||
{
|
||||
method = GetKnownMethod(data, span.Length, out length);
|
||||
return method != HttpMethod.Custom;
|
||||
}
|
||||
method = GetKnownMethod(span, out length);
|
||||
return method != HttpMethod.Custom;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe HttpMethod GetKnownMethod(byte* data, int length, out int methodLength)
|
||||
public static HttpMethod GetKnownMethod(this ReadOnlySpan<byte> span, out int methodLength)
|
||||
{
|
||||
methodLength = 0;
|
||||
if (length < sizeof(uint))
|
||||
if (sizeof(uint) <= span.Length)
|
||||
{
|
||||
return HttpMethod.Custom;
|
||||
}
|
||||
else if (*(uint*)data == _httpGetMethodInt)
|
||||
{
|
||||
methodLength = 3;
|
||||
return HttpMethod.Get;
|
||||
}
|
||||
else if (length < sizeof(ulong))
|
||||
{
|
||||
return HttpMethod.Custom;
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = *(ulong*)data;
|
||||
var key = GetKnownMethodIndex(value);
|
||||
var x = _knownMethods[key];
|
||||
|
||||
if (x != null && (value & x.Item1) == x.Item2)
|
||||
if (BinaryPrimitives.ReadUInt32LittleEndian(span) == _httpGetMethodInt)
|
||||
{
|
||||
methodLength = x.Item4;
|
||||
return x.Item3;
|
||||
methodLength = 3;
|
||||
return HttpMethod.Get;
|
||||
}
|
||||
else if (sizeof(ulong) <= span.Length)
|
||||
{
|
||||
var value = BinaryPrimitives.ReadUInt64LittleEndian(span);
|
||||
var index = GetKnownMethodIndex(value);
|
||||
var knownMehods = _knownMethods;
|
||||
if ((uint)index < (uint)knownMehods.Length)
|
||||
{
|
||||
var knownMethod = _knownMethods[index];
|
||||
|
||||
if (knownMethod != null && (value & knownMethod.Item1) == knownMethod.Item2)
|
||||
{
|
||||
methodLength = knownMethod.Item4;
|
||||
return knownMethod.Item3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,21 +372,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
/// To optimize performance the HTTP/1.1 will be checked first.
|
||||
/// </remarks>
|
||||
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool GetKnownVersion(this Span<byte> span, out HttpVersion knownVersion, out byte length)
|
||||
public static bool GetKnownVersion(this ReadOnlySpan<byte> span, out HttpVersion knownVersion, out byte length)
|
||||
{
|
||||
fixed (byte* data = span)
|
||||
knownVersion = GetKnownVersionAndConfirmCR(span);
|
||||
if (knownVersion != HttpVersion.Unknown)
|
||||
{
|
||||
knownVersion = GetKnownVersion(data, span.Length);
|
||||
if (knownVersion != HttpVersion.Unknown)
|
||||
{
|
||||
length = sizeof(ulong);
|
||||
return true;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
return false;
|
||||
length = sizeof(ulong);
|
||||
return true;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -415,28 +397,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
/// </remarks>
|
||||
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe HttpVersion GetKnownVersion(byte* location, int length)
|
||||
internal static HttpVersion GetKnownVersionAndConfirmCR(this ReadOnlySpan<byte> location)
|
||||
{
|
||||
HttpVersion knownVersion;
|
||||
var version = *(ulong*)location;
|
||||
if (length < sizeof(ulong) + 1 || location[sizeof(ulong)] != (byte)'\r')
|
||||
if (location.Length < sizeof(ulong))
|
||||
{
|
||||
knownVersion = HttpVersion.Unknown;
|
||||
}
|
||||
else if (version == _http11VersionLong)
|
||||
{
|
||||
knownVersion = HttpVersion.Http11;
|
||||
}
|
||||
else if (version == _http10VersionLong)
|
||||
{
|
||||
knownVersion = HttpVersion.Http10;
|
||||
return HttpVersion.Unknown;
|
||||
}
|
||||
else
|
||||
{
|
||||
knownVersion = HttpVersion.Unknown;
|
||||
var version = BinaryPrimitives.ReadUInt64LittleEndian(location);
|
||||
if (sizeof(ulong) >= (uint)location.Length || location[sizeof(ulong)] != (byte)'\r')
|
||||
{
|
||||
return HttpVersion.Unknown;
|
||||
}
|
||||
else if (version == _http11VersionLong)
|
||||
{
|
||||
return HttpVersion.Http11;
|
||||
}
|
||||
else if (version == _http10VersionLong)
|
||||
{
|
||||
return HttpVersion.Http10;
|
||||
}
|
||||
}
|
||||
|
||||
return knownVersion;
|
||||
return HttpVersion.Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(Encoding.UTF8.GetBytes("\r\n\r\n"));
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
_http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(headerValue, _http1Connection.RequestHeaders[headerName]);
|
||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes("\r\n\r\n"));
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
var takeMessageHeaders = TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
|
|
@ -264,7 +264,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||
readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
takeMessageHeaders = TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
|
|
@ -389,7 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
var returnValue = TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.True(returnValue);
|
||||
|
|
@ -412,7 +412,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await _application.Output.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
|
||||
var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
var returnValue = TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.True(returnValue);
|
||||
|
|
@ -426,7 +426,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||
|
||||
_http1Connection.ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||
ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks;
|
||||
|
|
@ -443,7 +443,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target), exception.Message);
|
||||
|
|
@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
|
||||
|
|
@ -495,10 +495,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message);
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()), exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -513,7 +513,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
|
||||
|
|
@ -531,7 +531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
|
||||
|
|
@ -548,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(405, exception.StatusCode);
|
||||
|
|
@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_transport.Input.AdvanceTo(_consumed, _examined);
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(string.Empty), exception.Message);
|
||||
|
|
@ -971,6 +971,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
|
||||
}
|
||||
|
||||
|
||||
private bool TakeMessageHeaders(ReadOnlySequence<byte> readableBuffer, bool trailers, out SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
if (_http1Connection.TakeMessageHeaders(ref reader, trailers: trailers))
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = reader.Position;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = readableBuffer.End;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TakeStartLine(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
if (_http1Connection.TakeStartLine(ref reader))
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = reader.Position;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = readableBuffer.End;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ParseRequest(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
if (_http1Connection.ParseRequest(ref reader))
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = reader.Position;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = readableBuffer.End;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition)
|
||||
{
|
||||
const int MaxWaitLoop = 150;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
|
||||
var requestHandler = new RequestHandler();
|
||||
|
||||
Assert.True(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
Assert.True(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(requestHandler.Method, expectedMethod);
|
||||
Assert.Equal(requestHandler.Version, expectedVersion);
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
|
||||
var requestHandler = new RequestHandler();
|
||||
|
||||
Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
Assert.False(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(requestLine));
|
||||
var requestHandler = new RequestHandler();
|
||||
|
||||
Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
Assert.False(ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(buffer.Start, consumed);
|
||||
Assert.True(buffer.Slice(examined).IsEmpty);
|
||||
|
|
@ -93,9 +93,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message);
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()), exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
|
|
@ -117,9 +117,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(method.EscapeNonPrintable() + @" / HTTP/1.1\x0D\x0A"), exception.Message);
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(method.EscapeNonPrintable() + @" / HTTP/1.1\x0D"), exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(httpVersion), exception.Message);
|
||||
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
|
||||
|
|
@ -363,7 +363,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal("Invalid request line: ''", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
|
|
@ -374,7 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined));
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(string.Empty), exception.Message);
|
||||
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
|
||||
|
|
@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Encoding.ASCII.GetBytes("/"));
|
||||
|
||||
var requestHandler = new RequestHandler();
|
||||
var result = parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined);
|
||||
var result = ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Equal(buffer.Start, consumed);
|
||||
|
|
@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var badHttpRequestException = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined);
|
||||
ParseRequestLine(parser, requestHandler, buffer, out var consumed, out var examined);
|
||||
});
|
||||
|
||||
Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest);
|
||||
|
|
@ -480,6 +480,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.True(result);
|
||||
}
|
||||
|
||||
|
||||
private bool ParseRequestLine(IHttpParser<RequestHandler> parser, RequestHandler requestHandler, ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
if (parser.ParseRequestLine(requestHandler, ref reader))
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = reader.Position;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumed = reader.Position;
|
||||
examined = readableBuffer.End;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyHeader(
|
||||
string headerName,
|
||||
string rawHeaderValue,
|
||||
|
|
@ -565,6 +583,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
PathEncoded = pathEncoded;
|
||||
}
|
||||
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
{
|
||||
var method = versionAndMethod.Method;
|
||||
var version = versionAndMethod.Version;
|
||||
var customMethod = startLine[..versionAndMethod.MethodEnd];
|
||||
var targetStart = targetPath.Offset;
|
||||
var target = startLine[targetStart..];
|
||||
var path = target[..targetPath.Length];
|
||||
var query = target[targetPath.Length..];
|
||||
|
||||
Method = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
|
||||
Version = HttpUtilities.VersionToString(version);
|
||||
RawTarget = target.GetAsciiStringNonNullCharacters();
|
||||
RawPath = path.GetAsciiStringNonNullCharacters();
|
||||
Query = query.GetAsciiStringNonNullCharacters();
|
||||
PathEncoded = targetPath.IsEncoded;
|
||||
}
|
||||
|
||||
public void OnStaticIndexedHeader(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var expectedMethod = (HttpMethod)intExpectedMethod;
|
||||
// Arrange
|
||||
var block = new Span<byte>(Encoding.ASCII.GetBytes(input));
|
||||
var block = new ReadOnlySpan<byte>(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
// Act
|
||||
var result = block.GetKnownMethod(out var knownMethod, out var length);
|
||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var version = (HttpVersion)intVersion;
|
||||
// Arrange
|
||||
var block = new Span<byte>(Encoding.ASCII.GetBytes(input));
|
||||
var block = new ReadOnlySpan<byte>(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
// Act
|
||||
var result = block.GetKnownVersion(out HttpVersion knownVersion, out var length);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void GetsKnownMethod(byte[] methodData, int intExpectedMethod, int expectedLength, bool expectedResult)
|
||||
{
|
||||
var expectedMethod = (HttpMethod)intExpectedMethod;
|
||||
var data = new Span<byte>(methodData);
|
||||
var data = new ReadOnlySpan<byte>(methodData);
|
||||
|
||||
var result = data.GetKnownMethod(out var method, out var length);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -64,7 +65,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -91,7 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -117,7 +120,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -140,7 +144,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
var query = "?q=123&w=xyzw12";
|
||||
Http1Connection.Reset();
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"POST {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -184,7 +189,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
Http1Connection.Reset();
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
var prevRequestUrl = Http1Connection.RawTarget;
|
||||
var prevPath = Http1Connection.Path;
|
||||
|
|
@ -201,7 +207,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
// Parser decodes % encoding in place, so we need to recreate the ROS
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -234,7 +241,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
Http1Connection.Reset();
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _);
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Parser.ParseRequestLine(ParsingHandler, ref reader);
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -275,7 +283,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
Http1Connection.Reset();
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
var prevRequestUrl = Http1Connection.RawTarget;
|
||||
var prevPath = Http1Connection.Path;
|
||||
|
|
@ -291,7 +300,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -324,7 +334,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
Http1Connection.Reset();
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _);
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Parser.ParseRequestLine(ParsingHandler, ref reader);
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -353,7 +364,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
Http1Connection.Reset();
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
var prevRequestUrl = Http1Connection.RawTarget;
|
||||
var prevPath = Http1Connection.Path;
|
||||
|
|
@ -369,7 +381,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -400,7 +413,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
Http1Connection.Reset();
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
|
||||
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _);
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Parser.ParseRequestLine(ParsingHandler, ref reader);
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -431,7 +445,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
Http1Connection.Reset();
|
||||
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
var reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
var prevRequestUrl = Http1Connection.RawTarget;
|
||||
var prevPath = Http1Connection.Path;
|
||||
|
|
@ -447,7 +462,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
Assert.Null(Http1Connection.QueryString);
|
||||
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ros, out _, out _));
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
@ -478,7 +494,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
Http1Connection.Reset();
|
||||
ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
|
||||
Parser.ParseRequestLine(ParsingHandler, ros, out _, out _);
|
||||
reader = new SequenceReader<byte>(ros);
|
||||
Parser.ParseRequestLine(ParsingHandler, ref reader);
|
||||
|
||||
// Equal the inputs.
|
||||
Assert.Equal(rawTarget, Http1Connection.RawTarget);
|
||||
|
|
|
|||
|
|
@ -79,14 +79,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
private void ParseData()
|
||||
{
|
||||
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined))
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_parser.ParseRequestLine(new Adapter(this), ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
||||
_buffer = _buffer.Slice(consumed, _buffer.End);
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
|
||||
if (!_parser.ParseHeaders(new Adapter(this), ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
|
|
@ -112,8 +110,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public void OnHeadersComplete(bool endStream)
|
||||
=> RequestHandler.Connection.OnHeadersComplete();
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
=> RequestHandler.Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
=> RequestHandler.Connection.OnStartLine(versionAndMethod, targetPath, startLine);
|
||||
|
||||
public void OnStaticIndexedHeader(int index)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -77,12 +77,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
_http1Connection.Reset();
|
||||
|
||||
if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined))
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_http1Connection.TakeStartLine(ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestLine();
|
||||
}
|
||||
|
||||
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out consumed, out examined))
|
||||
if (!_http1Connection.TakeMessageHeaders(ref reader, trailers: false))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
|
@ -92,7 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
_http1Connection.Reset();
|
||||
|
||||
if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined))
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_http1Connection.TakeStartLine(ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestLine();
|
||||
}
|
||||
|
|
@ -102,7 +104,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
_http1Connection.Reset();
|
||||
|
||||
if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out var consumed, out var examined))
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_http1Connection.TakeMessageHeaders(ref reader, trailers: false))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMe
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||
{
|
||||
internal class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||
public class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||
{
|
||||
private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>();
|
||||
|
||||
|
|
@ -63,21 +63,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
private void ParseData()
|
||||
{
|
||||
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined))
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_parser.ParseRequestLine(new Adapter(this), ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
||||
_buffer = _buffer.Slice(consumed, _buffer.End);
|
||||
|
||||
var reader = new SequenceReader<byte>(_buffer);
|
||||
if (!_parser.ParseHeaders(new Adapter(this), ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public void OnHeadersComplete(bool endStream)
|
||||
=> RequestHandler.OnHeadersComplete(endStream);
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
=> RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||
public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
|
||||
=> RequestHandler.OnStartLine(versionAndMethod, targetPath, startLine);
|
||||
|
||||
public void OnStaticIndexedHeader(int index)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
return GetKnownMethod(data);
|
||||
}
|
||||
|
||||
private int GetKnownMethod(Span<byte> data)
|
||||
private int GetKnownMethod(ReadOnlySpan<byte> data)
|
||||
{
|
||||
int len = 0;
|
||||
HttpMethod method;
|
||||
|
|
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
int len = 0;
|
||||
HttpVersion version;
|
||||
Span<byte> data = _version;
|
||||
ReadOnlySpan<byte> data = _version;
|
||||
for (int i = 0; i < loops; i++)
|
||||
{
|
||||
data.GetKnownVersion(out version, out var length);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using HttpMethod = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;
|
||||
|
|
@ -13,7 +12,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
internal class NullParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : struct, IHttpHeadersHandler, IHttpRequestLineHandler
|
||||
{
|
||||
private readonly byte[] _startLine = Encoding.ASCII.GetBytes("GET /plaintext HTTP/1.1\r\n");
|
||||
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");
|
||||
|
|
@ -33,18 +31,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||
public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
{
|
||||
handler.OnStartLine(HttpMethod.Get,
|
||||
HttpVersion.Http11,
|
||||
new Span<byte>(_target),
|
||||
new Span<byte>(_target),
|
||||
Span<byte>.Empty,
|
||||
Span<byte>.Empty,
|
||||
false);
|
||||
Span<byte> startLine = _startLine;
|
||||
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.End;
|
||||
handler.OnStartLine(
|
||||
new HttpVersionAndMethod(HttpMethod.Get, 3) { Version = HttpVersion.Http11 },
|
||||
new TargetOffsetPathLength(3, startLine.Length - 3, false),
|
||||
startLine);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,25 +147,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
}
|
||||
|
||||
var readableBuffer = awaitable.GetAwaiter().GetResult().Buffer;
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
do
|
||||
{
|
||||
Http1Connection.Reset();
|
||||
|
||||
if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined))
|
||||
if (!Http1Connection.TakeStartLine(ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestLine();
|
||||
}
|
||||
|
||||
readableBuffer = readableBuffer.Slice(consumed);
|
||||
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||
if (!Http1Connection.TakeMessageHeaders(ref reader, trailers: false))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
||||
readableBuffer = readableBuffer.Slice(consumed);
|
||||
}
|
||||
while (readableBuffer.Length > 0);
|
||||
while (!reader.End);
|
||||
|
||||
Pipe.Reader.AdvanceTo(readableBuffer.End);
|
||||
}
|
||||
|
|
@ -183,23 +180,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
var result = awaitable.GetAwaiter().GetResult();
|
||||
var readableBuffer = result.Buffer;
|
||||
var reader = new SequenceReader<byte>(readableBuffer);
|
||||
|
||||
Http1Connection.Reset();
|
||||
|
||||
if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined))
|
||||
if (!Http1Connection.TakeStartLine(ref reader))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestLine();
|
||||
}
|
||||
Pipe.Reader.AdvanceTo(consumed, examined);
|
||||
Pipe.Reader.AdvanceTo(reader.Position, reader.Position);
|
||||
|
||||
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
readableBuffer = result.Buffer;
|
||||
reader = new SequenceReader<byte>(readableBuffer);
|
||||
|
||||
if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined))
|
||||
if (!Http1Connection.TakeMessageHeaders(ref reader, trailers: false))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
Pipe.Reader.AdvanceTo(consumed, examined);
|
||||
Pipe.Reader.AdvanceTo(reader.Position, reader.Position);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
|
||||
foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
|
||||
{
|
||||
data.Add(requestLine, CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()));
|
||||
data.Add(requestLine, CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine[..^1].EscapeNonPrintable()));
|
||||
}
|
||||
|
||||
foreach (var target in HttpParsingData.TargetWithEncodedNullCharData)
|
||||
|
|
|
|||
Loading…
Reference in New Issue