diff --git a/.vscode/launch.json b/.vscode/launch.json index e91f61ede0..f97435a509 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "Compile: solution", - "program": "${workspaceRoot}/samples/SampleApp/bin/Debug/netcoreapp1.1/SampleApp.dll", - "args": [], + "program": "${workspaceRoot}/samples/SampleApp/bin/Debug/netcoreapp2.0/SampleApp.dll", "cwd": "${workspaceRoot}/samples/SampleApp", "console": "internalConsole", "stopAtEntry": false, @@ -38,8 +37,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "Compile: solution", - "program": "${workspaceRoot}/samples/LargeResponseApp/bin/Debug/netcoreapp1.1/LargeResponseApp.dll", - "args": [], + "program": "${workspaceRoot}/samples/LargeResponseApp/bin/Debug/netcoreapp2.0/LargeResponseApp.dll", "cwd": "${workspaceRoot}/samples/LargeResponseApp", "console": "internalConsole", "stopAtEntry": false, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b5ff23e775..db382e6056 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,10 +5,20 @@ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE": "true" } }, + // requires that you first run build.cmd or build.sh to install local builds of dotnet + "windows": { + "command": "${env.LOCALAPPDATA}/Microsoft/dotnet/dotnet.exe" + }, + "osx": { + "command": "${env.HOME}/.dotnet/dotnet" + }, + "linux": { + "command": "${env.HOME}/.dotnet/dotnet" + }, + "suppressTaskName": true, "tasks": [ { "taskName": "Restore: solution", - "command": "dotnet", "args": [ "restore" ] @@ -16,7 +26,6 @@ { "taskName": "Compile: solution", "isBuildCommand": true, - "command": "dotnet", "args": [ "build", "${workspaceRoot}/KestrelHttpServer.sln" @@ -40,9 +49,16 @@ } } }, + { + "taskName": "Test", + "args": [ + "test" + ], + "problemMatcher": "$msCompile", + "isTestCommand": true + }, { "taskName": "Compile: CodeGenerator", - "command": "dotnet", "args": [ "build" ], @@ -53,7 +69,6 @@ }, { "taskName": "Run: CodeGenerator", - "command": "dotnet", "args": [ "run" ], @@ -63,7 +78,6 @@ }, { "taskName": "Run: Benchmarks", - "command": "dotnet", "args": [ "run", "-c", @@ -72,33 +86,6 @@ "options": { "cwd": "${workspaceRoot}/test/Microsoft.AspNetCore.Server.Kestrel.Performance/" } - }, - { - "taskName": "Test: KestrelTests", - "isTestCommand": true, - "command": "dotnet", - "args": [ - "test", - "-f", - "netcoreapp1.1" - ], - "options": { - "cwd": "${workspaceRoot}/test/Microsoft.AspNetCore.Server.KestrelTests" - }, - "problemMatcher": "$msCompile" - }, - { - "taskName": "Test: FunctionalTests", - "command": "dotnet", - "args": [ - "test", - "-f", - "netcoreapp1.1" - ], - "options": { - "cwd": "${workspaceRoot}/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests" - }, - "problemMatcher": "$msCompile" } ] } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs index 5a19ea4565..6bc10f460d 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs @@ -80,6 +80,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core case RequestRejectionReason.ConnectMethodRequired: ex = new BadHttpRequestException("Method not allowed.", StatusCodes.Status405MethodNotAllowed, HttpMethod.Connect); break; + case RequestRejectionReason.MissingHostHeader: + ex = new BadHttpRequestException("Request is missing Host header.", StatusCodes.Status400BadRequest); + break; + case RequestRejectionReason.MultipleHostHeaders: + ex = new BadHttpRequestException("Multiple Host headers.", StatusCodes.Status400BadRequest); + break; + case RequestRejectionReason.InvalidHostHeader: + ex = new BadHttpRequestException("Invalid Host header.", StatusCodes.Status400BadRequest); + break; default: ex = new BadHttpRequestException("Bad request.", StatusCodes.Status400BadRequest); break; @@ -116,6 +125,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core case RequestRejectionReason.LengthRequiredHttp10: ex = new BadHttpRequestException($"{detail} request contains no Content-Length header", StatusCodes.Status400BadRequest); break; + case RequestRejectionReason.InvalidHostHeader: + ex = new BadHttpRequestException($"Invalid Host header: '{detail}'", StatusCodes.Status400BadRequest); + break; default: ex = new BadHttpRequestException("Bad request.", StatusCodes.Status400BadRequest); break; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs index 4d84a1c6d4..9dc9106e48 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs @@ -82,6 +82,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly FrameContext _frameContext; private readonly IHttpParser _parser; + private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown; + private Uri _absoluteRequestTarget; + public Frame(FrameContext frameContext) { _frameContext = frameContext; @@ -141,6 +144,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public string Path { get; set; } public string QueryString { get; set; } public string RawTarget { get; set; } + public string HttpVersion { get @@ -365,6 +369,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Method = null; PathBase = null; Path = null; + RawTarget = null; + _requestTargetForm = HttpRequestTarget.Unknown; + _absoluteRequestTarget = null; QueryString = null; _httpVersion = Http.HttpVersion.Unknown; StatusCode = StatusCodes.Status200OK; @@ -1266,6 +1273,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /"); + _requestTargetForm = HttpRequestTarget.OriginForm; + // 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; // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" @@ -1325,8 +1334,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void OnAuthorityFormTarget(HttpMethod method, Span target) { - // TODO Validate that target is a correct host[:port] string. - // Reject as 400 if not. This is just a quick scan for invalid characters + _requestTargetForm = HttpRequestTarget.AuthorityForm; + + // This is not complete validation. It is just a quick scan for invalid characters // but doesn't check that the target fully matches the URI spec. for (var i = 0; i < target.Length; i++) { @@ -1360,6 +1370,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void OnAsteriskFormTarget(HttpMethod method) { + _requestTargetForm = HttpRequestTarget.AsteriskForm; + // The asterisk-form of request-target is only used for a server-wide // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7). if (method != HttpMethod.Options) @@ -1374,6 +1386,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void OnAbsoluteFormTarget(Span target, Span query) { + _requestTargetForm = HttpRequestTarget.AbsoluteForm; + // absolute-form // https://tools.ietf.org/html/rfc7230#section-5.3.2 @@ -1394,6 +1408,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http RejectRequestTarget(target); } + _absoluteRequestTarget = uri; Path = uri.LocalPath; // don't use uri.Query because we need the unescaped version QueryString = query.GetAsciiStringNonNullCharacters(); @@ -1420,5 +1435,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http FrameRequestHeaders.Append(name, valueString); } + + protected void EnsureHostHeaderExists() + { + if (_httpVersion == Http.HttpVersion.Http10) + { + return; + } + + // https://tools.ietf.org/html/rfc7230#section-5.4 + // A server MUST respond with a 400 (Bad Request) status code to any + // HTTP/1.1 request message that lacks a Host header field and to any + // request message that contains more than one Host header field or a + // Host header field with an invalid field-value. + + var host = FrameRequestHeaders.HeaderHost; + if (host.Count <= 0) + { + RejectRequest(RequestRejectionReason.MissingHostHeader); + } + else if (host.Count > 1) + { + RejectRequest(RequestRejectionReason.MultipleHostHeaders); + } + else if (_requestTargetForm == HttpRequestTarget.AuthorityForm) + { + if (!host.Equals(RawTarget)) + { + RejectRequest(RequestRejectionReason.InvalidHostHeader, host.ToString()); + } + } + else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm) + { + // If the target URI includes an authority component, then a + // client MUST send a field - value for Host that is identical to that + // authority component, excluding any userinfo subcomponent and its "@" + // delimiter. + + // System.Uri doesn't not tell us if the port was in the original string or not. + // When IsDefaultPort = true, we will allow Host: with or without the default port + var authorityAndPort = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port; + if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort) + && host != authorityAndPort) + { + RejectRequest(RequestRejectionReason.InvalidHostHeader, host.ToString()); + } + } + } + + private enum HttpRequestTarget + { + Unknown = -1, + // origin-form is the most common + OriginForm, + AbsoluteForm, + AuthorityForm, + AsteriskForm + } } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameOfT.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameOfT.cs index 4073bc0837..b95a42c6a8 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameOfT.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameOfT.cs @@ -85,6 +85,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!_requestProcessingStopping) { + EnsureHostHeaderExists(); + var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; _upgrade = messageBody.RequestUpgrade; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/RequestRejectionReason.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/RequestRejectionReason.cs index e6b70b7551..ea03aab6fa 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/RequestRejectionReason.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/RequestRejectionReason.cs @@ -27,5 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http LengthRequiredHttp10, OptionsMethodRequired, ConnectMethodRequired, + MissingHostHeader, + MultipleHostHeaders, + InvalidHostHeader, } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs index f2928ac9c2..8ace3c7338 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameTests.cs @@ -533,7 +533,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { _frame.Start(); - var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"); + var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await _input.Writer.WriteAsync(data); var requestProcessingTask = _frame.StopAsync(); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs index 4c59212c6c..d97289edd8 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/BadHttpRequestTests.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method) { return TestBadRequest( - $"{method} / HTTP/1.1\r\n\r\n", + $"{method} / HTTP/1.1\r\nHost:\r\n\r\n", "411 Length Required", $"{method} request contains no Content-Length or Transfer-Encoding header"); } @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public Task BadRequestIfContentLengthInvalid(string contentLength) { return TestBadRequest( - $"POST / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n", + $"POST / HTTP/1.1\r\nHost:\r\nContent-Length: {contentLength}\r\n\r\n", "400 Bad Request", $"Invalid content length: {contentLength}"); } @@ -106,6 +106,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests $"Allow: {allowedMethod}"); } + [Fact] + public Task BadRequestIfHostHeaderMissing() + { + return TestBadRequest( + "GET / HTTP/1.1\r\n\r\n", + "400 Bad Request", + "Request is missing Host header."); + } + + [Fact] + public Task BadRequestIfMultipleHostHeaders() + { + return TestBadRequest("GET / HTTP/1.1\r\nHost: localhost\r\nHost: localhost\r\n\r\n", + "400 Bad Request", + "Multiple Host headers."); + } + + [Theory] + [MemberData(nameof(InvalidHostHeaderData))] + public Task BadRequestIfHostHeaderDoesNotMatchRequestTarget(string requestTarget, string host) + { + return TestBadRequest( + $"{requestTarget} HTTP/1.1\r\nHost: {host}\r\n\r\n", + "400 Bad Request", + $"Invalid Host header: '{host.Trim()}'"); + } + [Fact] public async Task BadRequestLogsAreNotHigherThanInformation() { @@ -211,5 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public static TheoryData UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData; public static IEnumerable InvalidRequestHeaderData => HttpParsingData.RequestHeaderInvalidData; + + public static TheoryData InvalidHostHeaderData => HttpParsingData.HostHeaderInvalidData; } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedRequestTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedRequestTests.cs index be1718d76a..c6e8adbdb2 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedRequestTests.cs @@ -66,6 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "POST / HTTP/1.0", + "Host:", "Transfer-Encoding: chunked", "", "5", "Hello", @@ -95,6 +96,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "POST / HTTP/1.0", + "Host:", "Transfer-Encoding: chunked", "Connection: keep-alive", "", @@ -146,15 +148,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "POST / HTTP/1.1", + "Host:", "Content-Length: 5", "", "HelloPOST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C", "HelloChunked", "0", "", "POST / HTTP/1.1", + "Host:", "Content-Length: 7", "", "Goodbye"); @@ -225,6 +230,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests IEnumerable sendSequence = new string[] { "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C", @@ -236,6 +242,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C", @@ -247,6 +254,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", + "Host:", "Content-Length: 7", "", "Goodbye" @@ -286,6 +294,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", $"{transferEncodingHeaderLine}", $"{headerLine}", "", @@ -327,6 +336,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", $"{transferEncodingHeaderLine}", $"{headerLine}", "", @@ -396,6 +406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests IEnumerable sendSequence = new string[] { "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C;hello there", @@ -407,6 +418,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C;hello there", @@ -418,6 +430,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", + "Host:", "Content-Length: 7", "", "Goodbye" @@ -459,6 +472,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "Cii"); @@ -502,6 +516,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", "C", @@ -535,6 +550,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: not-chunked", "", "C", @@ -557,6 +573,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: not-chunked", "Content-Length: 22", "", @@ -579,6 +596,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked, not-chunked", "", "C", @@ -601,6 +619,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.SendAll( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked, not-chunked", "Content-Length: 22", "", diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedResponseTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedResponseTests.cs index d68283a1ab..dc953808ce 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ChunkedResponseTests.cs @@ -41,6 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host:", "", ""); await connection.ReceiveEnd( @@ -104,6 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "Connection: close", "", ""); @@ -141,6 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.ReceiveEnd( @@ -178,6 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.ReceiveEnd( @@ -218,6 +222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); @@ -256,6 +261,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.ReceiveEnd( @@ -289,6 +295,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // client closing the connection. await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.ReceiveForcedEnd( @@ -321,6 +328,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // SendEnd is not called, so it isn't the client closing the connection. await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); @@ -358,6 +366,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.Receive( @@ -401,6 +410,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host: ", "", ""); await connection.ReceiveEnd( diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/DefaultHeaderTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/DefaultHeaderTests.cs index 91ff1816cf..c9c1a94ae6 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/DefaultHeaderTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/DefaultHeaderTests.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host:", "", "GET / HTTP/1.0", "", @@ -46,4 +47,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs index f692537ea7..e2f40011a7 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests using (var connection = server.CreateConnection()) { await connection.SendAll("GET / HTTP/1.1", + "Host:", "", "") .TimeoutAfter(TimeSpan.FromSeconds(10)); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs index dc075abf9e..391441ed5b 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs @@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, checkCertificateRevocation: false); - var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"); + var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await sslStream.WriteAsync(request, 0, request.Length); await sslStream.ReadAsync(new byte[32], 0, 32); } @@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, checkCertificateRevocation: false); - var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"); + var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await sslStream.WriteAsync(request, 0, request.Length); await sslStream.ReadAsync(new byte[32], 0, 32); } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs index 01b070dac6..64dd29e7cc 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/KeepAliveTimeoutTests.cs @@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host:", "", ""); await ReceiveResponse(connection); @@ -72,6 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET / HTTP/1.1", + "Host:", "", ""); await Task.Delay(ShortDelay); @@ -93,6 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await connection.Send( "POST / HTTP/1.1", + "Host:", "Transfer-Encoding: chunked", "", ""); @@ -120,6 +123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET /longrunning HTTP/1.1", + "Host:", "", ""); cts.CancelAfter(LongDelay); @@ -133,6 +137,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await connection.Send( "GET / HTTP/1.1", + "Host:", "", ""); await ReceiveResponse(connection); @@ -154,6 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { await connection.Send( "GET /upgrade HTTP/1.1", + "Host:", "", ""); await connection.Receive( diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs index 6452832639..2b9c7dcd46 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs @@ -89,8 +89,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // Initialize data with random bytes (new Random()).NextBytes(data); - var startReadingRequestBody = new ManualResetEvent(false); - var clientFinishedSendingRequestBody = new ManualResetEvent(false); + var startReadingRequestBody = new TaskCompletionSource(); + var clientFinishedSendingRequestBody = new TaskCompletionSource(); var lastBytesWritten = DateTime.MaxValue; using (var host = StartWebHost(maxRequestBufferSize, data, connectionAdapter, startReadingRequestBody, clientFinishedSendingRequestBody)) @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } Assert.Equal(data.Length, bytesWritten); - clientFinishedSendingRequestBody.Set(); + clientFinishedSendingRequestBody.TrySetResult(null); }; var sendTask = sendFunc(); @@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.InRange(bytesWritten, minimumExpectedBytesWritten, maximumExpectedBytesWritten); // Tell server to start reading request body - startReadingRequestBody.Set(); + startReadingRequestBody.TrySetResult(null); // Wait for sendTask to finish sending the remaining bytes await sendTask; @@ -160,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await sendTask; // Tell server to start reading request body - startReadingRequestBody.Set(); + startReadingRequestBody.TrySetResult(null); } using (var reader = new StreamReader(stream, Encoding.ASCII)) @@ -181,8 +181,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var bytesWrittenPollingInterval = TimeSpan.FromMilliseconds(bytesWrittenTimeout.TotalMilliseconds / 10); var maxSendSize = 4096; - var startReadingRequestBody = new ManualResetEvent(false); - var clientFinishedSendingRequestBody = new ManualResetEvent(false); + var startReadingRequestBody = new TaskCompletionSource(); + var clientFinishedSendingRequestBody = new TaskCompletionSource(); var lastBytesWritten = DateTime.MaxValue; using (var host = StartWebHost(16 * 1024, data, false, startReadingRequestBody, clientFinishedSendingRequestBody)) @@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests lastBytesWritten = DateTime.Now; } - clientFinishedSendingRequestBody.Set(); + clientFinishedSendingRequestBody.TrySetResult(null); }; var ignore = sendFunc(); @@ -244,8 +244,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - private static IWebHost StartWebHost(long? maxRequestBufferSize, byte[] expectedBody, bool useConnectionAdapter, ManualResetEvent startReadingRequestBody, - ManualResetEvent clientFinishedSendingRequestBody) + private static IWebHost StartWebHost(long? maxRequestBufferSize, + byte[] expectedBody, + bool useConnectionAdapter, + TaskCompletionSource startReadingRequestBody, + TaskCompletionSource clientFinishedSendingRequestBody) { var host = new WebHostBuilder() .UseKestrel(options => @@ -275,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests .UseContentRoot(Directory.GetCurrentDirectory()) .Configure(app => app.Run(async context => { - startReadingRequestBody.WaitOne(); + await startReadingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(30)); var buffer = new byte[expectedBody.Length]; var bytesRead = 0; @@ -284,7 +287,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests bytesRead += await context.Request.Body.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead); } - clientFinishedSendingRequestBody.WaitOne(); + await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(30)); // Verify client didn't send extra bytes if (context.Request.Body.ReadByte() != -1) diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestLineSizeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestLineSizeTests.cs index 8e3dd5d22e..0ac7785d11 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestLineSizeTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestLineSizeTests.cs @@ -12,18 +12,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public class MaxRequestLineSizeTests { [Theory] - [InlineData("GET / HTTP/1.1\r\n\r\n", 16)] - [InlineData("GET / HTTP/1.1\r\n\r\n", 17)] - [InlineData("GET / HTTP/1.1\r\n\r\n", 137)] - [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 23)] - [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 24)] - [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 287)] - [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 28)] - [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 29)] - [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 589)] - [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 40)] - [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 41)] - [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 1027)] + [InlineData("GET / HTTP/1.1\r\nHost:\r\n\r\n", 16)] + [InlineData("GET / HTTP/1.1\r\nHost:\r\n\r\n", 17)] + [InlineData("GET / HTTP/1.1\r\nHost:\r\n\r\n", 137)] + [InlineData("POST /abc/de HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 23)] + [InlineData("POST /abc/de HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 24)] + [InlineData("POST /abc/de HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 287)] + [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 28)] + [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 29)] + [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nHost:\r\nContent-Length: 0\r\n\r\n", 589)] + [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\nHost:\r\n\r\n", 40)] + [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\nHost:\r\n\r\n", 41)] + [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\nHost:\r\n\r\n", 1027)] public async Task ServerAcceptsRequestLineWithinLimit(string request, int limit) { using (var server = CreateServer(limit)) diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.csproj index 21a47f7c0d..cd10cab0df 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.csproj @@ -8,6 +8,7 @@ x64 true true + true