Improve HTTP parsing tests (#1393).

- Add several more test cases
- Share data between functional and unit tests
This commit is contained in:
Cesar Blum Silveira 2017-02-28 09:35:10 -08:00
parent f5ac8c4ebd
commit 568aaff9c4
6 changed files with 579 additions and 450 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26206.0
VisualStudioVersion = 15.0.26223.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
ProjectSection(SolutionItems) = preProject
test\shared\DummyApplication.cs = test\shared\DummyApplication.cs
test\shared\HttpClientSlim.cs = test\shared\HttpClientSlim.cs
test\shared\HttpParsingData.cs = test\shared\HttpParsingData.cs
test\shared\KestrelTestLoggerFactory.cs = test\shared\KestrelTestLoggerFactory.cs
test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs
test\shared\MockConnection.cs = test\shared\MockConnection.cs
@ -83,38 +84,38 @@ Global
{F510611A-3BEE-4B88-A613-5F4A74ED82A1}.Release|x86.Build.0 = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x64.ActiveCfg = Debug|x64
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x64.Build.0 = Debug|x64
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x86.ActiveCfg = Debug|x86
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x86.Build.0 = Debug|x86
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x64.ActiveCfg = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x64.Build.0 = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x86.ActiveCfg = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|x86.Build.0 = Debug|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|Any CPU.Build.0 = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x64.ActiveCfg = Release|x64
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x64.Build.0 = Release|x64
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x64.ActiveCfg = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x64.Build.0 = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x86.ActiveCfg = Release|Any CPU
{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|x86.Build.0 = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x64.ActiveCfg = Debug|x64
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x64.Build.0 = Debug|x64
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x86.ActiveCfg = Debug|x86
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x86.Build.0 = Debug|x86
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x64.Build.0 = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|x86.Build.0 = Debug|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|Any CPU.Build.0 = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x64.ActiveCfg = Release|x64
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x64.Build.0 = Release|x64
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x64.ActiveCfg = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x64.Build.0 = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x86.ActiveCfg = Release|Any CPU
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|x86.Build.0 = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x64.ActiveCfg = Debug|x64
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x64.Build.0 = Debug|x64
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x86.ActiveCfg = Debug|x86
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x86.Build.0 = Debug|x86
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x64.ActiveCfg = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x64.Build.0 = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x86.ActiveCfg = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|x86.Build.0 = Debug|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|Any CPU.Build.0 = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x64.ActiveCfg = Release|x64
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x64.Build.0 = Release|x64
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x64.ActiveCfg = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x64.Build.0 = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x86.ActiveCfg = Release|Any CPU
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|x86.Build.0 = Release|Any CPU
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@ -143,26 +144,26 @@ Global
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|x86.Build.0 = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.ActiveCfg = Debug|x64
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.Build.0 = Debug|x64
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.ActiveCfg = Debug|x86
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.Build.0 = Debug|x86
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.ActiveCfg = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x64.Build.0 = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.ActiveCfg = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|x86.Build.0 = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.Build.0 = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.ActiveCfg = Release|x64
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.Build.0 = Release|x64
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.ActiveCfg = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x64.Build.0 = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x86.ActiveCfg = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|x86.Build.0 = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x64.ActiveCfg = Debug|x64
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x64.Build.0 = Debug|x64
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x86.ActiveCfg = Debug|x86
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x86.Build.0 = Debug|x86
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x64.ActiveCfg = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x64.Build.0 = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x86.ActiveCfg = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Debug|x86.Build.0 = Debug|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|Any CPU.Build.0 = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.ActiveCfg = Release|x64
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.Build.0 = Release|x64
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.ActiveCfg = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.Build.0 = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.ActiveCfg = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection

View File

@ -1129,6 +1129,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
else if (ch == BytePercentage)
{
if (pathStart == -1)
{
// Empty path is illegal
RejectRequestLine(start, end);
}
needDecode = true;
}
@ -1268,7 +1274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
const int MaxRequestLineError = 32;
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxRequestLineError) : string.Empty);
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxRequestLineError) : string.Empty);
}
private static bool IsValidTokenChar(char c)

