Loop over bytes inside states of parser state machine (#1417)

This commit is contained in:
Pavel Krymets 2017-03-01 18:18:34 -08:00 committed by GitHub
parent d3924b0149
commit f2a00da811
1 changed files with 123 additions and 102 deletions

View File

@ -72,36 +72,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var versionStart = -1; var versionStart = -1;
HttpVersion httpVersion = HttpVersion.Unknown; HttpVersion httpVersion = HttpVersion.Unknown;
HttpMethod method = HttpMethod.Custom; HttpMethod method;
Span<byte> customMethod; Span<byte> customMethod;
var state = StartLineState.KnownMethod; int i = 0;
var length = span.Length;
var done = false;
int i;
fixed (byte* data = &span.DangerousGetPinnableReference()) fixed (byte* data = &span.DangerousGetPinnableReference())
{ {
var length = span.Length; switch (StartLineState.KnownMethod)
for (i = 0; i < length; i++)
{ {
var ch = data[i]; case StartLineState.KnownMethod:
if (span.GetKnownMethod(out method, out var methodLength))
{
// Update the index, current char, state and jump directly
// to the next state
i += methodLength + 1;
switch (state) goto case StartLineState.Path;
{ }
case StartLineState.KnownMethod: goto case StartLineState.UnknownMethod;
if (span.GetKnownMethod(out method, out var methodLength))
{
// Update the index, current char, state and jump directly
// to the next state
i += methodLength + 1;
ch = data[i];
state = StartLineState.Path;
goto case StartLineState.Path; case StartLineState.UnknownMethod:
} for (; i < length; i++)
{
var ch = data[i];
state = StartLineState.UnknownMethod;
goto case StartLineState.UnknownMethod;
case StartLineState.UnknownMethod:
if (ch == ByteSpace) if (ch == ByteSpace)
{ {
customMethod = span.Slice(0, i); customMethod = span.Slice(0, i);
@ -110,16 +106,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
RejectRequestLine(span); RejectRequestLine(span);
} }
// Consume space
i++;
state = StartLineState.Path; goto case StartLineState.Path;
} }
else if (!IsValidTokenChar((char)ch))
if (!IsValidTokenChar((char)ch))
{ {
RejectRequestLine(span); RejectRequestLine(span);
} }
}
break; break;
case StartLineState.Path: case StartLineState.Path:
for (; i < length; i++)
{
var ch = data[i];
if (ch == ByteSpace) if (ch == ByteSpace)
{ {
pathEnd = i; pathEnd = i;
@ -133,7 +136,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// No query string found // No query string found
queryStart = queryEnd = i; queryStart = queryEnd = i;
state = StartLineState.KnownVersion; // Consume space
i++;
goto case StartLineState.KnownVersion;
} }
else if (ch == ByteQuestionMark) else if (ch == ByteQuestionMark)
{ {
@ -146,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
queryStart = i; queryStart = i;
state = StartLineState.QueryString; goto case StartLineState.QueryString;
} }
else if (ch == BytePercentage) else if (ch == BytePercentage)
{ {
@ -160,35 +166,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
pathStart = i; pathStart = i;
} }
break; }
break;
case StartLineState.QueryString: case StartLineState.QueryString:
for (; i < length; i++)
{
var ch = data[i];
if (ch == ByteSpace) if (ch == ByteSpace)
{ {
queryEnd = i; queryEnd = i;
state = StartLineState.KnownVersion;
// Consume space
i++;
goto case StartLineState.KnownVersion;
} }
break; }
case StartLineState.KnownVersion: break;
// REVIEW: We don't *need* to slice here but it makes the API case StartLineState.KnownVersion:
// nicer, slicing should be free :) // REVIEW: We don't *need* to slice here but it makes the API
if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght)) // nicer, slicing should be free :)
{ if (span.Slice(i).GetKnownVersion(out httpVersion, out var versionLenght))
// Update the index, current char, state and jump directly {
// to the next state // Update the index, current char, state and jump directly
i += versionLenght + 1; // to the next state
ch = data[i]; i += versionLenght + 1;
state = StartLineState.NewLine; goto case StartLineState.NewLine;
}
goto case StartLineState.NewLine; versionStart = i;
}
versionStart = i; goto case StartLineState.UnknownVersion;
state = StartLineState.UnknownVersion;
goto case StartLineState.UnknownVersion;
case StartLineState.UnknownVersion: case StartLineState.UnknownVersion:
for (; i < length; i++)
{
var ch = data[i];
if (ch == ByteCR) if (ch == ByteCR)
{ {
var versionSpan = span.Slice(versionStart, i - versionStart); var versionSpan = span.Slice(versionStart, i - versionStart);
@ -199,25 +212,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
else else
{ {
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, versionSpan.GetAsciiStringEscaped(32)); RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion,
versionSpan.GetAsciiStringEscaped(32));
} }
} }
break; }
case StartLineState.NewLine: break;
if (ch != ByteLF) case StartLineState.NewLine:
{ if (data[i] != ByteLF)
RejectRequestLine(span); {
} RejectRequestLine(span);
}
i++;
state = StartLineState.Complete; goto case StartLineState.Complete;
break; case StartLineState.Complete:
case StartLineState.Complete: done = true;
break; break;
}
} }
} }
if (state != StartLineState.Complete) if (!done)
{ {
RejectRequestLine(span); RejectRequestLine(span);
} }
@ -311,7 +326,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
headerBuffer.CopyTo(span); headerBuffer.CopyTo(span);
} }
var state = HeaderState.Name;
var nameStart = 0; var nameStart = 0;
var nameEnd = -1; var nameEnd = -1;
var valueStart = -1; var valueStart = -1;
@ -320,33 +334,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var previouslyWhitespace = false; var previouslyWhitespace = false;
var headerLineLength = span.Length; var headerLineLength = span.Length;
int i = 0;
var length = span.Length;
bool done = false;
fixed (byte* data = &span.DangerousGetPinnableReference()) fixed (byte* data = &span.DangerousGetPinnableReference())
{ {
for (var i = 0; i < headerLineLength; i++) switch (HeaderState.Name)
{ {
var ch = data[i]; case HeaderState.Name:
for (; i < length; i++)
switch (state) {
{ var ch = data[i];
case HeaderState.Name:
if (ch == ByteColon) if (ch == ByteColon)
{ {
if (nameHasWhitespace) if (nameHasWhitespace)
{ {
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName); RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
} }
state = HeaderState.Whitespace;
nameEnd = i; nameEnd = i;
// Consume space
i++;
goto case HeaderState.Whitespace;
} }
if (ch == ByteSpace || ch == ByteTab) if (ch == ByteSpace || ch == ByteTab)
{ {
nameHasWhitespace = true; nameHasWhitespace = true;
} }
break; }
case HeaderState.Whitespace: RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
break;
case HeaderState.Whitespace:
for (; i < length; i++)
{ {
var ch = data[i];
var whitespace = ch == ByteTab || ch == ByteSpace || ch == ByteCR; var whitespace = ch == ByteTab || ch == ByteSpace || ch == ByteCR;
if (!whitespace) if (!whitespace)
@ -354,18 +378,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Mark the first non whitespace char as the start of the // Mark the first non whitespace char as the start of the
// header value and change the state to expect to the header value // header value and change the state to expect to the header value
valueStart = i; valueStart = i;
state = HeaderState.ExpectValue;
goto case HeaderState.ExpectValue;
} }
// If we see a CR then jump to the next state directly // If we see a CR then jump to the next state directly
else if (ch == ByteCR) else if (ch == ByteCR)
{ {
state = HeaderState.ExpectValue;
goto case HeaderState.ExpectValue; goto case HeaderState.ExpectValue;
} }
} }
break;
case HeaderState.ExpectValue: RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
break;
case HeaderState.ExpectValue:
for (; i < length; i++)
{ {
var ch = data[i];
var whitespace = ch == ByteTab || ch == ByteSpace; var whitespace = ch == ByteTab || ch == ByteSpace;
if (whitespace) if (whitespace)
@ -392,7 +421,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
valueStart = valueEnd; valueStart = valueEnd;
} }
state = HeaderState.ExpectNewLine; // Consume space
i++;
goto case HeaderState.ExpectNewLine;
} }
else else
{ {
@ -402,32 +434,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
previouslyWhitespace = whitespace; previouslyWhitespace = whitespace;
} }
break; RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
case HeaderState.ExpectNewLine: break;
if (ch != ByteLF) case HeaderState.ExpectNewLine:
{ if (data[i] != ByteLF)
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR); {
} RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
state = HeaderState.Complete; goto case HeaderState.Complete;
break; case HeaderState.Complete:
default: done = true;
break; break;
}
} }
} }
if (state == HeaderState.Name) if (!done)
{
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
if (state == HeaderState.ExpectValue || state == HeaderState.Whitespace)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
if (state != HeaderState.Complete)
{ {
return false; return false;
} }
@ -527,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void Reset() public void Reset()
{ {
} }
private enum HeaderState private enum HeaderState