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)}