View File

@ -0,0 +1,136 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class BadHttpRequestTests
{
[Theory]
[MemberData(nameof(InvalidRequestLineData))]
public async Task TestInvalidRequestLines(string request)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll(request);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[MemberData(nameof(UnrecognizedHttpVersionData))]
public async Task TestInvalidRequestLinesWithUnrecognizedVersion(string httpVersion)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / {httpVersion}\r\n");
await ReceiveBadRequestResponse(connection, "505 HTTP Version Not Supported", server.Context.DateHeaderValue);
}
}
}
[Theory]
[MemberData(nameof(InvalidRequestHeaderData))]
public async Task TestInvalidHeaders(string rawHeaders)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\n{rawHeaders}");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Fact]
public async Task BadRequestWhenHeaderNameContainsNonASCIICharacters()
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll(
"GET / HTTP/1.1",
"H\u00eb\u00e4d\u00ebr: value",
"",
"");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.Send($"{method} / HTTP/1.1\r\n\r\n");
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.Send($"{method} / HTTP/1.0\r\n\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("NaN")]
[InlineData("-1")]
public async Task BadRequestIfContentLengthInvalid(string contentLength)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
{
await connection.ReceiveForcedEnd(
$"HTTP/1.1 {expectedResponseStatusCode}",
"Connection: close",
$"Date: {expectedDateHeaderValue}",
"Content-Length: 0",
"",
"");
}
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData.Select(data => new[] { data[0] });
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData.Select(data => new[] { data[0] });
}
}

View File

