Make HTTP/1.1 startline parsing "safe" (#20885)

This commit is contained in:
Ben Adams 2020-04-24 02:32:54 +01:00 committed by GitHub
parent 351359f56d
commit f5fa16e998
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 542 additions and 411 deletions

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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)
{

View File

@ -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)

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum HttpVersion
public enum HttpVersion : sbyte
{
Unknown = -1,
Http10 = 0,

View File

@ -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);
}

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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();
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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)