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,19 +72,15 @@ 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];
switch (state)
{ {
case StartLineState.KnownMethod: case StartLineState.KnownMethod:
if (span.GetKnownMethod(out method, out var methodLength)) if (span.GetKnownMethod(out method, out var methodLength))
@ -92,16 +88,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Update the index, current char, state and jump directly // Update the index, current char, state and jump directly
// to the next state // to the next state
i += methodLength + 1; i += methodLength + 1;
ch = data[i];
state = StartLineState.Path;
goto case StartLineState.Path; goto case StartLineState.Path;
} }
state = StartLineState.UnknownMethod;
goto case StartLineState.UnknownMethod; goto case StartLineState.UnknownMethod;
case StartLineState.UnknownMethod: case StartLineState.UnknownMethod:
for (; i < length; i++)
{
var ch = data[i];
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,14 +166,21 @@ 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; break;
case StartLineState.KnownVersion: case StartLineState.KnownVersion:
@ -178,17 +191,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Update the index, current char, state and jump directly // Update the index, current char, state and jump directly
// to the next state // to the next state
i += versionLenght + 1; i += versionLenght + 1;
ch = data[i];
state = StartLineState.NewLine;
goto case StartLineState.NewLine; goto case StartLineState.NewLine;
} }
versionStart = i; versionStart = i;
state = StartLineState.UnknownVersion;
goto case 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; break;
case StartLineState.NewLine: case StartLineState.NewLine:
if (ch != ByteLF) 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];
switch (state)
{ {
case HeaderState.Name: case HeaderState.Name:
for (; i < length; i++)
{
var ch = data[i];
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;
} }
}
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
break; break;
case HeaderState.Whitespace: 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;
} }
} }
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
break; break;
case HeaderState.ExpectValue: 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;
} }
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
break; break;
case HeaderState.ExpectNewLine: case HeaderState.ExpectNewLine:
if (ch != ByteLF) if (data[i] != ByteLF)
{ {
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR); RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
} }
goto case HeaderState.Complete;
state = HeaderState.Complete; case HeaderState.Complete:
break; done = true;
default:
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;
} }