@ -1,231 +0,0 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class BadHttpRequestTests
{
// All test cases for this theory must end in '\n', otherwise the server will spin forever
[Theory]
// Incomplete request lines
[InlineData("G\r\n")]
[InlineData("GE\r\n")]
[InlineData("GET\r\n")]
[InlineData("GET \r\n")]
[InlineData("GET /\r\n")]
[InlineData("GET / \r\n")]
// Missing method
[InlineData(" \r\n")]
// Missing second space
[InlineData("/ \r\n")] // This fails trying to read the '/' because that's invalid for an HTTP method
[InlineData("GET /\r\n")]
// Missing target
[InlineData("GET \r\n")]
// Missing version
[InlineData("GET / \r\n")]
// Missing CR
[InlineData("GET / \n")]
// Missing LF after CR
[InlineData("GET / HTTP/1.0\rA\n")]
// Bad HTTP Methods (invalid according to RFC)
[InlineData("( / HTTP/1.0\r\n")]
[InlineData(") / HTTP/1.0\r\n")]
[InlineData("< / HTTP/1.0\r\n")]
[InlineData("> / HTTP/1.0\r\n")]
[InlineData("@ / HTTP/1.0\r\n")]
[InlineData(", / HTTP/1.0\r\n")]
[InlineData("; / HTTP/1.0\r\n")]
[InlineData(": / HTTP/1.0\r\n")]
[InlineData("\\ / HTTP/1.0\r\n")]
[InlineData("\" / HTTP/1.0\r\n")]
[InlineData("/ / HTTP/1.0\r\n")]
[InlineData("[ / HTTP/1.0\r\n")]
[InlineData("] / HTTP/1.0\r\n")]
[InlineData("? / HTTP/1.0\r\n")]
[InlineData("= / HTTP/1.0\r\n")]
[InlineData("{ / HTTP/1.0\r\n")]
[InlineData("} / HTTP/1.0\r\n")]
[InlineData("get@ / HTTP/1.0\r\n")]
[InlineData("post= / HTTP/1.0\r\n")]
public async Task TestInvalidRequestLines(string request)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll(request);
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("GET / H\r\n")]
[InlineData("GET / HT\r\n")]
[InlineData("GET / HTT\r\n")]
[InlineData("GET / HTTP\r\n")]
[InlineData("GET / HTTP/\r\n")]
[InlineData("GET / HTTP/1\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
[InlineData("GET / http/1.0\r\n")]
[InlineData("GET / http/1.1\r\n")]
[InlineData("GET / HTTP/1.1 \r\n")]
[InlineData("GET / HTTP/1.1a\r\n")]
[InlineData("GET / HTTP/1.2\r\n")]
[InlineData("GET / HTTP/3.0\r\n")]
[InlineData("GET / H\r\n")]
[InlineData("GET / HTTP/1.\r\n")]
[InlineData("GET / hello\r\n")]
[InlineData("GET / 8charact\r\n")]
public async Task TestInvalidRequestLinesWithUnsupportedVersion(string request)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll(request);
await ReceiveBadRequestResponse(connection, "505 HTTP Version Not Supported", server.Context.DateHeaderValue);
}
}
}
[Theory]
// Leading whitespace
[InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\n Header-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\n\tHeader-2: value2\r\n\r\n")]
// Missing LF
[InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")]
// Line folding
[InlineData("Header-1: multi\r\n line\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: multi\r\n line\r\n\r\n")]
// Missing ':'
[InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")]
// Whitespace in header name
[InlineData("Header 1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")]
[InlineData("Header-1 : value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1\t: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")]
public async Task TestInvalidHeaders(string rawHeaders)
{
using (var server = new TestServer(context => TaskCache.CompletedTask))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\n{rawHeaders}");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Fact]
public async Task BadRequestWhenNameHeaderNamesContainsNonASCIICharacters()
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll(
"GET / HTTP/1.1",
"H\u00eb\u00e4d\u00ebr: value",
"",
"");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("\0")]
[InlineData("%00")]
[InlineData("/\0")]
[InlineData("/%00")]
[InlineData("/\0\0")]
[InlineData("/%00%00")]
[InlineData("/%C8\0")]
[InlineData("/%E8%00%84")]
[InlineData("/%E8%85%00")]
[InlineData("/%F3%00%82%86")]
[InlineData("/%F3%85%00%82")]
[InlineData("/%F3%85%82%00")]
[InlineData("/%E8%85%00")]
[InlineData("/%E8%01%00")]
public async Task BadRequestIfPathContainsNullCharacters(string path)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET {path} HTTP/1.1\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.Send($"{method} / HTTP/1.1\r\n\r\n");
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.Send($"{method} / HTTP/1.0\r\n\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("NaN")]
[InlineData("-1")]
public async Task BadRequestIfContentLengthInvalid(string contentLength)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
{
await connection.ReceiveForcedEnd(
$"HTTP/1.1 {expectedResponseStatusCode}",
"Connection: close",
$"Date: {expectedDateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
@ -27,10 +28,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
private readonly TestFrame<object> _frame;
private readonly ServiceContext _serviceContext;
private readonly ConnectionContext _connectionContext;
private PipeFactory _pipelineFactory;
ReadCursor consumed;
ReadCursor examined;
private readonly PipeFactory _pipelineFactory;
private ReadCursor _consumed;
private ReadCursor _examined;
private class TestFrame<TContext> : Frame<TContext>
{
@ -88,13 +88,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header:value\r\n\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders) _frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, _consumed);
}
[Theory]
@ -113,13 +113,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, _consumed);
}
[Theory]
@ -137,13 +137,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, _consumed);
}
[Theory]
@ -160,32 +160,26 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, _consumed);
}
[Theory]
[InlineData("Header: line1\r\n line2\r\n\r\n")]
[InlineData("Header: line1\r\n\tline2\r\n\r\n")]
[InlineData("Header: line1\r\n line2\r\n\r\n")]
[InlineData("Header: line1\r\n \tline2\r\n\r\n")]
[InlineData("Header: line1\r\n\t line2\r\n\r\n")]
[InlineData("Header: line1\r\n\t\tline2\r\n\r\n")]
[InlineData("Header: line1\r\n \t\t line2\r\n\r\n")]
[InlineData("Header: line1\r\n \t \t line2\r\n\r\n")]
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding(string rawHeaders)
[MemberData(nameof(InvalidRequestHeaderData))]
public async Task TakeMessageHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
Assert.Equal("Header value line folding not supported.", exception.Message);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal(expectedExceptionMessage, exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
@ -195,107 +189,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.False(_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.False(_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Header value line folding not supported.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Theory]
[InlineData("Header-1: value1\r\r\n")]
[InlineData("Header-1: val\rue1\r\n")]
[InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: v\ralue2\r\n")]
public async Task TakeMessageHeadersThrowsOnHeaderValueContainingCR(string rawHeaders)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Header value must not contain CR characters.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Theory]
[InlineData("Header-1 value1\r\n\r\n")]
[InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")]
public async Task TakeMessageHeadersThrowsOnHeaderLineMissingColon(string rawHeaders)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("No ':' character found in header line.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Theory]
[InlineData(" Header: value\r\n\r\n")]
[InlineData("\tHeader: value\r\n\r\n")]
[InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")]
public async Task TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Header line must not start with whitespace.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Theory]
[InlineData("Header : value\r\n\r\n")]
[InlineData("Header\t: value\r\n\r\n")]
[InlineData("Header 1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header 1 : value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header 1\t: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")]
public async Task TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Theory]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r\r")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r ")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r \n")]
public async Task TakeMessageHeadersThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Headers corrupted, invalid header sequence.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
[Fact]
public async Task TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
{
@ -306,8 +212,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Request headers too long.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
@ -322,8 +228,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Request contains too many headers.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
@ -341,12 +247,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(success);
Assert.Equal(numHeaders, _frame.RequestHeaders.Count);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, _consumed);
}
[Fact]
@ -375,8 +281,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count);
@ -387,8 +293,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count);
@ -478,6 +384,29 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Same(originalDuplexStream, _frame.DuplexStream);
}
[Theory]
[MemberData(nameof(ValidRequestLineData))]
public async Task TakeStartLineSetsFrameProperties(
string requestLine,
string expectedMethod,
string expectedPath,
string expectedQueryString,
string expectedHttpVersion)
{
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.True(returnValue);
Assert.Equal(expectedMethod, _frame.Method);
Assert.Equal(expectedPath, _frame.Path);
Assert.Equal(expectedQueryString, _frame.QueryString);
Assert.Equal(expectedHttpVersion, _frame.HttpVersion);
}
[Fact]
public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
{
@ -485,21 +414,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal(readableBuffer.Start, consumed);
Assert.Equal(readableBuffer.End, examined);
Assert.Equal(readableBuffer.Start, _consumed);
Assert.Equal(readableBuffer.End, _examined);
requestLineBytes = Encoding.ASCII.GetBytes("HTTP/1.1\r\n");
await _socketInput.Writer.WriteAsync(requestLineBytes);
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, examined);
Assert.Equal(readableBuffer.End, _consumed);
Assert.Equal(readableBuffer.End, _examined);
}
[Theory]
@ -524,8 +453,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var returnValue = _frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.False(returnValue);
}
@ -538,8 +467,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
_frame.TakeStartLine((await _socketInput.Reader.ReadAsync()).Buffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
_frame.TakeStartLine((await _socketInput.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
@ -554,79 +483,58 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() =>_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Request line too long.", exception.Message);
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
}
[Theory]
[InlineData("GET/HTTP/1.1\r\n", "Invalid request line: GET/HTTP/1.1<0x0D><0x0A>")]
[InlineData(" / HTTP/1.1\r\n", "Invalid request line: / HTTP/1.1<0x0D><0x0A>")]
[InlineData("GET? / HTTP/1.1\r\n", "Invalid request line: GET? / HTTP/1.1<0x0D><0x0A>")]
[InlineData("GET /HTTP/1.1\r\n", "Invalid request line: GET /HTTP/1.1<0x0D><0x0A>")]
[InlineData("GET /a?b=cHTTP/1.1\r\n", "Invalid request line: GET /a?b=cHTTP/1.1<0x0D><0x0A>")]
[InlineData("GET /a%20bHTTP/1.1\r\n", "Invalid request line: GET /a%20bHTTP/1.1<0x0D><0x0A>")]
[InlineData("GET /a%20b?c=dHTTP/1.1\r\n", "Invalid request line: GET /a%20b?c=dHTTP/1.1<0x0D><0x0A>")]
[InlineData("GET HTTP/1.1\r\n", "Invalid request line: GET HTTP/1.1<0x0D><0x0A>")]
[InlineData("GET / HTTP/1.1\n", "Invalid request line: GET / HTTP/1.1<0x0A>")]
[InlineData("GET / \r\n", "Invalid request line: GET / <0x0D><0x0A>")]
[InlineData("GET ? HTTP/1.1\r\n", "Invalid request line: GET ? HTTP/1.1<0x0D><0x0A>")]
[InlineData("GET / HTTP/1.1\ra\n", "Invalid request line: GET / HTTP/1.1<0x0D>a<0x0A>")]
public async Task TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
[MemberData(nameof(InvalidRequestLineData))]
public async Task TakeStartLineThrowsOnInvalidRequestLine(string requestLine, Type expectedExceptionType, string expectedExceptionMessage)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws(expectedExceptionType, () =>
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal(expectedExceptionMessage, exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
if (expectedExceptionType == typeof(BadHttpRequestException))
{
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
}
}
[Fact]
public async Task TakeStartLineThrowsOnUnsupportedHttpVersion()
[Theory]
[MemberData(nameof(UnrecognizedHttpVersionData))]
public async Task TakeStartLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n"));
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET / {httpVersion}\r\n"));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal("Unrecognized HTTP version: HTTP/1.2", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
}
[Fact]
public async Task TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEightCharacters()
{
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.1ab\r\n");
await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal("Unrecognized HTTP version: HTTP/1.1ab", exception.Message);
Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
}
[Fact]
public async Task TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
{
foreach (var rawHeader in new [] { "Header: " , "value\r\n" , "\r\n"})
foreach (var rawHeader in new[] { "Header: ", "value\r\n", "\r\n" })
{
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal(readableBuffer.End, examined);
_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out _consumed, out _examined);
_socketInput.Reader.Advance(_consumed, _examined);
Assert.Equal(readableBuffer.End, _examined);
}
}
@ -831,5 +739,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
await _frame.ProduceEndAsync();
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
}
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.ValidRequestLineData;
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData;
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData;
}
}

