Make header parsing "safe" (#20562)

* OnHeadersComplete improve speculation and memory ordering (VTune)
This commit is contained in:
Ben Adams 2020-04-13 17:46:43 +01:00 committed by GitHub
parent e3d3da3546
commit 6016e70285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 293 additions and 148 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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") },

View File

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