Revert "Use Spans to parse the start line and headers (#1394)"

This reverts commit 8140b8cdfe.
This commit is contained in:
David Fowler 2017-02-24 10:03:32 -08:00
parent 4544f881a2
commit 19c3107deb
10 changed files with 225 additions and 412 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26206.0 VisualStudioVersion = 15.0.26213.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject

View File

@ -3,7 +3,7 @@
<Import Project="..\..\build\common.props" /> <Import Project="..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net451</TargetFrameworks> <TargetFrameworks>net451;netcoreapp1.1</TargetFrameworks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved --> <!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved -->
<RuntimeIdentifier Condition="'$(TargetFramework)'!='netcoreapp1.1'">win7-x64</RuntimeIdentifier> <RuntimeIdentifier Condition="'$(TargetFramework)'!='netcoreapp1.1'">win7-x64</RuntimeIdentifier>

View File

@ -997,16 +997,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_requestProcessingStatus = RequestProcessingStatus.RequestStarted; _requestProcessingStatus = RequestProcessingStatus.RequestStarted;
ReadableBuffer limitedBuffer; var limitedBuffer = buffer;
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize) if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
{ {
limitedBuffer = buffer.Slice(0, ServerOptions.Limits.MaxRequestLineSize); limitedBuffer = buffer.Slice(0, ServerOptions.Limits.MaxRequestLineSize);
} }
else
{
limitedBuffer = buffer;
}
if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out end, ByteLF) == -1) if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out end, ByteLF) == -1)
{ {
if (limitedBuffer.Length == ServerOptions.Limits.MaxRequestLineSize) if (limitedBuffer.Length == ServerOptions.Limits.MaxRequestLineSize)
@ -1019,46 +1014,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
const int stackAllocLimit = 512; end = buffer.Move(end, 1);
ReadCursor methodEnd;
// Move 1 byte past the \r string method;
end = limitedBuffer.Move(end, 1); if (!buffer.GetKnownMethod(out method))
var startLineBuffer = limitedBuffer.Slice(0, end);
Span<byte> span;
if (startLineBuffer.IsSingleSpan)
{ {
// No copies, directly use the one and only span if (ReadCursorOperations.Seek(buffer.Start, end, out methodEnd, ByteSpace) == -1)
span = startLineBuffer.ToSpan();
}
else if (startLineBuffer.Length < stackAllocLimit)
{
unsafe
{
// Multiple buffers and < stackAllocLimit, copy into a stack buffer
byte* stackBuffer = stackalloc byte[startLineBuffer.Length];
span = new Span<byte>(stackBuffer, startLineBuffer.Length);
startLineBuffer.CopyTo(span);
}
}
else
{
// We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
span = new Span<byte>(new byte[startLineBuffer.Length]);
startLineBuffer.CopyTo(span);
}
var methodEnd = 0;
if (!span.GetKnownMethod(out string method))
{
methodEnd = span.IndexOf(ByteSpace);
if (methodEnd == -1)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
method = span.Slice(0, methodEnd).GetAsciiString(); method = buffer.Slice(buffer.Start, methodEnd).GetAsciiString();
if (method == null) if (method == null)
{ {
@ -1077,67 +1043,57 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else else
{ {
methodEnd += method.Length; methodEnd = buffer.Slice(method.Length).Start;
} }
var needDecode = false; var needDecode = false;
var pathBegin = methodEnd + 1; ReadCursor pathEnd;
var pathToEndSpan = span.Slice(pathBegin, span.Length - pathBegin);
pathBegin = 0;
// TODO: IndexOfAny var pathBegin = buffer.Move(methodEnd, 1);
var spaceIndex = pathToEndSpan.IndexOf(ByteSpace);
var questionMarkIndex = pathToEndSpan.IndexOf(ByteQuestionMark);
var percentageIndex = pathToEndSpan.IndexOf(BytePercentage);
var pathEnd = MinNonZero(spaceIndex, questionMarkIndex, percentageIndex); var chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark, BytePercentage);
if (chFound == -1)
if (spaceIndex == -1 && questionMarkIndex == -1 && percentageIndex == -1)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
else if (percentageIndex != -1) else if (chFound == BytePercentage)
{ {
needDecode = true; needDecode = true;
chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark);
pathEnd = MinNonZero(spaceIndex, questionMarkIndex); if (chFound == -1)
if (questionMarkIndex == -1 && spaceIndex == -1)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
} };
var queryString = ""; var queryString = "";
var queryEnd = pathEnd; ReadCursor queryEnd = pathEnd;
if (questionMarkIndex != -1) if (chFound == ByteQuestionMark)
{ {
queryEnd = spaceIndex; if (ReadCursorOperations.Seek(pathEnd, end, out queryEnd, ByteSpace) == -1)
if (spaceIndex == -1)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
queryString = buffer.Slice(pathEnd, queryEnd).GetAsciiString();
queryString = pathToEndSpan.Slice(pathEnd, queryEnd - pathEnd).GetAsciiString();
} }
// No path
if (pathBegin == pathEnd) if (pathBegin == pathEnd)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
var versionBegin = queryEnd + 1; ReadCursor versionEnd;
var versionToEndSpan = pathToEndSpan.Slice(versionBegin, pathToEndSpan.Length - versionBegin); if (ReadCursorOperations.Seek(queryEnd, end, out versionEnd, ByteCR) == -1)
versionBegin = 0;
var versionEnd = versionToEndSpan.IndexOf(ByteCR);
if (versionEnd == -1)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
if (!versionToEndSpan.GetKnownVersion(out string httpVersion)) string httpVersion;
var versionBuffer = buffer.Slice(queryEnd, end).Slice(1);
if (!versionBuffer.GetKnownVersion(out httpVersion))
{ {
httpVersion = versionToEndSpan.Slice(0, versionEnd).GetAsciiStringEscaped(); httpVersion = versionBuffer.Start.GetAsciiStringEscaped(versionEnd, 9);
if (httpVersion == string.Empty) if (httpVersion == string.Empty)
{ {
@ -1149,13 +1105,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
if (versionToEndSpan[versionEnd + 1] != ByteLF) var lineEnd = buffer.Slice(versionEnd, 2).ToSpan();
if (lineEnd[1] != ByteLF)
{ {
RejectRequestLine(start, end); RejectRequestLine(start, end);
} }
var pathBuffer = pathToEndSpan.Slice(pathBegin, pathEnd); var pathBuffer = buffer.Slice(pathBegin, pathEnd);
var targetBuffer = pathToEndSpan.Slice(pathBegin, queryEnd); var targetBuffer = buffer.Slice(pathBegin, queryEnd);
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
@ -1168,7 +1125,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
rawTarget = targetBuffer.GetAsciiString() ?? string.Empty; rawTarget = targetBuffer.GetAsciiString() ?? string.Empty;
// URI was encoded, unescape and then parse as utf8 // URI was encoded, unescape and then parse as utf8
var pathSpan = pathBuffer; var pathSpan = pathBuffer.ToSpan();
int pathLength = UrlEncoder.Decode(pathSpan, pathSpan); int pathLength = UrlEncoder.Decode(pathSpan, pathSpan);
requestUrlPath = new Utf8String(pathSpan.Slice(0, pathLength)).ToString(); requestUrlPath = new Utf8String(pathSpan.Slice(0, pathLength)).ToString();
} }
@ -1218,21 +1175,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return true; return true;
} }
private int MinNonZero(int v1, int v2)
{
v1 = v1 == -1 ? int.MaxValue : v1;
v2 = v2 == -1 ? int.MaxValue : v2;
return Math.Min(v1, v2);
}
private int MinNonZero(int v1, int v2, int v3)
{
v1 = v1 == -1 ? int.MaxValue : v1;
v2 = v2 == -1 ? int.MaxValue : v2;
v3 = v3 == -1 ? int.MaxValue : v3;
return Math.Min(Math.Min(v1, v2), v3);
}
private void RejectRequestLine(ReadCursor start, ReadCursor end) private void RejectRequestLine(ReadCursor start, ReadCursor end)
{ {
const int MaxRequestLineError = 32; const int MaxRequestLineError = 32;
@ -1302,41 +1244,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
consumed = buffer.Start; consumed = buffer.Start;
examined = buffer.End; examined = buffer.End;
var bufferLength = buffer.Length;
var reader = new ReadableBufferReader(buffer);
while (true) while (true)
{ {
var start = reader; var headersEnd = buffer.Slice(0, Math.Min(buffer.Length, 2));
int ch1 = reader.Take(); var headersEndSpan = headersEnd.ToSpan();
var ch2 = reader.Take();
if (ch1 == -1) if (headersEndSpan.Length == 0)
{ {
return false; return false;
} }
else
if (ch1 == ByteCR)
{ {
// Check for final CRLF. var ch = headersEndSpan[0];
if (ch2 == -1) if (ch == ByteCR)
{ {
return false; // Check for final CRLF.
} if (headersEndSpan.Length < 2)
else if (ch2 == ByteLF) {
{ return false;
consumed = reader.Cursor; }
examined = consumed; else if (headersEndSpan[1] == ByteLF)
ConnectionControl.CancelTimeout(); {
return true; consumed = headersEnd.End;
} examined = consumed;
ConnectionControl.CancelTimeout();
return true;
}
// Headers don't end in CRLF line. // Headers don't end in CRLF line.
RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence); RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
} }
else if (ch1 == ByteSpace || ch1 == ByteTab) else if (ch == ByteSpace || ch == ByteTab)
{ {
RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace); RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
}
} }
// If we've parsed the max allowed numbers of headers and we're starting a new // If we've parsed the max allowed numbers of headers and we're starting a new
@ -1346,30 +1287,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
RejectRequest(RequestRejectionReason.TooManyHeaders); RejectRequest(RequestRejectionReason.TooManyHeaders);
} }
// Reset the reader since we're not at the end of headers ReadCursor lineEnd;
reader = start; var limitedBuffer = buffer;
if (buffer.Length >= _remainingRequestHeadersBytesAllowed)
// Now parse a single header
ReadableBuffer limitedBuffer;
var overLength = false;
if (bufferLength >= _remainingRequestHeadersBytesAllowed)
{ {
limitedBuffer = buffer.Slice(consumed, _remainingRequestHeadersBytesAllowed); limitedBuffer = buffer.Slice(0, _remainingRequestHeadersBytesAllowed);
// If we sliced it means the current buffer bigger than what we're
// allowed to look at
overLength = true;
} }
else if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out lineEnd, ByteLF) == -1)
{ {
limitedBuffer = buffer; if (limitedBuffer.Length == _remainingRequestHeadersBytesAllowed)
}
if (ReadCursorOperations.Seek(consumed, limitedBuffer.End, out var lineEnd, ByteLF) == -1)
{
// We didn't find a \n in the current buffer and we had to slice it so it's an issue
if (overLength)
{ {
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize); RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
} }
@ -1379,94 +1305,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
const int stackAllocLimit = 512; var beginName = buffer.Start;
ReadCursor endName;
if (lineEnd != limitedBuffer.End) if (ReadCursorOperations.Seek(buffer.Start, lineEnd, out endName, ByteColon) == -1)
{
lineEnd = limitedBuffer.Move(lineEnd, 1);
}
var headerBuffer = limitedBuffer.Slice(consumed, lineEnd);
Span<byte> span;
if (headerBuffer.IsSingleSpan)
{
// No copies, directly use the one and only span
span = headerBuffer.ToSpan();
}
else if (headerBuffer.Length < stackAllocLimit)
{
unsafe
{
// Multiple buffers and < stackAllocLimit, copy into a stack buffer
byte* stackBuffer = stackalloc byte[headerBuffer.Length];
span = new Span<byte>(stackBuffer, headerBuffer.Length);
headerBuffer.CopyTo(span);
}
}
else
{
// We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
span = new Span<byte>(new byte[headerBuffer.Length]);
headerBuffer.CopyTo(span);
}
int endNameIndex = span.IndexOf(ByteColon);
if (endNameIndex == -1)
{ {
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine); RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
} }
var nameBuffer = span.Slice(0, endNameIndex); ReadCursor whitespace;
if (nameBuffer.IndexOf(ByteSpace) != -1 || nameBuffer.IndexOf(ByteTab) != -1) if (ReadCursorOperations.Seek(beginName, endName, out whitespace, ByteTab, ByteSpace) != -1)
{ {
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName); RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
} }
int endValueIndex = span.IndexOf(ByteCR); ReadCursor endValue;
if (endValueIndex == -1) if (ReadCursorOperations.Seek(beginName, lineEnd, out endValue, ByteCR) == -1)
{ {
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine); RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
} }
var lineSuffix = span.Slice(endValueIndex); var lineSufix = buffer.Slice(endValue);
if (lineSuffix.Length < 2) if (lineSufix.Length < 3)
{ {
return false; return false;
} }
lineSufix = lineSufix.Slice(0, 3); // \r\n\r
var lineSufixSpan = lineSufix.ToSpan();
// This check and MissingCRInHeaderLine is a bit backwards, we should do it at once instead of having another seek // This check and MissingCRInHeaderLine is a bit backwards, we should do it at once instead of having another seek
if (lineSuffix[1] != ByteLF) if (lineSufixSpan[1] != ByteLF)
{ {
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR); RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
} }
// Trim trailing whitespace from header value by repeatedly advancing to next var next = lineSufixSpan[2];
// whitespace or CR.
//
// - If CR is found, this is the end of the header value.
// - If whitespace is found, this is the _tentative_ end of the header value.
// If non-whitespace is found after it and it's not CR, seek again to the next
// whitespace or CR for a new (possibly tentative) end of value.
var valueBuffer = span.Slice(endNameIndex + 1, endValueIndex - (endNameIndex + 1));
// TODO: Trim else where
var value = valueBuffer.GetAsciiString()?.Trim() ?? string.Empty;
var headerLineLength = span.Length;
// -1 so that we can re-check the extra \r
reader.Skip(headerLineLength);
var next = reader.Peek();
// We cant check for line continuations to reject everything we've done so far
if (next == -1)
{
return false;
}
if (next == ByteSpace || next == ByteTab) if (next == ByteSpace || next == ByteTab)
{ {
// From https://tools.ietf.org/html/rfc7230#section-3.2.4: // From https://tools.ietf.org/html/rfc7230#section-3.2.4:
@ -1489,15 +1360,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported); RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
} }
// Update the frame state only after we know there's no header line continuation // Trim trailing whitespace from header value by repeatedly advancing to next
_remainingRequestHeadersBytesAllowed -= headerLineLength; // whitespace or CR.
bufferLength -= headerLineLength; //
// - If CR is found, this is the end of the header value.
// - If whitespace is found, this is the _tentative_ end of the header value.
// If non-whitespace is found after it and it's not CR, seek again to the next
// whitespace or CR for a new (possibly tentative) end of value.
var nameBuffer = buffer.Slice(beginName, endName);
// TODO: TrimStart and TrimEnd are pretty slow
var valueBuffer = buffer.Slice(endName, endValue).Slice(1).TrimStart().TrimEnd();
var name = nameBuffer.ToArraySegment();
var value = valueBuffer.GetAsciiString();
lineEnd = limitedBuffer.Move(lineEnd, 1);
// TODO: bad
_remainingRequestHeadersBytesAllowed -= buffer.Slice(0, lineEnd).Length;
_requestHeadersParsed++; _requestHeadersParsed++;
requestHeaders.Append(nameBuffer, value); requestHeaders.Append(name.Array, name.Offset, name.Count, value);
buffer = buffer.Slice(lineEnd);
consumed = reader.Cursor; consumed = buffer.Start;
} }
} }