View File

@ -0,0 +1,301 @@
// 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.Generic;
using System.Linq;
using Microsoft.AspNetCore.Server.Kestrel;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
public class HttpParsingData
{
public static IEnumerable<string[]> ValidRequestLineData
{
get
{
var methods = new[]
{
"GET",
"CUSTOM",
};
var targets = new[]
{
Tuple.Create("/", "/"),
Tuple.Create("/abc", "/abc"),
Tuple.Create("/abc/de/f", "/abc/de/f"),
Tuple.Create("/%20", "/ "),
Tuple.Create("/a%20", "/a "),
Tuple.Create("/%20a", "/ a"),
Tuple.Create("/a/b%20c", "/a/b c"),
Tuple.Create("/%C3%A5", "/\u00E5"),
Tuple.Create("/a%C3%A5a", "/a\u00E5a"),
Tuple.Create("/%C3%A5/bc", "/\u00E5/bc"),
Tuple.Create("/%25", "/%"),
Tuple.Create("/%2F", "/%2F"),
};
var queryStrings = new[]
{
"",
"?",
"?arg1=val1",
"?arg1=a%20b",
"?%A",
"?%20=space",
"?%C3%A5=val",
"?path=/home",
"?path=/%C3%A5/",
"?question=what?",
"?%00",
"?arg=%00"
};
var httpVersions = new[]
{
"HTTP/1.0",
"HTTP/1.1"
};
return from method in methods
from target in targets
from queryString in queryStrings
from httpVersion in httpVersions
select new[]
{
$"{method} {target.Item1}{queryString} {httpVersion}\r\n",
method,
$"{target.Item2}",
queryString,
httpVersion
};
}
}
// All these test cases must end in '\n', otherwise the server will spin forever
public static IEnumerable<object[]> InvalidRequestLineData
{
get
{
var invalidRequestLines = new[]
{
"G\r\n",
"GE\r\n",
"GET\r\n",
"GET \r\n",
"GET /\r\n",
"GET / \r\n",
"GET/HTTP/1.1\r\n",
"GET /HTTP/1.1\r\n",
" \r\n",
" \r\n",
"/ HTTP/1.1\r\n",
" / HTTP/1.1\r\n",
"/ \r\n",
"GET \r\n",
"GET HTTP/1.0\r\n",
"GET HTTP/1.1\r\n",
"GET / \n",
"GET / HTTP/1.0\n",
"GET / HTTP/1.1\n",
"GET / HTTP/1.0\rA\n",
"GET / HTTP/1.1\ra\n",
"GET? / HTTP/1.1\r\n",
"GET ? HTTP/1.1\r\n",
"GET /a?b=cHTTP/1.1\r\n",
"GET /a%20bHTTP/1.1\r\n",
"GET /a%20b?c=dHTTP/1.1\r\n",
"GET %2F HTTP/1.1\r\n",
"GET %00 HTTP/1.1\r\n",
"CUSTOM \r\n",
"CUSTOM /\r\n",
"CUSTOM / \r\n",
"CUSTOM /HTTP/1.1\r\n",
"CUSTOM \r\n",
"CUSTOM HTTP/1.0\r\n",
"CUSTOM HTTP/1.1\r\n",
"CUSTOM / \n",
"CUSTOM / HTTP/1.0\n",
"CUSTOM / HTTP/1.1\n",
"CUSTOM / HTTP/1.0\rA\n",
"CUSTOM / HTTP/1.1\ra\n",
"CUSTOM ? HTTP/1.1\r\n",
"CUSTOM /a?b=cHTTP/1.1\r\n",
"CUSTOM /a%20bHTTP/1.1\r\n",
"CUSTOM /a%20b?c=dHTTP/1.1\r\n",
"CUSTOM %2F HTTP/1.1\r\n",
"CUSTOM %00 HTTP/1.1\r\n",
// Bad HTTP Methods (invalid according to RFC)
"( / HTTP/1.0\r\n",
") / HTTP/1.0\r\n",
"< / HTTP/1.0\r\n",
"> / HTTP/1.0\r\n",
"@ / HTTP/1.0\r\n",
", / HTTP/1.0\r\n",
"; / HTTP/1.0\r\n",
": / HTTP/1.0\r\n",
"\\ / HTTP/1.0\r\n",
"\" / HTTP/1.0\r\n",
"/ / HTTP/1.0\r\n",
"[ / HTTP/1.0\r\n",
"] / HTTP/1.0\r\n",
"? / HTTP/1.0\r\n",
"= / HTTP/1.0\r\n",
"{ / HTTP/1.0\r\n",
"} / HTTP/1.0\r\n",
"get@ / HTTP/1.0\r\n",
"post= / HTTP/1.0\r\n",
};
var encodedNullCharInTargetRequestLines = new[]
{
"GET /%00 HTTP/1.1\r\n",
"GET /%00%00 HTTP/1.1\r\n",
"GET /%E8%00%84 HTTP/1.1\r\n",
"GET /%E8%85%00 HTTP/1.1\r\n",
"GET /%F3%00%82%86 HTTP/1.1\r\n",
"GET /%F3%85%00%82 HTTP/1.1\r\n",
"GET /%F3%85%82%00 HTTP/1.1\r\n",
"GET /%E8%85%00 HTTP/1.1\r\n",
"GET /%E8%01%00 HTTP/1.1\r\n",
};
var nullCharInTargetRequestLines = new[]
{
"GET \0 HTTP/1.1\r\n",
"GET /\0 HTTP/1.1\r\n",
"GET /\0\0 HTTP/1.1\r\n",
"GET /%C8\0 HTTP/1.1\r\n",
};
return invalidRequestLines.Select(requestLine => new object[]
{
requestLine,
typeof(BadHttpRequestException),
$"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}"
})
.Concat(encodedNullCharInTargetRequestLines.Select(requestLine => new object[]
{
requestLine,
typeof(InvalidOperationException),
$"The path contains null characters."
}))
.Concat(nullCharInTargetRequestLines.Select(requestLine => new object[]
{
requestLine,
typeof(InvalidOperationException),
new InvalidOperationException().Message
}));
}
}
public static TheoryData<string> UnrecognizedHttpVersionData
{
get
{
return new TheoryData<string>
{
"H",
"HT",
"HTT",
"HTTP",
"HTTP/",
"HTTP/1",
"HTTP/1.",
"http/1.0",
"http/1.1",
"HTTP/1.1 ",
"HTTP/1.0a",
"HTTP/1.0ab",
"HTTP/1.1a",
"HTTP/1.1ab",
"HTTP/1.2",
"HTTP/3.0",
"hello",
"8charact",
};
}
}
public static IEnumerable<object[]> InvalidRequestHeaderData
{
get
{
// Line folding
var headersWithLineFolding = new[]
{
"Header: line1\r\n line2\r\n\r\n",
"Header: line1\r\n\tline2\r\n\r\n",
"Header: line1\r\n line2\r\n\r\n",
"Header: line1\r\n \tline2\r\n\r\n",
"Header: line1\r\n\t line2\r\n\r\n",
"Header: line1\r\n\t\tline2\r\n\r\n",
"Header: line1\r\n \t\t line2\r\n\r\n",
"Header: line1\r\n \t \t line2\r\n\r\n",
"Header-1: multi\r\n line\r\nHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2: multi\r\n line\r\n\r\n",
"Header-1: value1\r\n Header-2: value2\r\n\r\n",
"Header-1: value1\r\n\tHeader-2: value2\r\n\r\n",
};
// CR in value
var headersWithCRInValue = new[]
{
"Header-1: value1\r\r\n",
"Header-1: val\rue1\r\n",
"Header-1: value1\rHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2: value2\r\r\n",
"Header-1: value1\r\nHeader-2: v\ralue2\r\n",
};
// Missing colon
var headersWithMissingColon = new[]
{
"Header-1 value1\r\n\r\n",
"Header-1 value1\r\nHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2 value2\r\n\r\n",
};
// Starting with whitespace
var headersStartingWithWhitespace = new[]
{
" Header: value\r\n\r\n",
"\tHeader: value\r\n\r\n",
" Header-1: value1\r\nHeader-2: value2\r\n\r\n",
"\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n",
};
// Whitespace in header name
var headersWithWithspaceInName = new[]
{
"Header : value\r\n\r\n",
"Header\t: value\r\n\r\n",
"Header 1: value1\r\nHeader-2: value2\r\n\r\n",
"Header 1 : value1\r\nHeader-2: value2\r\n\r\n",
"Header 1\t: value1\r\nHeader-2: value2\r\n\r\n",
"Header-1: value1\r\nHeader 2: value2\r\n\r\n",
"Header-1: value1\r\nHeader-2 : value2\r\n\r\n",
"Header-1: value1\r\nHeader-2\t: value2\r\n\r\n",
};
// Headers not ending in CRLF line
var headersNotEndingInCrLfLine = new[]
{
"Header-1: value1\r\nHeader-2: value2\r\n\r\r",
"Header-1: value1\r\nHeader-2: value2\r\n\r ",
"Header-1: value1\r\nHeader-2: value2\r\n\r \n",
};
return new[]
{
Tuple.Create(headersWithLineFolding,"Header value line folding not supported."),
Tuple.Create(headersWithCRInValue,"Header value must not contain CR characters."),
Tuple.Create(headersWithMissingColon,"No ':' character found in header line."),
Tuple.Create(headersStartingWithWhitespace, "Header line must not start with whitespace."),
Tuple.Create(headersWithWithspaceInName,"Whitespace is not allowed in header name."),
Tuple.Create(headersNotEndingInCrLfLine, "Headers corrupted, invalid header sequence.")
}
.SelectMany(t => t.Item1.Select(headers => new[] { headers, t.Item2 }));
}
}
}
}