diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln
index d935b2d9b4..24da20c0e0 100644
--- a/KestrelHttpServer.sln
+++ b/KestrelHttpServer.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26213.1
+VisualStudioVersion = 15.0.26206.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
diff --git a/samples/SampleApp/SampleApp.csproj b/samples/SampleApp/SampleApp.csproj
index 9d5b086b5c..5fcf04fb03 100644
--- a/samples/SampleApp/SampleApp.csproj
+++ b/samples/SampleApp/SampleApp.csproj
@@ -3,7 +3,7 @@
- net451;netcoreapp1.1
+ netcoreapp1.1;net451
Exe
win7-x64
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
index 525dc56135..232e567a27 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs
@@ -997,11 +997,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
- var limitedBuffer = buffer;
+ ReadableBuffer limitedBuffer;
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
{
limitedBuffer = buffer.Slice(0, ServerOptions.Limits.MaxRequestLineSize);
}
+ else
+ {
+ limitedBuffer = buffer;
+ }
+
if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out end, ByteLF) == -1)
{
if (limitedBuffer.Length == ServerOptions.Limits.MaxRequestLineSize)
@@ -1014,17 +1019,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
- end = buffer.Move(end, 1);
- ReadCursor methodEnd;
- string method;
- if (!buffer.GetKnownMethod(out method))
+ const int stackAllocLimit = 512;
+
+ // Move 1 byte past the \r
+ end = limitedBuffer.Move(end, 1);
+ var startLineBuffer = limitedBuffer.Slice(0, end);
+
+ Span span;
+
+ if (startLineBuffer.IsSingleSpan)
{
- if (ReadCursorOperations.Seek(buffer.Start, end, out methodEnd, ByteSpace) == -1)
+ // No copies, directly use the one and only span
+ span = startLineBuffer.ToSpan();
+ }
+ else if (startLineBuffer.Length < stackAllocLimit)
+ {
+ unsafe
+ {
+ // Multiple buffers and < stackAllocLimit, copy into a stack buffer
+ byte* stackBuffer = stackalloc byte[startLineBuffer.Length];
+ span = new Span(stackBuffer, startLineBuffer.Length);
+ startLineBuffer.CopyTo(span);
+ }
+ }
+ else
+ {
+ // We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
+ span = new Span(new byte[startLineBuffer.Length]);
+ startLineBuffer.CopyTo(span);
+ }
+
+ var methodEnd = 0;
+ if (!span.GetKnownMethod(out string method))
+ {
+ methodEnd = span.IndexOf(ByteSpace);
+ if (methodEnd == -1)
{
RejectRequestLine(start, end);
}
- method = buffer.Slice(buffer.Start, methodEnd).GetAsciiString();
+ method = span.Slice(0, methodEnd).GetAsciiString();
if (method == null)
{
@@ -1043,57 +1077,67 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- methodEnd = buffer.Slice(method.Length).Start;
+ methodEnd += method.Length;
}
var needDecode = false;
- ReadCursor pathEnd;
+ var pathBegin = methodEnd + 1;
+ var pathToEndSpan = span.Slice(pathBegin, span.Length - pathBegin);
+ pathBegin = 0;
- var pathBegin = buffer.Move(methodEnd, 1);
+ // TODO: IndexOfAny
+ var spaceIndex = pathToEndSpan.IndexOf(ByteSpace);
+ var questionMarkIndex = pathToEndSpan.IndexOf(ByteQuestionMark);
+ var percentageIndex = pathToEndSpan.IndexOf(BytePercentage);
- var chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark, BytePercentage);
- if (chFound == -1)
+ var pathEnd = MinNonZero(spaceIndex, questionMarkIndex, percentageIndex);
+
+ if (spaceIndex == -1 && questionMarkIndex == -1 && percentageIndex == -1)
{
RejectRequestLine(start, end);
}
- else if (chFound == BytePercentage)
+ else if (percentageIndex != -1)
{
needDecode = true;
- chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark);
- if (chFound == -1)
- {
- RejectRequestLine(start, end);
- }
- };
- var queryString = "";
- ReadCursor queryEnd = pathEnd;
- if (chFound == ByteQuestionMark)
- {
- if (ReadCursorOperations.Seek(pathEnd, end, out queryEnd, ByteSpace) == -1)
+ pathEnd = MinNonZero(spaceIndex, questionMarkIndex);
+ if (questionMarkIndex == -1 && spaceIndex == -1)
{
RejectRequestLine(start, end);
}
- queryString = buffer.Slice(pathEnd, queryEnd).GetAsciiString();
}
- // No path
+ var queryString = "";
+ var queryEnd = pathEnd;
+ if (questionMarkIndex != -1)
+ {
+ queryEnd = spaceIndex;
+ if (spaceIndex == -1)
+ {
+ RejectRequestLine(start, end);
+ }
+
+ queryString = pathToEndSpan.Slice(pathEnd, queryEnd - pathEnd).GetAsciiString();
+ }
+
if (pathBegin == pathEnd)
{
RejectRequestLine(start, end);
}
- ReadCursor versionEnd;
- if (ReadCursorOperations.Seek(queryEnd, end, out versionEnd, ByteCR) == -1)
+ var versionBegin = queryEnd + 1;
+ var versionToEndSpan = pathToEndSpan.Slice(versionBegin, pathToEndSpan.Length - versionBegin);
+ versionBegin = 0;
+ var versionEnd = versionToEndSpan.IndexOf(ByteCR);
+
+ if (versionEnd == -1)
{
RejectRequestLine(start, end);
}
- string httpVersion;
- var versionBuffer = buffer.Slice(queryEnd, end).Slice(1);
- if (!versionBuffer.GetKnownVersion(out httpVersion))
+ if (!versionToEndSpan.GetKnownVersion(out string httpVersion))
{
- httpVersion = versionBuffer.Start.GetAsciiStringEscaped(versionEnd, 9);
+ httpVersion = versionToEndSpan.Slice(0, versionEnd).GetAsciiStringEscaped();
if (httpVersion == string.Empty)
{
@@ -1105,14 +1149,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
- var lineEnd = buffer.Slice(versionEnd, 2).ToSpan();
- if (lineEnd[1] != ByteLF)
+ if (versionToEndSpan[versionEnd + 1] != ByteLF)
{
RejectRequestLine(start, end);
}
- var pathBuffer = buffer.Slice(pathBegin, pathEnd);
- var targetBuffer = buffer.Slice(pathBegin, queryEnd);
+ var pathBuffer = pathToEndSpan.Slice(pathBegin, pathEnd);
+ var targetBuffer = pathToEndSpan.Slice(pathBegin, queryEnd);
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
@@ -1125,7 +1168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
rawTarget = targetBuffer.GetAsciiString() ?? string.Empty;
// URI was encoded, unescape and then parse as utf8
- var pathSpan = pathBuffer.ToSpan();
+ var pathSpan = pathBuffer;
int pathLength = UrlEncoder.Decode(pathSpan, pathSpan);
requestUrlPath = new Utf8String(pathSpan.Slice(0, pathLength)).ToString();
}
@@ -1175,6 +1218,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return true;
}
+ private int MinNonZero(int v1, int v2)
+ {
+ v1 = v1 == -1 ? int.MaxValue : v1;
+ v2 = v2 == -1 ? int.MaxValue : v2;
+ return Math.Min(v1, v2);
+ }
+
+ private int MinNonZero(int v1, int v2, int v3)
+ {
+ v1 = v1 == -1 ? int.MaxValue : v1;
+ v2 = v2 == -1 ? int.MaxValue : v2;
+ v3 = v3 == -1 ? int.MaxValue : v3;
+ return Math.Min(Math.Min(v1, v2), v3);
+ }
+
private void RejectRequestLine(ReadCursor start, ReadCursor end)
{
const int MaxRequestLineError = 32;
@@ -1244,40 +1302,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
consumed = buffer.Start;
examined = buffer.End;
+ var bufferLength = buffer.Length;
+ var reader = new ReadableBufferReader(buffer);
+
while (true)
{
- var headersEnd = buffer.Slice(0, Math.Min(buffer.Length, 2));
- var headersEndSpan = headersEnd.ToSpan();
+ var start = reader;
+ int ch1 = reader.Take();
+ var ch2 = reader.Take();
- if (headersEndSpan.Length == 0)
+ if (ch1 == -1)
{
return false;
}
- else
- {
- var ch = headersEndSpan[0];
- if (ch == ByteCR)
- {
- // Check for final CRLF.
- if (headersEndSpan.Length < 2)
- {
- return false;
- }
- else if (headersEndSpan[1] == ByteLF)
- {
- consumed = headersEnd.End;
- examined = consumed;
- ConnectionControl.CancelTimeout();
- return true;
- }
- // Headers don't end in CRLF line.
- RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
- }
- else if (ch == ByteSpace || ch == ByteTab)
+ if (ch1 == ByteCR)
+ {
+ // Check for final CRLF.
+ if (ch2 == -1)
{
- RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
+ return false;
}
+ else if (ch2 == ByteLF)
+ {
+ consumed = reader.Cursor;
+ examined = consumed;
+ ConnectionControl.CancelTimeout();
+ return true;
+ }
+
+ // Headers don't end in CRLF line.
+ RejectRequest(RequestRejectionReason.HeadersCorruptedInvalidHeaderSequence);
+ }
+ else if (ch1 == ByteSpace || ch1 == ByteTab)
+ {
+ RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
}
// If we've parsed the max allowed numbers of headers and we're starting a new
@@ -1287,15 +1346,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
RejectRequest(RequestRejectionReason.TooManyHeaders);
}
- ReadCursor lineEnd;
- var limitedBuffer = buffer;
- if (buffer.Length >= _remainingRequestHeadersBytesAllowed)
+ // Reset the reader since we're not at the end of headers
+ reader = start;
+
+ // Now parse a single header
+ ReadableBuffer limitedBuffer;
+ var overLength = false;
+
+ if (bufferLength >= _remainingRequestHeadersBytesAllowed)
{
- limitedBuffer = buffer.Slice(0, _remainingRequestHeadersBytesAllowed);
+ limitedBuffer = buffer.Slice(consumed, _remainingRequestHeadersBytesAllowed);
+
+ // If we sliced it means the current buffer bigger than what we're
+ // allowed to look at
+ overLength = true;
}
- if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out lineEnd, ByteLF) == -1)
+ else
{
- if (limitedBuffer.Length == _remainingRequestHeadersBytesAllowed)
+ limitedBuffer = buffer;
+ }
+
+ if (ReadCursorOperations.Seek(consumed, limitedBuffer.End, out var lineEnd, ByteLF) == -1)
+ {
+ // We didn't find a \n in the current buffer and we had to slice it so it's an issue
+ if (overLength)
{
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
}
@@ -1305,39 +1379,94 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
- var beginName = buffer.Start;
- ReadCursor endName;
- if (ReadCursorOperations.Seek(buffer.Start, lineEnd, out endName, ByteColon) == -1)
+ const int stackAllocLimit = 512;
+
+ if (lineEnd != limitedBuffer.End)
+ {
+ lineEnd = limitedBuffer.Move(lineEnd, 1);
+ }
+
+ var headerBuffer = limitedBuffer.Slice(consumed, lineEnd);
+
+ Span span;
+ if (headerBuffer.IsSingleSpan)
+ {
+ // No copies, directly use the one and only span
+ span = headerBuffer.ToSpan();
+ }
+ else if (headerBuffer.Length < stackAllocLimit)
+ {
+ unsafe
+ {
+ // Multiple buffers and < stackAllocLimit, copy into a stack buffer
+ byte* stackBuffer = stackalloc byte[headerBuffer.Length];
+ span = new Span(stackBuffer, headerBuffer.Length);
+ headerBuffer.CopyTo(span);
+ }
+ }
+ else
+ {
+ // We're not a single span here but we can use pooled arrays to avoid allocations in the rare case
+ span = new Span(new byte[headerBuffer.Length]);
+ headerBuffer.CopyTo(span);
+ }
+
+ int endNameIndex = span.IndexOf(ByteColon);
+ if (endNameIndex == -1)
{
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
- ReadCursor whitespace;
- if (ReadCursorOperations.Seek(beginName, endName, out whitespace, ByteTab, ByteSpace) != -1)
+ var nameBuffer = span.Slice(0, endNameIndex);
+ if (nameBuffer.IndexOf(ByteSpace) != -1 || nameBuffer.IndexOf(ByteTab) != -1)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
- ReadCursor endValue;
- if (ReadCursorOperations.Seek(beginName, lineEnd, out endValue, ByteCR) == -1)
+ int endValueIndex = span.IndexOf(ByteCR);
+ if (endValueIndex == -1)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
- var lineSufix = buffer.Slice(endValue);
- if (lineSufix.Length < 3)
+ var lineSuffix = span.Slice(endValueIndex);
+ if (lineSuffix.Length < 2)
{
return false;
}
- lineSufix = lineSufix.Slice(0, 3); // \r\n\r
- var lineSufixSpan = lineSufix.ToSpan();
+
// This check and MissingCRInHeaderLine is a bit backwards, we should do it at once instead of having another seek
- if (lineSufixSpan[1] != ByteLF)
+ if (lineSuffix[1] != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
- var next = lineSufixSpan[2];
+ // Trim trailing whitespace from header value by repeatedly advancing to next
+ // whitespace or CR.
+ //
+ // - If CR is found, this is the end of the header value.
+ // - If whitespace is found, this is the _tentative_ end of the header value.
+ // If non-whitespace is found after it and it's not CR, seek again to the next
+ // whitespace or CR for a new (possibly tentative) end of value.
+
+ var valueBuffer = span.Slice(endNameIndex + 1, endValueIndex - (endNameIndex + 1));
+
+ // TODO: Trim else where
+ var value = valueBuffer.GetAsciiString()?.Trim() ?? string.Empty;
+
+ var headerLineLength = span.Length;
+
+ // -1 so that we can re-check the extra \r
+ reader.Skip(headerLineLength);
+
+ var next = reader.Peek();
+
+ // We cant check for line continuations to reject everything we've done so far
+ if (next == -1)
+ {
+ return false;
+ }
+
if (next == ByteSpace || next == ByteTab)
{
// From https://tools.ietf.org/html/rfc7230#section-3.2.4:
@@ -1360,31 +1489,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
}
- // Trim trailing whitespace from header value by repeatedly advancing to next
- // whitespace or CR.
- //
- // - If CR is found, this is the end of the header value.
- // - If whitespace is found, this is the _tentative_ end of the header value.
- // If non-whitespace is found after it and it's not CR, seek again to the next
- // whitespace or CR for a new (possibly tentative) end of value.
+ // Update the frame state only after we know there's no header line continuation
+ _remainingRequestHeadersBytesAllowed -= headerLineLength;
+ bufferLength -= headerLineLength;
- var nameBuffer = buffer.Slice(beginName, endName);
-
- // TODO: TrimStart and TrimEnd are pretty slow
- var valueBuffer = buffer.Slice(endName, endValue).Slice(1).TrimStart().TrimEnd();
-
- var name = nameBuffer.ToArraySegment();
- var value = valueBuffer.GetAsciiString();
-
- lineEnd = limitedBuffer.Move(lineEnd, 1);
-
- // TODO: bad
- _remainingRequestHeadersBytesAllowed -= buffer.Slice(0, lineEnd).Length;
_requestHeadersParsed++;
- requestHeaders.Append(name.Array, name.Offset, name.Count, value);
- buffer = buffer.Slice(lineEnd);
- consumed = buffer.Start;
+ requestHeaders.Append(nameBuffer, value);
+
+ consumed = reader.Cursor;
}
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.Generated.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.Generated.cs
index 7cfb64ff31..051b6b5737 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.Generated.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.Generated.cs
@@ -3501,12 +3501,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
- public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
+ public unsafe void Append(byte* pKeyBytes, int keyLength, string value)
{
- fixed (byte* ptr = &keyBytes[keyOffset])
- {
- var pUB = ptr;
- var pUL = (ulong*)pUB;
+ var pUB = pKeyBytes;
+ var pUL = (ulong*)pUB;
var pUI = (uint*)pUB;
var pUS = (ushort*)pUB;
var stringValue = new StringValues(value);
@@ -3567,11 +3565,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
break;
}
- AppendNonPrimaryHeaders(ptr, keyOffset, keyLength, value);
- }
+ AppendNonPrimaryHeaders(pKeyBytes, keyLength, value);
}
-
- private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyOffset, int keyLength, string value)
+
+ private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyLength, string value)
{
var pUB = pKeyBytes;
var pUL = (ulong*)pUB;
@@ -7767,10 +7764,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- var valueCount = _headers._Connection.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Connection.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Connection[i];
+ var value = _headers._Connection[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 17, 14);
@@ -7793,10 +7790,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- var valueCount = _headers._Date.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Date.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Date[i];
+ var value = _headers._Date[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 31, 8);
@@ -7814,10 +7811,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 2048L) != 0)
{
{
- var valueCount = _headers._ContentType.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentType.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentType[i];
+ var value = _headers._ContentType[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 133, 16);
@@ -7840,10 +7837,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- var valueCount = _headers._Server.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Server.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Server[i];
+ var value = _headers._Server[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 350, 10);
@@ -7872,10 +7869,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 1L) != 0)
{
{
- var valueCount = _headers._CacheControl.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._CacheControl.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._CacheControl[i];
+ var value = _headers._CacheControl[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 0, 17);
@@ -7893,10 +7890,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 8L) != 0)
{
{
- var valueCount = _headers._KeepAlive.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._KeepAlive.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._KeepAlive[i];
+ var value = _headers._KeepAlive[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 39, 14);
@@ -7914,10 +7911,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 16L) != 0)
{
{
- var valueCount = _headers._Pragma.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Pragma.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Pragma[i];
+ var value = _headers._Pragma[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 53, 10);
@@ -7935,10 +7932,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 32L) != 0)
{
{
- var valueCount = _headers._Trailer.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Trailer.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Trailer[i];
+ var value = _headers._Trailer[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 63, 11);
@@ -7961,10 +7958,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else
{
- var valueCount = _headers._TransferEncoding.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._TransferEncoding.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._TransferEncoding[i];
+ var value = _headers._TransferEncoding[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 74, 21);
@@ -7982,10 +7979,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 128L) != 0)
{
{
- var valueCount = _headers._Upgrade.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Upgrade.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Upgrade[i];
+ var value = _headers._Upgrade[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 95, 11);
@@ -8003,10 +8000,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 256L) != 0)
{
{
- var valueCount = _headers._Via.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Via.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Via[i];
+ var value = _headers._Via[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 106, 7);
@@ -8024,10 +8021,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 512L) != 0)
{
{
- var valueCount = _headers._Warning.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Warning.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Warning[i];
+ var value = _headers._Warning[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 113, 11);
@@ -8045,10 +8042,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 1024L) != 0)
{
{
- var valueCount = _headers._Allow.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Allow.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Allow[i];
+ var value = _headers._Allow[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 124, 9);
@@ -8066,10 +8063,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 4096L) != 0)
{
{
- var valueCount = _headers._ContentEncoding.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentEncoding.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentEncoding[i];
+ var value = _headers._ContentEncoding[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 149, 20);
@@ -8087,10 +8084,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 8192L) != 0)
{
{
- var valueCount = _headers._ContentLanguage.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentLanguage.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentLanguage[i];
+ var value = _headers._ContentLanguage[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 169, 20);
@@ -8108,10 +8105,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 16384L) != 0)
{
{
- var valueCount = _headers._ContentLocation.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentLocation.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentLocation[i];
+ var value = _headers._ContentLocation[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 189, 20);
@@ -8129,10 +8126,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 32768L) != 0)
{
{
- var valueCount = _headers._ContentMD5.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentMD5.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentMD5[i];
+ var value = _headers._ContentMD5[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 209, 15);
@@ -8150,10 +8147,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 65536L) != 0)
{
{
- var valueCount = _headers._ContentRange.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ContentRange.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ContentRange[i];
+ var value = _headers._ContentRange[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 224, 17);
@@ -8171,10 +8168,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 131072L) != 0)
{
{
- var valueCount = _headers._Expires.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Expires.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Expires[i];
+ var value = _headers._Expires[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 241, 11);
@@ -8192,10 +8189,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 262144L) != 0)
{
{
- var valueCount = _headers._LastModified.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._LastModified.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._LastModified[i];
+ var value = _headers._LastModified[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 252, 17);
@@ -8213,10 +8210,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 524288L) != 0)
{
{
- var valueCount = _headers._AcceptRanges.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AcceptRanges.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AcceptRanges[i];
+ var value = _headers._AcceptRanges[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 269, 17);
@@ -8234,10 +8231,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 1048576L) != 0)
{
{
- var valueCount = _headers._Age.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Age.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Age[i];
+ var value = _headers._Age[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 286, 7);
@@ -8255,10 +8252,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 2097152L) != 0)
{
{
- var valueCount = _headers._ETag.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ETag.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ETag[i];
+ var value = _headers._ETag[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 293, 8);
@@ -8276,10 +8273,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 4194304L) != 0)
{
{
- var valueCount = _headers._Location.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Location.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Location[i];
+ var value = _headers._Location[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 301, 12);
@@ -8297,10 +8294,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 8388608L) != 0)
{
{
- var valueCount = _headers._ProxyAuthenticate.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._ProxyAuthenticate.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._ProxyAuthenticate[i];
+ var value = _headers._ProxyAuthenticate[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 313, 22);
@@ -8318,10 +8315,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 16777216L) != 0)
{
{
- var valueCount = _headers._RetryAfter.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._RetryAfter.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._RetryAfter[i];
+ var value = _headers._RetryAfter[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 335, 15);
@@ -8339,10 +8336,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 67108864L) != 0)
{
{
- var valueCount = _headers._SetCookie.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._SetCookie.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._SetCookie[i];
+ var value = _headers._SetCookie[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 360, 14);
@@ -8360,10 +8357,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 134217728L) != 0)
{
{
- var valueCount = _headers._Vary.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._Vary.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._Vary[i];
+ var value = _headers._Vary[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 374, 8);
@@ -8381,10 +8378,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 268435456L) != 0)
{
{
- var valueCount = _headers._WWWAuthenticate.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._WWWAuthenticate.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._WWWAuthenticate[i];
+ var value = _headers._WWWAuthenticate[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 382, 20);
@@ -8402,10 +8399,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 536870912L) != 0)
{
{
- var valueCount = _headers._AccessControlAllowCredentials.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlAllowCredentials.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlAllowCredentials[i];
+ var value = _headers._AccessControlAllowCredentials[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 402, 36);
@@ -8423,10 +8420,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 1073741824L) != 0)
{
{
- var valueCount = _headers._AccessControlAllowHeaders.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlAllowHeaders.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlAllowHeaders[i];
+ var value = _headers._AccessControlAllowHeaders[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 438, 32);
@@ -8444,10 +8441,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 2147483648L) != 0)
{
{
- var valueCount = _headers._AccessControlAllowMethods.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlAllowMethods.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlAllowMethods[i];
+ var value = _headers._AccessControlAllowMethods[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 470, 32);
@@ -8465,10 +8462,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 4294967296L) != 0)
{
{
- var valueCount = _headers._AccessControlAllowOrigin.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlAllowOrigin.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlAllowOrigin[i];
+ var value = _headers._AccessControlAllowOrigin[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 502, 31);
@@ -8486,10 +8483,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 8589934592L) != 0)
{
{
- var valueCount = _headers._AccessControlExposeHeaders.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlExposeHeaders.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlExposeHeaders[i];
+ var value = _headers._AccessControlExposeHeaders[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 533, 33);
@@ -8507,10 +8504,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if ((tempBits & 17179869184L) != 0)
{
{
- var valueCount = _headers._AccessControlMaxAge.Count;
- for (var i = 0; i < valueCount; i++)
+ var valueCount = _headers._AccessControlMaxAge.Count;
+ for (var i = 0; i < valueCount; i++)
{
- var value = _headers._AccessControlMaxAge[i];
+ var value = _headers._AccessControlMaxAge[i];
if (value != null)
{
output.CopyFrom(_headerBytes, 566, 26);
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameRequestHeaders.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameRequestHeaders.cs
index 4dd46d5cf1..ca6496d320 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameRequestHeaders.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameRequestHeaders.cs
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -29,6 +30,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Unknown[key] = value;
}
+ public unsafe void Append(Span name, string value)
+ {
+ fixed (byte* namePtr = &name.DangerousGetPinnableReference())
+ {
+ Append(namePtr, name.Length, value);
+ }
+ }
+
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void AppendUnknownHeaders(byte* pKeyBytes, int keyLength, string value)
{
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs
index 94cf66ef7b..95c3c5a59f 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs
@@ -90,6 +90,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return sb.ToString();
}
+ public static string GetAsciiStringEscaped(this Span span)
+ {
+ var sb = new StringBuilder();
+
+ for (var i = 0; i < span.Length; ++i)
+ {
+ var ch = span[i];
+ sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch:X2}>" : ((char)ch).ToString());
+ }
+
+ return sb.ToString();
+ }
+
public static ArraySegment PeekArraySegment(this MemoryPoolIterator iter)
{
if (iter.IsDefault || iter.IsEnd)
@@ -157,6 +170,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return false;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool GetKnownMethod(this Span span, out string knownMethod)
+ {
+ knownMethod = null;
+ if (span.Length < sizeof(ulong))
+ {
+ return false;
+ }
+
+ ulong value = span.Read();
+ if ((value & _mask4Chars) == _httpGetMethodLong)
+ {
+ knownMethod = HttpMethods.Get;
+ return true;
+ }
+ foreach (var x in _knownMethods)
+ {
+ if ((value & x.Item1) == x.Item2)
+ {
+ knownMethod = x.Item3;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
/// Checks 9 bytes from correspond to a known HTTP version.
///
@@ -200,5 +240,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return knownVersion != null;
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool GetKnownVersion(this Span span, out string knownVersion)
+ {
+ knownVersion = null;
+
+ if (span.Length < sizeof(ulong))
+ {
+ return false;
+ }
+
+ var value = span.Read();
+ if (value == _http11VersionLong)
+ {
+ knownVersion = Http11Version;
+ }
+ else if (value == _http10VersionLong)
+ {
+ knownVersion = Http10Version;
+ }
+
+ if (knownVersion != null)
+ {
+ if (span[sizeof(ulong)] != (byte)'\r')
+ {
+ knownVersion = null;
+ }
+ }
+
+ return knownVersion != null;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
index c62c37e231..5dd7b984c9 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
+++ b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj
@@ -30,4 +30,4 @@
-
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
index 91212d627b..dbf6d857a5 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameRequestHeadersTests.cs
@@ -311,7 +311,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var encoding = Encoding.GetEncoding("iso-8859-1");
var exception = Assert.Throws(
- () => headers.Append(encoding.GetBytes(key), 0, encoding.GetByteCount(key), key));
+ () => headers.Append(encoding.GetBytes(key), key));
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
}
diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
index bf6fc9c78b..3edb7fad3d 100644
--- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
+++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs
@@ -611,7 +611,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var exception = Assert.Throws(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
- Assert.Equal("Unrecognized HTTP version: HTTP/1.1a...", exception.Message);
+ Assert.Equal("Unrecognized HTTP version: HTTP/1.1ab", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
}
diff --git a/tools/CodeGenerator/KnownHeaders.cs b/tools/CodeGenerator/KnownHeaders.cs
index 0e2fdf858f..a211013404 100644
--- a/tools/CodeGenerator/KnownHeaders.cs
+++ b/tools/CodeGenerator/KnownHeaders.cs
@@ -564,18 +564,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}}" : "")}")}
}}" : "")}
{(loop.ClassName == "FrameRequestHeaders" ? $@"
- public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
+ public unsafe void Append(byte* pKeyBytes, int keyLength, string value)
{{
- fixed (byte* ptr = &keyBytes[keyOffset])
- {{
- var pUB = ptr;
- {AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
+ var pUB = pKeyBytes;
+ {AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
- AppendNonPrimaryHeaders(ptr, keyOffset, keyLength, value);
- }}
+ AppendNonPrimaryHeaders(pKeyBytes, keyLength, value);
}}
- private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyOffset, int keyLength, string value)
+ private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyLength, string value)
{{
var pUB = pKeyBytes;
{AppendSwitch(loop.Headers.Where(h => !h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}