Make header parsing "safe" (#20562)
* OnHeadersComplete improve speculation and memory ordering (VTune)
This commit is contained in:
parent
e3d3da3546
commit
6016e70285
|
|
@ -4,9 +4,7 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
|
|
@ -179,7 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded);
|
||||
}
|
||||
|
||||
public unsafe bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
public bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
{
|
||||
while (!reader.End)
|
||||
{
|
||||
|
|
@ -236,159 +234,229 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// in the span to contain a header.
|
||||
if (readAhead == 0)
|
||||
{
|
||||
length = span.IndexOf(ByteLF) + 1;
|
||||
if (length > 0)
|
||||
length = span.IndexOfAny(ByteCR, ByteLF);
|
||||
// If not found length with be -1; casting to uint will turn it to uint.MaxValue
|
||||
// which will be larger than any possible span.Length. This also serves to eliminate
|
||||
// the bounds check for the next lookup of span[length]
|
||||
if ((uint)length < (uint)span.Length)
|
||||
{
|
||||
// Potentially found the end, or an invalid header.
|
||||
fixed (byte* pHeader = span)
|
||||
// Early memory read to hide latency
|
||||
var expectedCR = span[length];
|
||||
// Correctly has a CR, move to next
|
||||
length++;
|
||||
|
||||
if (expectedCR != ByteCR)
|
||||
{
|
||||
TakeSingleHeader(pHeader, length, handler);
|
||||
// Sequence needs to be CRLF not LF first.
|
||||
RejectRequestHeader(span[..length]);
|
||||
}
|
||||
|
||||
if ((uint)length < (uint)span.Length)
|
||||
{
|
||||
// Early memory read to hide latency
|
||||
var expectedLF = span[length];
|
||||
// Correctly has a LF, move to next
|
||||
length++;
|
||||
|
||||
if (expectedLF != ByteLF ||
|
||||
length < 5 ||
|
||||
// Exclude the CRLF from the headerLine and parse the header name:value pair
|
||||
!TryTakeSingleHeader(handler, span[..(length - 2)]))
|
||||
{
|
||||
// Sequence needs to be CRLF and not contain an inner CR not part of terminator.
|
||||
// Less than min possible headerSpan of 5 bytes a:b\r\n
|
||||
// Not parsable as a valid name:value header pair.
|
||||
RejectRequestHeader(span[..length]);
|
||||
}
|
||||
|
||||
// Read the header successfully, skip the reader forward past the headerSpan.
|
||||
span = span.Slice(length);
|
||||
reader.Advance(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No enough data, set length to 0.
|
||||
length = 0;
|
||||
}
|
||||
// Read the header successfully, skip the reader forward past the header line.
|
||||
reader.Advance(length);
|
||||
span = span.Slice(length);
|
||||
}
|
||||
}
|
||||
|
||||
// End not found in current span
|
||||
if (length <= 0)
|
||||
// End found in current span
|
||||
if (length > 0)
|
||||
{
|
||||
// We moved the reader to look ahead 2 bytes so rewind the reader
|
||||
if (readAhead > 0)
|
||||
{
|
||||
reader.Rewind(readAhead);
|
||||
}
|
||||
|
||||
length = ParseMultiSpanHeader(handler, ref reader);
|
||||
if (length < 0)
|
||||
{
|
||||
// Not there
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.Advance(length);
|
||||
// As we crossed spans set the current span to default
|
||||
// so we move to the next span on the next iteration
|
||||
span = default;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We moved the reader to look ahead 2 bytes so rewind the reader
|
||||
if (readAhead > 0)
|
||||
{
|
||||
reader.Rewind(readAhead);
|
||||
}
|
||||
|
||||
length = ParseMultiSpanHeader(handler, ref reader);
|
||||
if (length < 0)
|
||||
{
|
||||
// Not there
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.Advance(length);
|
||||
// As we crossed spans set the current span to default
|
||||
// so we move to the next span on the next iteration
|
||||
span = default;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe int ParseMultiSpanHeader(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
private int ParseMultiSpanHeader(TRequestHandler handler, ref SequenceReader<byte> reader)
|
||||
{
|
||||
var buffer = reader.Sequence;
|
||||
var currentSlice = buffer.Slice(reader.Position, reader.Remaining);
|
||||
var lineEndPosition = currentSlice.PositionOf(ByteLF);
|
||||
// Split buffers
|
||||
var currentSlice = reader.UnreadSequence;
|
||||
var lineEndPosition = currentSlice.PositionOfAny(ByteCR, ByteLF);
|
||||
|
||||
if (lineEndPosition == null)
|
||||
{
|
||||
// Not there
|
||||
// Not there.
|
||||
return -1;
|
||||
}
|
||||
|
||||
var lineEnd = lineEndPosition.Value;
|
||||
|
||||
// Make sure LF is included in lineEnd
|
||||
lineEnd = buffer.GetPosition(1, lineEnd);
|
||||
var headerSpan = buffer.Slice(reader.Position, lineEnd).ToSpan();
|
||||
var length = headerSpan.Length;
|
||||
|
||||
fixed (byte* pHeader = headerSpan)
|
||||
SequencePosition lineEnd;
|
||||
ReadOnlySpan<byte> headerSpan;
|
||||
if (currentSlice.Slice(reader.Position, lineEndPosition.Value).Length == currentSlice.Length - 1)
|
||||
{
|
||||
TakeSingleHeader(pHeader, length, handler);
|
||||
// No enough data, so CRLF can't currently be there.
|
||||
// However, we need to check the found char is CR and not LF
|
||||
|
||||
// Advance 1 to include CR/LF in lineEnd
|
||||
lineEnd = currentSlice.GetPosition(1, lineEndPosition.Value);
|
||||
headerSpan = currentSlice.Slice(reader.Position, lineEnd).ToSpan();
|
||||
if (headerSpan[^1] != ByteCR)
|
||||
{
|
||||
RejectRequestHeader(headerSpan);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return length;
|
||||
// Advance 2 to include CR{LF?} in lineEnd
|
||||
lineEnd = currentSlice.GetPosition(2, lineEndPosition.Value);
|
||||
headerSpan = currentSlice.Slice(reader.Position, lineEnd).ToSpan();
|
||||
|
||||
if (headerSpan.Length < 5)
|
||||
{
|
||||
// Less than min possible headerSpan is 5 bytes a:b\r\n
|
||||
RejectRequestHeader(headerSpan);
|
||||
}
|
||||
|
||||
if (headerSpan[^2] != ByteCR)
|
||||
{
|
||||
// Sequence needs to be CRLF not LF first.
|
||||
RejectRequestHeader(headerSpan[..^1]);
|
||||
}
|
||||
|
||||
if (headerSpan[^1] != ByteLF ||
|
||||
// Exclude the CRLF from the headerLine and parse the header name:value pair
|
||||
!TryTakeSingleHeader(handler, headerSpan[..^2]))
|
||||
{
|
||||
// Sequence needs to be CRLF and not contain an inner CR not part of terminator.
|
||||
// Not parsable as a valid name:value header pair.
|
||||
RejectRequestHeader(headerSpan);
|
||||
}
|
||||
|
||||
return headerSpan.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe int FindEndOfName(byte* headerLine, int length)
|
||||
private static bool TryTakeSingleHeader(TRequestHandler handler, ReadOnlySpan<byte> headerLine)
|
||||
{
|
||||
var index = 0;
|
||||
var sawWhitespace = false;
|
||||
for (; index < length; index++)
|
||||
// We are looking for a colon to terminate the header name.
|
||||
// However, the header name cannot contain a space or tab so look for all three
|
||||
// and see which is found first.
|
||||
var nameEnd = headerLine.IndexOfAny(ByteColon, ByteSpace, ByteTab);
|
||||
// If not found length with be -1; casting to uint will turn it to uint.MaxValue
|
||||
// which will be larger than any possible headerLine.Length. This also serves to eliminate
|
||||
// the bounds check for the next lookup of headerLine[nameEnd]
|
||||
if ((uint)nameEnd >= (uint)headerLine.Length)
|
||||
{
|
||||
var ch = headerLine[index];
|
||||
if (ch == ByteColon)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (ch == ByteTab || ch == ByteSpace || ch == ByteCR)
|
||||
{
|
||||
sawWhitespace = true;
|
||||
}
|
||||
// Colon not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == length || sawWhitespace)
|
||||
// Early memory read to hide latency
|
||||
var expectedColon = headerLine[nameEnd];
|
||||
if (nameEnd == 0)
|
||||
{
|
||||
// Set to -1 to indicate invalid.
|
||||
index = -1;
|
||||
// Header name is empty.
|
||||
return false;
|
||||
}
|
||||
if (expectedColon != ByteColon)
|
||||
{
|
||||
// Header name space or tab.
|
||||
return false;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void TakeSingleHeader(byte* headerLine, int length, TRequestHandler handler)
|
||||
{
|
||||
// Skip CR, LF from end position
|
||||
var valueEnd = length - 3;
|
||||
var nameEnd = FindEndOfName(headerLine, length);
|
||||
|
||||
// Header name is empty, invalid, or doesn't end in CRLF
|
||||
if (nameEnd <= 0 || headerLine[valueEnd + 2] != ByteLF || headerLine[valueEnd + 1] != ByteCR)
|
||||
{
|
||||
RejectRequestHeader(headerLine, length);
|
||||
}
|
||||
|
||||
// Skip colon from value start
|
||||
// Skip colon to get to the value start.
|
||||
var valueStart = nameEnd + 1;
|
||||
// Ignore start whitespace
|
||||
for (; valueStart < valueEnd; valueStart++)
|
||||
|
||||
// Generally there will only be one space, so we will check it directly
|
||||
if ((uint)valueStart < (uint)headerLine.Length)
|
||||
{
|
||||
var ch = headerLine[valueStart];
|
||||
if (ch != ByteTab && ch != ByteSpace && ch != ByteCR)
|
||||
if (ch == ByteSpace || ch == ByteTab)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ch == ByteCR)
|
||||
{
|
||||
RejectRequestHeader(headerLine, length);
|
||||
// Ignore first whitespace.
|
||||
valueStart++;
|
||||
|
||||
// More header chars?
|
||||
if ((uint)valueStart < (uint)headerLine.Length)
|
||||
{
|
||||
ch = headerLine[valueStart];
|
||||
// Do a fast check; as we now expect non-space, before moving into loop.
|
||||
if (ch <= ByteSpace && (ch == ByteSpace || ch == ByteTab))
|
||||
{
|
||||
valueStart++;
|
||||
// Is more whitespace, so we will loop to find the end. This is the slow path.
|
||||
for (; valueStart < headerLine.Length; valueStart++)
|
||||
{
|
||||
ch = headerLine[valueStart];
|
||||
if (ch != ByteTab && ch != ByteSpace)
|
||||
{
|
||||
// Non-whitespace char found, valueStart is now start of value.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for CR in value
|
||||
var valueBuffer = new Span<byte>(headerLine + valueStart, valueEnd - valueStart + 1);
|
||||
if (valueBuffer.Contains(ByteCR))
|
||||
{
|
||||
RejectRequestHeader(headerLine, length);
|
||||
}
|
||||
|
||||
// Ignore end whitespace
|
||||
var lengthChanged = false;
|
||||
for (; valueEnd >= valueStart; valueEnd--)
|
||||
var valueEnd = headerLine.Length - 1;
|
||||
// Ignore end whitespace. Generally there will no spaces
|
||||
// so we will check the first before moving to a loop.
|
||||
if (valueEnd > valueStart)
|
||||
{
|
||||
var ch = headerLine[valueEnd];
|
||||
if (ch != ByteTab && ch != ByteSpace)
|
||||
// Do a fast check; as we now expect non-space, before moving into loop.
|
||||
if (ch <= ByteSpace && (ch == ByteSpace || ch == ByteTab))
|
||||
{
|
||||
break;
|
||||
// Is whitespace so move to loop
|
||||
valueEnd--;
|
||||
for (; valueEnd > valueStart; valueEnd--)
|
||||
{
|
||||
ch = headerLine[valueEnd];
|
||||
if (ch != ByteTab && ch != ByteSpace)
|
||||
{
|
||||
// Non-whitespace char found, valueEnd is now start of value.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lengthChanged = true;
|
||||
}
|
||||
|
||||
if (lengthChanged)
|
||||
{
|
||||
// Length changed
|
||||
valueBuffer = new Span<byte>(headerLine + valueStart, valueEnd - valueStart + 1);
|
||||
}
|
||||
// Range end is exclusive, so add 1 to valueEnd
|
||||
valueEnd++;
|
||||
handler.OnHeader(name: headerLine[..nameEnd], value: headerLine[valueStart..valueEnd]);
|
||||
|
||||
var nameBuffer = new Span<byte>(headerLine, nameEnd);
|
||||
|
||||
handler.OnHeader(nameBuffer, valueBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
|
|
@ -445,8 +513,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
private unsafe void RejectRequestHeader(byte* headerLine, int length)
|
||||
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine, length);
|
||||
private void RejectRequestHeader(ReadOnlySpan<byte> headerLine)
|
||||
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine);
|
||||
|
||||
[StackTraceHidden]
|
||||
private unsafe void RejectUnknownVersion(byte* version, int length)
|
||||
|
|
@ -454,10 +522,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
|
||||
=> GetInvalidRequestException(reason, new ReadOnlySpan<byte>(detail, length));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, ReadOnlySpan<byte> headerLine)
|
||||
=> KestrelBadHttpRequestException.GetException(
|
||||
reason,
|
||||
_showErrorDetails
|
||||
? new Span<byte>(detail, length).GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
|
||||
? headerLine.GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
|
||||
: string.Empty);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
BeginRequestProcessing();
|
||||
|
||||
var result = default(ReadResult);
|
||||
var endConnection = false;
|
||||
bool endConnection;
|
||||
do
|
||||
{
|
||||
if (BeginRead(out var awaitable))
|
||||
|
|
@ -1274,7 +1274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
=> throw GetInvalidRequestTargetException(target);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target)
|
||||
private BadHttpRequestException GetInvalidRequestTargetException(ReadOnlySpan<byte> target)
|
||||
=> KestrelBadHttpRequestException.GetException(
|
||||
RequestRejectionReason.InvalidRequestTarget,
|
||||
Log.IsEnabled(LogLevel.Information)
|
||||
|
|
|
|||
|
|
@ -27,18 +27,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public void OnHeadersComplete()
|
||||
{
|
||||
var bitsToClear = _previousBits & ~_bits;
|
||||
var newHeaderFlags = _bits;
|
||||
var previousHeaderFlags = _previousBits;
|
||||
_previousBits = 0;
|
||||
|
||||
if (bitsToClear != 0)
|
||||
var headersToClear = (~newHeaderFlags) & previousHeaderFlags;
|
||||
if (headersToClear == 0)
|
||||
{
|
||||
// Some previous headers were not reused or overwritten.
|
||||
|
||||
// While they cannot be accessed by the current request (as they were not supplied by it)
|
||||
// there is no point in holding on to them, so clear them now,
|
||||
// to allow them to get collected by the GC.
|
||||
Clear(bitsToClear);
|
||||
// All headers were resued.
|
||||
return;
|
||||
}
|
||||
|
||||
// Some previous headers were not reused or overwritten.
|
||||
// While they cannot be accessed by the current request (as they were not supplied by it)
|
||||
// there is no point in holding on to them, so clear them now,
|
||||
// to allow them to get collected by the GC.
|
||||
Clear(headersToClear);
|
||||
}
|
||||
|
||||
protected override void ClearFast()
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
public static string GetRequestHeaderStringNonNullCharacters(this ReadOnlySpan<byte> span, bool useLatin1) =>
|
||||
useLatin1 ? GetLatin1StringNonNullCharacters(span) : GetAsciiOrUTF8StringNonNullCharacters(span);
|
||||
|
||||
public static string GetAsciiStringEscaped(this Span<byte> span, int maxChars)
|
||||
public static string GetAsciiStringEscaped(this ReadOnlySpan<byte> span, int maxChars)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
|
|
|
|||
|
|
@ -429,6 +429,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(RequestRejectionReason.TlsOverHttpError, badHttpRequestException.Reason);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestHeaderInvalidData))]
|
||||
public void ParseHeadersThrowsOnInvalidRequestHeadersWithGratuitouslySplitBuffers(string rawHeaders, string expectedExceptionMessage)
|
||||
{
|
||||
var mockTrace = new Mock<IKestrelTrace>();
|
||||
mockTrace
|
||||
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||
.Returns(true);
|
||||
|
||||
var parser = CreateParser(mockTrace.Object);
|
||||
var buffer = BytePerSegmentTestSequenceFactory.Instance.CreateWithContent(rawHeaders);
|
||||
var requestHandler = new RequestHandler();
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
var reader = new SequenceReader<byte>(buffer);
|
||||
parser.ParseHeaders(requestHandler, ref reader);
|
||||
});
|
||||
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseHeadersWithGratuitouslySplitBuffers()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -377,19 +377,19 @@ namespace Microsoft.AspNetCore.Testing
|
|||
new[] { "Header-1: value1\r\n\tHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09Header-2: value2\x0D\x0A") },
|
||||
|
||||
// CR in value
|
||||
new[] { "Header-1: value1\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: val\rue1\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: val\x0Due1\x0D\x0A") },
|
||||
new[] { "Header-1: value1\rHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0DHeader-2: value2\x0D\x0A") },
|
||||
new[] { "Header-1: value1\r\nHeader-2: value2\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: value2\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: value1\r\nHeader-2: v\ralue2\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: v\x0Dalue2\x0D\x0A") },
|
||||
new[] { "Header-1: Value__\rVector16________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value__\x0DVector16________Vector32\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16\r________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16\x0D________Vector32\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16_______\x0DVector32\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32_\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16_______\x0DVector32\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\x0D\x0D\x0A") },
|
||||
new[] { "Header-1: value1\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0D\x0D") },
|
||||
new[] { "Header-1: val\rue1\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: val\x0Du") },
|
||||
new[] { "Header-1: value1\rHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0DH") },
|
||||
new[] { "Header-1: value1\r\nHeader-2: value2\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: value2\x0D\x0D") },
|
||||
new[] { "Header-1: value1\r\nHeader-2: v\ralue2\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: v\x0Da") },
|
||||
new[] { "Header-1: Value__\rVector16________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value__\x0DV") },
|
||||
new[] { "Header-1: Value___Vector16\r________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16\x0D_") },
|
||||
new[] { "Header-1: Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16_______\x0DV") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32\x0D\x0D") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32_\x0D\x0D") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16_______\x0DV") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\x0D\x0D") },
|
||||
new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\x0D\x0D") },
|
||||
|
||||
// Missing colon
|
||||
new[] { "Header-1 value1\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1 value1\x0D\x0A") },
|
||||
|
|
@ -406,20 +406,20 @@ namespace Microsoft.AspNetCore.Testing
|
|||
// Whitespace in header name
|
||||
new[] { "Header : value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header : value\x0D\x0A") },
|
||||
new[] { "Header\t: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header\x09: value\x0D\x0A") },
|
||||
new[] { "Header\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header\x0D: value\x0D\x0A") },
|
||||
new[] { "Header_\rVector16: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DVector16: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16\x0D: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16_\x0D: value\x0D\x0A") },
|
||||
new[] { "Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DVector16________Vector32: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32\x0D: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32_\x0D: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16________Vector32Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header_\x0DVector16________Vector32: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16________Vector32Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32\x0D: value\x0D\x0A") },
|
||||
new[] { "Header__Vector16________Vector32Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32_\x0D: value\x0D\x0A") },
|
||||
new[] { "Header\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header\x0D:") },
|
||||
new[] { "Header_\rVector16: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DV") },
|
||||
new[] { "Header__Vector16\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16\x0D:") },
|
||||
new[] { "Header__Vector16_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16_\x0D:") },
|
||||
new[] { "Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DV") },
|
||||
new[] { "Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32\x0D:") },
|
||||
new[] { "Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32_\x0D:") },
|
||||
new[] { "Header__Vector16________Vector32Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header_\x0DV") },
|
||||
new[] { "Header__Vector16________Vector32Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32\x0D:") },
|
||||
new[] { "Header__Vector16________Vector32Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32_\x0D:") },
|
||||
new[] { "Header 1: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1: value1\x0D\x0A") },
|
||||
new[] { "Header 1 : value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1 : value1\x0D\x0A") },
|
||||
new[] { "Header 1\t: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1\x09: value1\x0D\x0A") },
|
||||
new[] { "Header 1\r: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1\x0D: value1\x0D\x0A") },
|
||||
new[] { "Header 1\r: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1\x0D:") },
|
||||
new[] { "Header-1: value1\r\nHeader 2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 2: value2\x0D\x0A") },
|
||||
new[] { "Header-1: value1\r\nHeader-2 : value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2 : value2\x0D\x0A") },
|
||||
new[] { "Header-1: value1\r\nHeader-2\t: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2\x09: value2\x0D\x0A") },
|
||||
|
|
|
|||
|
|
@ -40,6 +40,50 @@ namespace System.Buffers
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns position of first occurrence of item in the <see cref="ReadOnlySequence{T}"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SequencePosition? PositionOfAny<T>(in this ReadOnlySequence<T> source, T value0, T value1) where T : IEquatable<T>
|
||||
{
|
||||
if (source.IsSingleSegment)
|
||||
{
|
||||
int index = source.First.Span.IndexOfAny(value0, value1);
|
||||
if (index != -1)
|
||||
{
|
||||
return source.GetPosition(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PositionOfAnyMultiSegment(source, value0, value1);
|
||||
}
|
||||
}
|
||||
|
||||
private static SequencePosition? PositionOfAnyMultiSegment<T>(in ReadOnlySequence<T> source, T value0, T value1) where T : IEquatable<T>
|
||||
{
|
||||
SequencePosition position = source.Start;
|
||||
SequencePosition result = position;
|
||||
while (source.TryGet(ref position, out ReadOnlyMemory<T> memory))
|
||||
{
|
||||
int index = memory.Span.IndexOfAny(value0, value1);
|
||||
if (index != -1)
|
||||
{
|
||||
return source.GetPosition(index, result);
|
||||
}
|
||||
else if (position.GetObject() == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result = position;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static void WriteAscii(ref this BufferWriter<PipeWriter> buffer, string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
|
|
|
|||
Loading…
Reference in New Issue