Loop over bytes inside states of parser state machine (#1417)
This commit is contained in:
parent
d3924b0149
commit
f2a00da811
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue