Made changes to TakeSingleHeader (#1453)

* Made changes to TakeSingleHeader
- Remove state machine and just parse in place
- Inline OnHeader into TakeSingleHeader
- Use IndexOfVectorized instead of custom indexof
- Normalize header whitespace error
- Combine IndexOf and IndexOfAny into a single IndexOfNameEnd call
This commit is contained in:
David Fowler 2017-03-07 08:58:55 -08:00 committed by GitHub
parent 06134bc6e0
commit 02a4342908
5 changed files with 131 additions and 152 deletions

View File

@ -25,9 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
case RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence:
ex = new BadHttpRequestException("Headers corrupted, invalid header sequence.", StatusCodes.Status400BadRequest);
break;
case RequestRejectionReason.HeaderLineMustNotStartWithWhitespace:
ex = new BadHttpRequestException("Header line must not start with whitespace.", StatusCodes.Status400BadRequest);
break;
case RequestRejectionReason.NoColonCharacterFoundInHeaderLine:
ex = new BadHttpRequestException("No ':' character found in header line.", StatusCodes.Status400BadRequest);
break;

View File

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
@ -316,9 +317,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Headers don't end in CRLF line.
RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
}
else if (ch1 == ByteSpace || ch1 == ByteTab)
else if(ch1 == ByteSpace || ch1 == ByteTab)
{
RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
// We moved the reader so look ahead 2 bytes so reset both the reader
@ -329,13 +330,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
index = reader.Index;
}
Span<byte> headerSpan;
var endIndex = IndexOf(pBuffer, index, remaining, ByteLF);
var endIndex = new Span<byte>(pBuffer + index, remaining).IndexOfVectorized(ByteLF);
var length = 0;
if (endIndex != -1)
{
headerSpan = new Span<byte>(pBuffer + index, (endIndex - index + 1));
length = endIndex + 1;
var pHeader = pBuffer + index;
TakeSingleHeader(pHeader, length, handler);
}
else
{
@ -350,28 +353,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Make sure LF is included in lineEnd
lineEnd = buffer.Move(lineEnd, 1);
headerSpan = buffer.Slice(current, lineEnd).ToSpan();
var headerSpan = buffer.Slice(current, lineEnd).ToSpan();
length = headerSpan.Length;
fixed (byte* pHeader = &headerSpan.DangerousGetPinnableReference())
{
TakeSingleHeader(pHeader, length, handler);
}
// We're going to the next span after this since we know we crossed spans here
// so mark the remaining as equal to the headerSpan so that we end up at 0
// on the next iteration
remaining = headerSpan.Length;
}
if (!TakeSingleHeader(headerSpan, out var nameStart, out var nameEnd, out var valueStart, out var valueEnd))
{
return false;
remaining = length;
}
// Skip the reader forward past the header line
reader.Skip(headerSpan.Length);
remaining -= headerSpan.Length;
var nameBuffer = headerSpan.Slice(nameStart, nameEnd - nameStart);
var valueBuffer = headerSpan.Slice(valueStart, valueEnd - valueStart);
handler.OnHeader(nameBuffer, valueBuffer);
reader.Skip(length);
remaining -= length;
}
}
}
@ -390,142 +388,127 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
private unsafe int IndexOf(byte* pBuffer, int index, int length, byte value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe int IndexOfNameEnd(byte* pBuffer, int index, int length)
{
for (int i = 0; i < length; i++, index++)
var pCurrent = pBuffer + index;
var pEnd = pBuffer + index + length;
var result = -1;
var sawWhitespace = false;
while (pCurrent < pEnd)
{
if (pBuffer[index] == value)
var ch = *pCurrent;
if (ch == ByteColon)
{
return index;
if (sawWhitespace)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
result = index;
break;
}
if (ch == ByteTab || ch == ByteSpace)
{
sawWhitespace = true;
}
index++;
pCurrent++;
}
return -1;
return result;
}
private unsafe bool TakeSingleHeader(Span<byte> span, out int nameStart, out int nameEnd, out int valueStart, out int valueEnd)
private unsafe void TakeSingleHeader<T>(byte* pHeader, int headerLineLength, T handler) where T : IHttpHeadersHandler
{
nameStart = 0;
nameEnd = -1;
valueStart = -1;
valueEnd = -1;
var headerLineLength = span.Length;
var nameHasWhitespace = false;
var previouslyWhitespace = false;
var nameEnd = -1;
var valueStart = -1;
var valueEnd = -1;
var index = 0;
var pCurrent = pHeader + index;
var pEnd = pHeader + headerLineLength;
int i = 0;
var done = false;
fixed (byte* pHeader = &span.DangerousGetPinnableReference())
nameEnd = IndexOfNameEnd(pHeader, index, headerLineLength);
if (nameEnd == -1)
{
switch (HeaderState.Name)
{
case HeaderState.Name:
for (; i < headerLineLength; i++)
{
var ch = pHeader[i];
if (ch == ByteColon)
{
if (nameHasWhitespace)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
nameEnd = i;
// Consume space
i++;
goto case HeaderState.Whitespace;
}
if (ch == ByteSpace || ch == ByteTab)
{
nameHasWhitespace = true;
}
}
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
break;
case HeaderState.Whitespace:
for (; i < headerLineLength; i++)
{
var ch = pHeader[i];
var whitespace = ch == ByteTab || ch == ByteSpace || ch == ByteCR;
if (!whitespace)
{
// Mark the first non whitespace char as the start of the
// header value and change the state to expect to the header value
valueStart = i;
goto case HeaderState.ExpectValue;
}
// If we see a CR then jump to the next state directly
else if (ch == ByteCR)
{
goto case HeaderState.ExpectValue;
}
}
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
break;
case HeaderState.ExpectValue:
for (; i < headerLineLength; i++)
{
var ch = pHeader[i];
var whitespace = ch == ByteTab || ch == ByteSpace;
if (whitespace)
{
if (!previouslyWhitespace)
{
// If we see a whitespace char then maybe it's end of the
// header value
valueEnd = i;
}
}
else if (ch == ByteCR)
{
// If we see a CR and we haven't ever seen whitespace then
// this is the end of the header value
if (valueEnd == -1)
{
valueEnd = i;
}
// We never saw a non whitespace character before the CR
if (valueStart == -1)
{
valueStart = valueEnd;
}
// Consume space
i++;
goto case HeaderState.ExpectNewLine;
}
else
{
// If we find a non whitespace char that isn't CR then reset the end index
valueEnd = -1;
}
previouslyWhitespace = whitespace;
}
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
break;
case HeaderState.ExpectNewLine:
if (pHeader[i] != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
goto case HeaderState.Complete;
case HeaderState.Complete:
done = true;
break;
}
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
return done;
// Skip colon
index += nameEnd + 1;
pCurrent += index;
valueStart = index;
var pValueStart = pCurrent;
while (pCurrent < pEnd)
{
var ch = *pCurrent;
if (ch != ByteTab && ch != ByteSpace && ch != ByteCR)
{
valueStart = index;
pValueStart = pCurrent;
break;
}
else if (ch == ByteCR)
{
break;
}
pCurrent++;
index++;
}
var endIndex = headerLineLength - 1;
pEnd--;
if (*pEnd != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
endIndex--;
pEnd--;
if (*pEnd != ByteCR)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
endIndex--;
pEnd--;
while (pEnd >= pValueStart)
{
var ch = *pEnd;
if (ch != ByteTab && ch != ByteSpace && ch != ByteCR && valueEnd == -1)
{
valueEnd = endIndex;
}
else if (ch == ByteCR)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
pEnd--;
endIndex--;
}
if (valueEnd == -1)
{
valueEnd = valueStart;
}
else
{
valueEnd++;
}
var nameBuffer = new Span<byte>(pHeader, nameEnd);
var valueBuffer = new Span<byte>(pHeader + valueStart, valueEnd - valueStart);
handler.OnHeader(nameBuffer, valueBuffer);
}
private static bool IsValidTokenChar(char c)

View File

@ -7,7 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
UnrecognizedHTTPVersion,
HeadersCorruptedInvalidHeaderSequence,
HeaderLineMustNotStartWithWhitespace,
NoColonCharacterFoundInHeaderLine,
WhitespaceIsNotAllowedInHeaderName,
HeaderValueMustNotContainCR,

View File

@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Header line must not start with whitespace.", exception.Message);
Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}

View File

@ -254,10 +254,10 @@ namespace Microsoft.AspNetCore.Testing
return new[]
{
Tuple.Create(headersWithLineFolding,"Header line must not start with whitespace."),
Tuple.Create(headersWithLineFolding,"Whitespace is not allowed in header name."),
Tuple.Create(headersWithCRInValue,"Header value must not contain CR characters."),
Tuple.Create(headersWithMissingColon,"No ':' character found in header line."),
Tuple.Create(headersStartingWithWhitespace, "Header line must not start with whitespace."),
Tuple.Create(headersStartingWithWhitespace, "Whitespace is not allowed in header name."),
Tuple.Create(headersWithWithspaceInName,"Whitespace is not allowed in header name."),
Tuple.Create(headersNotEndingInCrLfLine, "Headers corrupted, invalid header sequence.")
}