View File

@ -3501,10 +3501,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
public unsafe void Append(byte* pKeyBytes, int keyLength, string value) public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
{ {
var pUB = pKeyBytes; fixed (byte* ptr = &keyBytes[keyOffset])
var pUL = (ulong*)pUB; {
var pUB = ptr;
var pUL = (ulong*)pUB;
var pUI = (uint*)pUB; var pUI = (uint*)pUB;
var pUS = (ushort*)pUB; var pUS = (ushort*)pUB;
var stringValue = new StringValues(value); var stringValue = new StringValues(value);
@ -3565,10 +3567,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
break; break;
} }
AppendNonPrimaryHeaders(pKeyBytes, keyLength, value); AppendNonPrimaryHeaders(ptr, keyOffset, keyLength, value);
}
} }
private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyLength, string value) private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyOffset, int keyLength, string value)
{ {
var pUB = pKeyBytes; var pUB = pKeyBytes;
var pUL = (ulong*)pUB; var pUL = (ulong*)pUB;

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -30,14 +29,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Unknown[key] = value; Unknown[key] = value;
} }
public unsafe void Append(Span<byte> name, string value)
{
fixed (byte* namePtr = &name.DangerousGetPinnableReference())
{
Append(namePtr, name.Length, value);
}
}
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void AppendUnknownHeaders(byte* pKeyBytes, int keyLength, string value) private unsafe void AppendUnknownHeaders(byte* pKeyBytes, int keyLength, string value)
{ {

View File

@ -90,19 +90,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return sb.ToString(); return sb.ToString();
} }
public static string GetAsciiStringEscaped(this Span<byte> span)
{
var sb = new StringBuilder();
for (var i = 0; i < span.Length; ++i)
{
var ch = span[i];
sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch:X2}>" : ((char)ch).ToString());
}
return sb.ToString();
}
public static ArraySegment<byte> PeekArraySegment(this MemoryPoolIterator iter) public static ArraySegment<byte> PeekArraySegment(this MemoryPoolIterator iter)
{ {
if (iter.IsDefault || iter.IsEnd) if (iter.IsDefault || iter.IsEnd)
@ -170,33 +157,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return false; return false;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownMethod(this Span<byte> span, out string knownMethod)
{
knownMethod = null;
if (span.Length < sizeof(ulong))
{
return false;
}
ulong value = span.Read<ulong>();
if ((value & _mask4Chars) == _httpGetMethodLong)
{
knownMethod = HttpMethods.Get;
return true;
}
foreach (var x in _knownMethods)
{
if ((value & x.Item1) == x.Item2)
{
knownMethod = x.Item3;
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Checks 9 bytes from <paramref name="begin"/> correspond to a known HTTP version. /// Checks 9 bytes from <paramref name="begin"/> correspond to a known HTTP version.
/// </summary> /// </summary>
@ -240,36 +200,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return knownVersion != null; return knownVersion != null;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownVersion(this Span<byte> span, out string knownVersion)
{
knownVersion = null;
if (span.Length < sizeof(ulong))
{
return false;
}
var value = span.Read<ulong>();
if (value == _http11VersionLong)
{
knownVersion = Http11Version;
}
else if (value == _http10VersionLong)
{
knownVersion = Http10Version;
}
if (knownVersion != null)
{
if (span[sizeof(ulong)] != (byte)'\r')
{
knownVersion = null;
}
}
return knownVersion != null;
}
} }
} }

View File

@ -311,7 +311,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var encoding = Encoding.GetEncoding("iso-8859-1"); var encoding = Encoding.GetEncoding("iso-8859-1");
var exception = Assert.Throws<BadHttpRequestException>( var exception = Assert.Throws<BadHttpRequestException>(
() => headers.Append(encoding.GetBytes(key), key)); () => headers.Append(encoding.GetBytes(key), 0, encoding.GetByteCount(key), key));
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
} }

View File

@ -611,7 +611,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined)); var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined); _socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Unrecognized HTTP version: HTTP/1.1ab", exception.Message); Assert.Equal("Unrecognized HTTP version: HTTP/1.1a...", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode); Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
} }

View File

@ -564,15 +564,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}}" : "")}")} }}" : "")}")}
}}" : "")} }}" : "")}
{(loop.ClassName == "FrameRequestHeaders" ? $@" {(loop.ClassName == "FrameRequestHeaders" ? $@"
public unsafe void Append(byte* pKeyBytes, int keyLength, string value) public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
{{ {{
var pUB = pKeyBytes; fixed (byte* ptr = &keyBytes[keyOffset])
{AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)} {{
var pUB = ptr;
{AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
AppendNonPrimaryHeaders(pKeyBytes, keyLength, value); AppendNonPrimaryHeaders(ptr, keyOffset, keyLength, value);
}}
}} }}
private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyLength, string value) private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyOffset, int keyLength, string value)
{{ {{
var pUB = pKeyBytes; var pUB = pKeyBytes;
{AppendSwitch(loop.Headers.Where(h => !h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)} {AppendSwitch(loop.Headers.Where(h => !h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}