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