Convert HTTP parsing FrameTests to IHttpParser tests (#1416).
- Also fix an issue in KestrelHttpParser where "Header: \r\n" is parsed with a value of " " instead of "".
This commit is contained in:
parent
e2f8c226ef
commit
5743d740b4
|
|
@ -459,7 +459,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
|
||||
// Ignore end whitespace
|
||||
for (; valueEnd > valueStart; valueEnd--)
|
||||
for (; valueEnd >= valueStart; valueEnd--)
|
||||
{
|
||||
var ch = headerLine[valueEnd];
|
||||
if (ch != ByteTab && ch != ByteSpace)
|
||||
|
|
|
|||
|
|
@ -1,9 +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.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
|
|
@ -127,25 +125,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"");
|
||||
}
|
||||
|
||||
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData
|
||||
.Select(requestLine => new object[]
|
||||
public static TheoryData<string, string> InvalidRequestLineData
|
||||
{
|
||||
get
|
||||
{
|
||||
requestLine,
|
||||
$"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}",
|
||||
})
|
||||
.Concat(HttpParsingData.EncodedNullCharInTargetRequestLines.Select(requestLine => new object[]
|
||||
{
|
||||
requestLine,
|
||||
"Invalid request line."
|
||||
}))
|
||||
.Concat(HttpParsingData.NullCharInTargetRequestLines.Select(requestLine => new object[]
|
||||
{
|
||||
requestLine,
|
||||
"Invalid request line."
|
||||
}));
|
||||
var data = new TheoryData<string, string>();
|
||||
|
||||
foreach (var requestLine in HttpParsingData.RequestLineInvalidData)
|
||||
{
|
||||
data.Add(requestLine, $"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}");
|
||||
}
|
||||
|
||||
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
||||
{
|
||||
data.Add(requestLine, "Invalid request line.");
|
||||
}
|
||||
|
||||
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
||||
{
|
||||
data.Add(requestLine, "Invalid request line.");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||
|
||||
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData;
|
||||
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.RequestHeaderInvalidData;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
|
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
public class FrameTests : IDisposable
|
||||
{
|
||||
private readonly IPipe _socketInput;
|
||||
private readonly IPipe _input;
|
||||
private readonly TestFrame<object> _frame;
|
||||
private readonly ServiceContext _serviceContext;
|
||||
private readonly ConnectionContext _connectionContext;
|
||||
|
|
@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||
_pipelineFactory = new PipeFactory();
|
||||
_socketInput = _pipelineFactory.Create();
|
||||
_input = _pipelineFactory.Create();
|
||||
|
||||
_serviceContext = new ServiceContext
|
||||
{
|
||||
|
|
@ -65,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
};
|
||||
_connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
Input = _socketInput,
|
||||
Input = _input,
|
||||
Output = new MockSocketOutput(),
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
|
@ -77,149 +76,30 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_socketInput.Reader.Complete();
|
||||
_socketInput.Writer.Complete();
|
||||
_input.Reader.Complete();
|
||||
_input.Writer.Complete();
|
||||
_pipelineFactory.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadHeaderValueWithoutLeadingWhitespace()
|
||||
{
|
||||
_frame.InitializeHeaders();
|
||||
|
||||
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, 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);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Header: value\r\n\r\n")]
|
||||
[InlineData("Header: value\r\n\r\n")]
|
||||
[InlineData("Header:\tvalue\r\n\r\n")]
|
||||
[InlineData("Header: \tvalue\r\n\r\n")]
|
||||
[InlineData("Header:\t value\r\n\r\n")]
|
||||
[InlineData("Header:\t\tvalue\r\n\r\n")]
|
||||
[InlineData("Header:\t\t value\r\n\r\n")]
|
||||
[InlineData("Header: \t\tvalue\r\n\r\n")]
|
||||
[InlineData("Header: \t\t value\r\n\r\n")]
|
||||
[InlineData("Header: \t \t value\r\n\r\n")]
|
||||
public async Task LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var success = _frame.TakeMessageHeaders(readableBuffer, 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);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Header: value \r\n\r\n")]
|
||||
[InlineData("Header: value\t\r\n\r\n")]
|
||||
[InlineData("Header: value \t\r\n\r\n")]
|
||||
[InlineData("Header: value\t \r\n\r\n")]
|
||||
[InlineData("Header: value\t\t\r\n\r\n")]
|
||||
[InlineData("Header: value\t\t \r\n\r\n")]
|
||||
[InlineData("Header: value \t\t\r\n\r\n")]
|
||||
[InlineData("Header: value \t\t \r\n\r\n")]
|
||||
[InlineData("Header: value \t \t \r\n\r\n")]
|
||||
public async Task TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var success = _frame.TakeMessageHeaders(readableBuffer, 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);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Header: one two three\r\n\r\n", "one two three")]
|
||||
[InlineData("Header: one two three\r\n\r\n", "one two three")]
|
||||
[InlineData("Header: one\ttwo\tthree\r\n\r\n", "one\ttwo\tthree")]
|
||||
[InlineData("Header: one two\tthree\r\n\r\n", "one two\tthree")]
|
||||
[InlineData("Header: one\ttwo three\r\n\r\n", "one\ttwo three")]
|
||||
[InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")]
|
||||
[InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")]
|
||||
[InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")]
|
||||
public async Task WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var success = _frame.TakeMessageHeaders(readableBuffer, 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);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[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;
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
Assert.False(_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
|
||||
|
||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TakeMessageHeadersConsumesBytesCorrectlyAtEnd()
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n\r"));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
Assert.False(_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("\n"));
|
||||
|
||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
Assert.True(_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
|
||||
{
|
||||
|
|
@ -227,11 +107,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
|
||||
_frame.Reset();
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal("Request headers too long.", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||
|
|
@ -243,36 +123,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
|
||||
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal("Request contains too many headers.", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Cookie: \r\n\r\n", 1)]
|
||||
[InlineData("Cookie:\r\n\r\n", 1)]
|
||||
[InlineData("Cookie: \r\nConnection: close\r\n\r\n", 2)]
|
||||
[InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)]
|
||||
[InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)]
|
||||
[InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)]
|
||||
public async Task EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var success = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal(numHeaders, _frame.RequestHeaders.Count);
|
||||
Assert.Equal(readableBuffer.End, _consumed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetResetsScheme()
|
||||
{
|
||||
|
|
@ -296,11 +156,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
options.Limits.MaxRequestHeaderCount = 1;
|
||||
_serviceContext.ServerOptions = options;
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
||||
|
|
@ -308,11 +168,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
_frame.Reset();
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||
readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.True(takeMessageHeaders);
|
||||
Assert.Equal(1, _frame.RequestHeaders.Count);
|
||||
|
|
@ -407,86 +267,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public async Task TakeStartLineSetsFrameProperties(
|
||||
string requestLine,
|
||||
string expectedMethod,
|
||||
string expectedPath,
|
||||
string expectedRawTarget,
|
||||
string expectedRawPath,
|
||||
string expectedDecodedPath,
|
||||
string expectedQueryString,
|
||||
string expectedHttpVersion)
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.True(returnValue);
|
||||
Assert.Equal(expectedMethod, _frame.Method);
|
||||
Assert.Equal(expectedPath, _frame.Path);
|
||||
Assert.Equal(expectedRawTarget, _frame.RawTarget);
|
||||
Assert.Equal(expectedDecodedPath, _frame.Path);
|
||||
Assert.Equal(expectedQueryString, _frame.QueryString);
|
||||
Assert.Equal(expectedHttpVersion, _frame.HttpVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET / ");
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
|
||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _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);
|
||||
|
||||
Assert.Equal(readableBuffer.End, _consumed);
|
||||
Assert.Equal(readableBuffer.End, _examined);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("G")]
|
||||
[InlineData("GE")]
|
||||
[InlineData("GET")]
|
||||
[InlineData("GET ")]
|
||||
[InlineData("GET /")]
|
||||
[InlineData("GET / ")]
|
||||
[InlineData("GET / H")]
|
||||
[InlineData("GET / HT")]
|
||||
[InlineData("GET / HTT")]
|
||||
[InlineData("GET / HTTP")]
|
||||
[InlineData("GET / HTTP/")]
|
||||
[InlineData("GET / HTTP/1")]
|
||||
[InlineData("GET / HTTP/1.")]
|
||||
[InlineData("GET / HTTP/1.1")]
|
||||
[InlineData("GET / HTTP/1.1\r")]
|
||||
public async Task TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine)
|
||||
{
|
||||
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.False(returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||
{
|
||||
var connectionControl = new Mock<IConnectionControl>();
|
||||
_connectionContext.ConnectionControl = connectionControl.Object;
|
||||
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||
|
||||
_frame.ParseRequest((await _socketInput.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||
|
|
@ -498,93 +309,42 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
|
||||
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
await _input.Writer.WriteAsync(requestLineBytes);
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal("Request line too long.", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidRequestLineData))]
|
||||
public async Task TakeStartLineThrowsOnInvalidRequestLine(string requestLine, Type expectedExceptionType, string expectedExceptionMessage)
|
||||
[MemberData(nameof(RequestLineWithEncodedNullCharInTargetData))]
|
||||
public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string requestLine)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var exception = Assert.Throws(expectedExceptionType, () =>
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
||||
|
||||
if (expectedExceptionType == typeof(BadHttpRequestException))
|
||||
{
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||
}
|
||||
Assert.Equal("The path contains null characters.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
||||
public async Task TakeStartLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
|
||||
[MemberData(nameof(RequestLineWithNullCharInTargetData))]
|
||||
public async Task TakeStartLineThrowsOnNullCharInTarget(string requestLine)
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"GET / {httpVersion}\r\n"));
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||
var readableBuffer = (await _input.Reader.ReadAsync()).Buffer;
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
_frame.TakeStartLine(readableBuffer, out _consumed, out _examined));
|
||||
_input.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: {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" })
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
|
||||
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
_frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined);
|
||||
_socketInput.Reader.Advance(_consumed, _examined);
|
||||
Assert.Equal(readableBuffer.End, _examined);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\r")]
|
||||
[InlineData("H")]
|
||||
[InlineData("He")]
|
||||
[InlineData("Hea")]
|
||||
[InlineData("Head")]
|
||||
[InlineData("Heade")]
|
||||
[InlineData("Header")]
|
||||
[InlineData("Header:")]
|
||||
[InlineData("Header: ")]
|
||||
[InlineData("Header: v")]
|
||||
[InlineData("Header: va")]
|
||||
[InlineData("Header: val")]
|
||||
[InlineData("Header: valu")]
|
||||
[InlineData("Header: value")]
|
||||
[InlineData("Header: value\r")]
|
||||
[InlineData("Header: value\r\n")]
|
||||
[InlineData("Header: value\r\n\r")]
|
||||
public async Task TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers)
|
||||
{
|
||||
var headerBytes = Encoding.ASCII.GetBytes(headers);
|
||||
await _socketInput.Writer.WriteAsync(headerBytes);
|
||||
|
||||
ReadCursor consumed;
|
||||
ReadCursor examined;
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
|
||||
Assert.Equal(false, _frame.TakeMessageHeaders(readableBuffer, out consumed, out examined));
|
||||
_socketInput.Reader.Advance(consumed, examined);
|
||||
Assert.Equal(new InvalidOperationException().Message, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -599,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||
|
||||
_frame.StopAsync();
|
||||
_socketInput.Writer.Complete();
|
||||
_input.Writer.Complete();
|
||||
|
||||
requestProcessingTask.Wait();
|
||||
}
|
||||
|
|
@ -681,13 +441,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
_frame.Start();
|
||||
|
||||
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
|
||||
await _socketInput.Writer.WriteAsync(data);
|
||||
await _input.Writer.WriteAsync(data);
|
||||
|
||||
var requestProcessingTask = _frame.StopAsync();
|
||||
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
|
||||
|
||||
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
_socketInput.Writer.Complete();
|
||||
_input.Writer.Complete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -758,30 +518,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.NotSame(original, _frame.RequestAborted.WaitHandle);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.ValidRequestLineData;
|
||||
public static IEnumerable<object> ValidRequestLineData => HttpParsingData.RequestLineValidData;
|
||||
|
||||
public static IEnumerable<object> InvalidRequestLineData => HttpParsingData.InvalidRequestLineData
|
||||
.Select(requestLine => new object[]
|
||||
public static TheoryData<string> RequestLineWithEncodedNullCharInTargetData
|
||||
{
|
||||
get
|
||||
{
|
||||
requestLine,
|
||||
typeof(BadHttpRequestException),
|
||||
$"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}",
|
||||
})
|
||||
.Concat(HttpParsingData.EncodedNullCharInTargetRequestLines.Select(requestLine => new object[]
|
||||
{
|
||||
requestLine,
|
||||
typeof(InvalidOperationException),
|
||||
"The path contains null characters."
|
||||
}))
|
||||
.Concat(HttpParsingData.NullCharInTargetRequestLines.Select(requestLine => new object[]
|
||||
{
|
||||
requestLine,
|
||||
typeof(InvalidOperationException),
|
||||
new InvalidOperationException().Message
|
||||
}));
|
||||
var data = new TheoryData<string>();
|
||||
|
||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||
foreach (var requestLine in HttpParsingData.RequestLineWithEncodedNullCharInTargetData)
|
||||
{
|
||||
data.Add(requestLine);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.InvalidRequestHeaderData;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string> RequestLineWithNullCharInTargetData
|
||||
{
|
||||
get
|
||||
{
|
||||
var data = new TheoryData<string>();
|
||||
|
||||
foreach (var requestLine in HttpParsingData.RequestLineWithNullCharInTargetData)
|
||||
{
|
||||
data.Add(requestLine);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,383 @@
|
|||
// 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.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class HttpParserTests
|
||||
{
|
||||
// Returns true when all headers parsed
|
||||
// Return false otherwise
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestLineValidData))]
|
||||
public void ParsesRequestLine(
|
||||
string requestLine,
|
||||
string expectedMethod,
|
||||
string expectedRawTarget,
|
||||
string expectedRawPath,
|
||||
string expectedDecodedPath,
|
||||
string expectedQueryString,
|
||||
string expectedVersion)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
string parsedMethod = null;
|
||||
string parsedVersion = null;
|
||||
string parsedRawTarget = null;
|
||||
string parsedRawPath = null;
|
||||
string parsedQuery = null;
|
||||
var requestLineHandler = new Mock<IHttpRequestLineHandler>();
|
||||
requestLineHandler
|
||||
.Setup(handler => handler.OnStartLine(
|
||||
It.IsAny<HttpMethod>(),
|
||||
It.IsAny<HttpVersion>(),
|
||||
It.IsAny<Span<byte>>(),
|
||||
It.IsAny<Span<byte>>(),
|
||||
It.IsAny<Span<byte>>(),
|
||||
It.IsAny<Span<byte>>()))
|
||||
.Callback<HttpMethod, HttpVersion, Span<byte>, Span<byte>, Span<byte>, Span<byte>>((method, version, target, path, query, customMethod) =>
|
||||
{
|
||||
parsedMethod = method != HttpMethod.Custom ? HttpUtilities.MethodToString(method) : customMethod.GetAsciiStringNonNullCharacters();
|
||||
parsedVersion = HttpUtilities.VersionToString(version);
|
||||
parsedRawTarget = target.GetAsciiStringNonNullCharacters();
|
||||
parsedRawPath = path.GetAsciiStringNonNullCharacters();
|
||||
parsedQuery = query.GetAsciiStringNonNullCharacters();
|
||||
});
|
||||
|
||||
Assert.True(parser.ParseRequestLine(requestLineHandler.Object, buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(parsedMethod, expectedMethod);
|
||||
Assert.Equal(parsedVersion, expectedVersion);
|
||||
Assert.Equal(parsedRawTarget, expectedRawTarget);
|
||||
Assert.Equal(parsedRawPath, expectedRawPath);
|
||||
Assert.Equal(parsedVersion, expectedVersion);
|
||||
Assert.Equal(buffer.End, consumed);
|
||||
Assert.Equal(buffer.End, examined);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestLineIncompleteData))]
|
||||
public void ParseRequestLineReturnsFalseWhenGivenIncompleteRequestLines(string requestLine)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
Assert.False(parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestLineIncompleteData))]
|
||||
public void ParseRequestLineDoesNotConsumeIncompleteRequestLine(string requestLine)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
Assert.False(parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal(buffer.Start, consumed);
|
||||
Assert.Equal(buffer.End, examined);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestLineInvalidData))]
|
||||
public void ParseRequestLineThrowsOnInvalidRequestLine(string requestLine)
|
||||
{
|
||||
var mockTrace = new Mock<IKestrelTrace>();
|
||||
mockTrace
|
||||
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||
.Returns(true);
|
||||
|
||||
var parser = CreateParser(mockTrace.Object);
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal($"Invalid request line: {requestLine.Replace("\r", "<0x0D>").Replace("\n", "<0x0A>")}", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(UnrecognizedHttpVersionData))]
|
||||
public void ParseRequestLineThrowsOnUnrecognizedHttpVersion(string httpVersion)
|
||||
{
|
||||
var requestLine = $"GET / {httpVersion}\r\n";
|
||||
|
||||
var mockTrace = new Mock<IKestrelTrace>();
|
||||
mockTrace
|
||||
.Setup(trace => trace.IsEnabled(LogLevel.Information))
|
||||
.Returns(true);
|
||||
|
||||
var parser = CreateParser(mockTrace.Object);
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
parser.ParseRequestLine(Mock.Of<IHttpRequestLineHandler>(), buffer, out var consumed, out var examined));
|
||||
|
||||
Assert.Equal($"Unrecognized HTTP version: {httpVersion}", exception.Message);
|
||||
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\r")]
|
||||
[InlineData("H")]
|
||||
[InlineData("He")]
|
||||
[InlineData("Hea")]
|
||||
[InlineData("Head")]
|
||||
[InlineData("Heade")]
|
||||
[InlineData("Header")]
|
||||
[InlineData("Header:")]
|
||||
[InlineData("Header: ")]
|
||||
[InlineData("Header: v")]
|
||||
[InlineData("Header: va")]
|
||||
[InlineData("Header: val")]
|
||||
[InlineData("Header: valu")]
|
||||
[InlineData("Header: value")]
|
||||
[InlineData("Header: value\r")]
|
||||
[InlineData("Header: value\r\n")]
|
||||
[InlineData("Header: value\r\n\r")]
|
||||
[InlineData("Header-1: value1\r\nH")]
|
||||
[InlineData("Header-1: value1\r\nHe")]
|
||||
[InlineData("Header-1: value1\r\nHea")]
|
||||
[InlineData("Header-1: value1\r\nHead")]
|
||||
[InlineData("Header-1: value1\r\nHeade")]
|
||||
[InlineData("Header-1: value1\r\nHeader")]
|
||||
[InlineData("Header-1: value1\r\nHeader-")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2:")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: ")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: v")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: va")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: val")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: valu")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: value")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: value2")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: value2\r")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n")]
|
||||
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r")]
|
||||
public void ParseHeadersReturnsFalseWhenGivenIncompleteHeaders(string rawHeaders)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
Assert.False(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\r")]
|
||||
[InlineData("H")]
|
||||
[InlineData("He")]
|
||||
[InlineData("Hea")]
|
||||
[InlineData("Head")]
|
||||
[InlineData("Heade")]
|
||||
[InlineData("Header")]
|
||||
[InlineData("Header:")]
|
||||
[InlineData("Header: ")]
|
||||
[InlineData("Header: v")]
|
||||
[InlineData("Header: va")]
|
||||
[InlineData("Header: val")]
|
||||
[InlineData("Header: valu")]
|
||||
[InlineData("Header: value")]
|
||||
[InlineData("Header: value\r")]
|
||||
public void ParseHeadersDoesNotConsumeIncompleteHeader(string rawHeaders)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes);
|
||||
|
||||
Assert.Equal(buffer.Start, consumed);
|
||||
Assert.Equal(buffer.End, examined);
|
||||
Assert.Equal(0, consumedBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseHeadersCanReadHeaderValueWithoutLeadingWhitespace()
|
||||
{
|
||||
VerifyHeader("Header", "value", "value");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Cookie: \r\n\r\n", "Cookie", "", null, null)]
|
||||
[InlineData("Cookie:\r\n\r\n", "Cookie", "", null, null)]
|
||||
[InlineData("Cookie: \r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
|
||||
[InlineData("Cookie:\r\nConnection: close\r\n\r\n", "Cookie", "", "Connection", "close")]
|
||||
[InlineData("Connection: close\r\nCookie: \r\n\r\n", "Connection", "close", "Cookie", "")]
|
||||
[InlineData("Connection: close\r\nCookie:\r\n\r\n", "Connection", "close", "Cookie", "")]
|
||||
public void ParseHeadersCanParseEmptyHeaderValues(
|
||||
string rawHeaders,
|
||||
string expectedHeaderName1,
|
||||
string expectedHeaderValue1,
|
||||
string expectedHeaderName2,
|
||||
string expectedHeaderValue2)
|
||||
{
|
||||
var expectedHeaderNames = expectedHeaderName2 == null
|
||||
? new[] { expectedHeaderName1 }
|
||||
: new[] { expectedHeaderName1, expectedHeaderName2 };
|
||||
var expectedHeaderValues = expectedHeaderValue2 == null
|
||||
? new[] { expectedHeaderValue1 }
|
||||
: new[] { expectedHeaderValue1, expectedHeaderValue2 };
|
||||
|
||||
VerifyRawHeaders(rawHeaders, expectedHeaderNames, expectedHeaderValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(" value")]
|
||||
[InlineData(" value")]
|
||||
[InlineData("\tvalue")]
|
||||
[InlineData(" \tvalue")]
|
||||
[InlineData("\t value")]
|
||||
[InlineData("\t\tvalue")]
|
||||
[InlineData("\t\t value")]
|
||||
[InlineData(" \t\tvalue")]
|
||||
[InlineData(" \t\t value")]
|
||||
[InlineData(" \t \t value")]
|
||||
public void ParseHeadersDoesNotIncludeLeadingWhitespaceInHeaderValue(string rawHeaderValue)
|
||||
{
|
||||
VerifyHeader("Header", rawHeaderValue, "value");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("value ")]
|
||||
[InlineData("value\t")]
|
||||
[InlineData("value \t")]
|
||||
[InlineData("value\t ")]
|
||||
[InlineData("value\t\t")]
|
||||
[InlineData("value\t\t ")]
|
||||
[InlineData("value \t\t")]
|
||||
[InlineData("value \t\t ")]
|
||||
[InlineData("value \t \t ")]
|
||||
public void ParseHeadersDoesNotIncludeTrailingWhitespaceInHeaderValue(string rawHeaderValue)
|
||||
{
|
||||
VerifyHeader("Header", rawHeaderValue, "value");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("one two three")]
|
||||
[InlineData("one two three")]
|
||||
[InlineData("one\ttwo\tthree")]
|
||||
[InlineData("one two\tthree")]
|
||||
[InlineData("one\ttwo three")]
|
||||
[InlineData("one \ttwo \tthree")]
|
||||
[InlineData("one\t two\t three")]
|
||||
[InlineData("one \ttwo\t three")]
|
||||
public void ParseHeadersPreservesWhitespaceWithinHeaderValue(string headerValue)
|
||||
{
|
||||
VerifyHeader("Header", headerValue, headerValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseHeadersConsumesBytesCorrectlyAtEnd()
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
|
||||
const string headerLine = "Header: value\r\n\r";
|
||||
var buffer1 = ReadableBuffer.Create(Encoding.ASCII.GetBytes(headerLine));
|
||||
Assert.False(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer1, out var consumed, out var examined, out var consumedBytes));
|
||||
|
||||
Assert.Equal(buffer1.Move(buffer1.Start, headerLine.Length - 1), consumed);
|
||||
Assert.Equal(buffer1.End, examined);
|
||||
Assert.Equal(headerLine.Length - 1, consumedBytes);
|
||||
|
||||
var buffer2 = ReadableBuffer.Create(Encoding.ASCII.GetBytes("\r\n"));
|
||||
Assert.True(parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer2, out consumed, out examined, out consumedBytes));
|
||||
|
||||
Assert.Equal(buffer2.End, consumed);
|
||||
Assert.Equal(buffer2.End, examined);
|
||||
Assert.Equal(2, consumedBytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequestHeaderInvalidData))]
|
||||
public void ParseHeadersThrowsOnInvalidRequestHeaders(string rawHeaders, string expectedExceptionMessage)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
var exception = Assert.Throws<BadHttpRequestException>(() =>
|
||||
parser.ParseHeaders(Mock.Of<IHttpHeadersHandler>(), buffer, out var consumed, out var examined, out var consumedBytes));
|
||||
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message);
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
private void VerifyHeader(
|
||||
string headerName,
|
||||
string rawHeaderValue,
|
||||
string expectedHeaderValue)
|
||||
{
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes($"{headerName}:{rawHeaderValue}\r\n"));
|
||||
|
||||
string parsedHeaderName = "unexpected";
|
||||
string parsedHeaderValue = "unexpected";
|
||||
var headersHandler = new Mock<IHttpHeadersHandler>();
|
||||
headersHandler
|
||||
.Setup(handler => handler.OnHeader(It.IsAny<Span<byte>>(), It.IsAny<Span<byte>>()))
|
||||
.Callback<Span<byte>, Span<byte>>((name, value) =>
|
||||
{
|
||||
parsedHeaderName = name.GetAsciiStringNonNullCharacters();
|
||||
parsedHeaderValue = value.GetAsciiStringNonNullCharacters();
|
||||
});
|
||||
|
||||
parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
|
||||
|
||||
Assert.Equal(headerName, parsedHeaderName);
|
||||
Assert.Equal(expectedHeaderValue, parsedHeaderValue);
|
||||
Assert.Equal(buffer.End, consumed);
|
||||
Assert.Equal(buffer.End, examined);
|
||||
}
|
||||
|
||||
private void VerifyRawHeaders(string rawHeaders, IEnumerable<string> expectedHeaderNames, IEnumerable<string> expectedHeaderValues)
|
||||
{
|
||||
Assert.True(expectedHeaderNames.Count() == expectedHeaderValues.Count(), $"{nameof(expectedHeaderNames)} and {nameof(expectedHeaderValues)} sizes must match");
|
||||
|
||||
var parser = CreateParser(Mock.Of<IKestrelTrace>());
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
var parsedHeaders = new List<Tuple<string, string>>();
|
||||
var headersHandler = new Mock<IHttpHeadersHandler>();
|
||||
headersHandler
|
||||
.Setup(handler => handler.OnHeader(It.IsAny<Span<byte>>(), It.IsAny<Span<byte>>()))
|
||||
.Callback<Span<byte>, Span<byte>>((name, value) =>
|
||||
{
|
||||
parsedHeaders.Add(Tuple.Create(name.GetAsciiStringNonNullCharacters(), value.GetAsciiStringNonNullCharacters()));
|
||||
});
|
||||
|
||||
parser.ParseHeaders(headersHandler.Object, buffer, out var consumed, out var examined, out var consumedBytes);
|
||||
|
||||
Assert.Equal(expectedHeaderNames.Count(), parsedHeaders.Count);
|
||||
Assert.Equal(expectedHeaderNames, parsedHeaders.Select(t => t.Item1));
|
||||
Assert.Equal(expectedHeaderValues, parsedHeaders.Select(t => t.Item2));
|
||||
Assert.Equal(buffer.End, consumed);
|
||||
Assert.Equal(buffer.End, examined);
|
||||
}
|
||||
|
||||
private IHttpParser CreateParser(IKestrelTrace log) => new KestrelHttpParser(log);
|
||||
|
||||
public static IEnumerable<string[]> RequestLineValidData => HttpParsingData.RequestLineValidData;
|
||||
|
||||
public static IEnumerable<object[]> RequestLineIncompleteData => HttpParsingData.RequestLineIncompleteData.Select(requestLine => new[] { requestLine });
|
||||
|
||||
public static IEnumerable<object[]> RequestLineInvalidData => HttpParsingData.RequestLineInvalidData.Select(requestLine => new[] { requestLine });
|
||||
|
||||
public static TheoryData<string> UnrecognizedHttpVersionData => HttpParsingData.UnrecognizedHttpVersionData;
|
||||
|
||||
public static IEnumerable<object[]> RequestHeaderInvalidData => HttpParsingData.RequestHeaderInvalidData;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
{
|
||||
public class HttpParsingData
|
||||
{
|
||||
public static IEnumerable<string[]> ValidRequestLineData
|
||||
public static IEnumerable<string[]> RequestLineValidData
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
"GET",
|
||||
"CUSTOM",
|
||||
};
|
||||
var targets = new[]
|
||||
var paths = new[]
|
||||
{
|
||||
Tuple.Create("/", "/"),
|
||||
Tuple.Create("/abc", "/abc"),
|
||||
|
|
@ -56,21 +56,42 @@ namespace Microsoft.AspNetCore.Testing
|
|||
};
|
||||
|
||||
return from method in methods
|
||||
from target in targets
|
||||
from path in paths
|
||||
from queryString in queryStrings
|
||||
from httpVersion in httpVersions
|
||||
select new[]
|
||||
{
|
||||
$"{method} {target.Item1}{queryString} {httpVersion}\r\n",
|
||||
$"{method} {path.Item1}{queryString} {httpVersion}\r\n",
|
||||
method,
|
||||
$"{target.Item2}",
|
||||
$"{path.Item1}{queryString}",
|
||||
$"{path.Item1}",
|
||||
$"{path.Item2}",
|
||||
queryString,
|
||||
httpVersion
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> InvalidRequestLineData => new[]
|
||||
public static IEnumerable<string> RequestLineIncompleteData => new[]
|
||||
{
|
||||
"G",
|
||||
"GE",
|
||||
"GET",
|
||||
"GET ",
|
||||
"GET /",
|
||||
"GET / ",
|
||||
"GET / H",
|
||||
"GET / HT",
|
||||
"GET / HTT",
|
||||
"GET / HTTP",
|
||||
"GET / HTTP/",
|
||||
"GET / HTTP/1",
|
||||
"GET / HTTP/1.",
|
||||
"GET / HTTP/1.1",
|
||||
"GET / HTTP/1.1\r",
|
||||
};
|
||||
|
||||
public static IEnumerable<string> RequestLineInvalidData => new[]
|
||||
{
|
||||
"G\r\n",
|
||||
"GE\r\n",
|
||||
|
|
@ -140,7 +161,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
"post= / HTTP/1.0\r\n",
|
||||
};
|
||||
|
||||
public static IEnumerable<string> EncodedNullCharInTargetRequestLines => new[]
|
||||
public static IEnumerable<string> RequestLineWithEncodedNullCharInTargetData => new[]
|
||||
{
|
||||
"GET /%00 HTTP/1.1\r\n",
|
||||
"GET /%00%00 HTTP/1.1\r\n",
|
||||
|
|
@ -149,17 +170,16 @@ namespace Microsoft.AspNetCore.Testing
|
|||
"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",
|
||||
};
|
||||
|
||||
public static IEnumerable<string> 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",
|
||||
};
|
||||
public static IEnumerable<string> RequestLineWithNullCharInTargetData => 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",
|
||||
};
|
||||
|
||||
public static TheoryData<string> UnrecognizedHttpVersionData => new TheoryData<string>
|
||||
{
|
||||
|
|
@ -183,7 +203,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
"8charact",
|
||||
};
|
||||
|
||||
public static IEnumerable<object[]> InvalidRequestHeaderData
|
||||
public static IEnumerable<object[]> RequestHeaderInvalidData
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -228,6 +248,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
"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",
|
||||
"\n"
|
||||
};
|
||||
|
||||
// Starting with whitespace
|
||||
|
|
@ -273,11 +294,11 @@ namespace Microsoft.AspNetCore.Testing
|
|||
|
||||
return new[]
|
||||
{
|
||||
Tuple.Create(headersWithLineFolding,"Whitespace is not allowed in header name."),
|
||||
Tuple.Create(headersWithCRInValue,"Header value must not contain CR characters."),
|
||||
Tuple.Create(headersWithMissingColon,"No ':' character found in header line."),
|
||||
Tuple.Create(headersWithLineFolding, "Whitespace is not allowed in header name."),
|
||||
Tuple.Create(headersWithCRInValue, "Header value must not contain CR characters."),
|
||||
Tuple.Create(headersWithMissingColon, "No ':' character found in header line."),
|
||||
Tuple.Create(headersStartingWithWhitespace, "Whitespace is not allowed in header name."),
|
||||
Tuple.Create(headersWithWithspaceInName,"Whitespace is not allowed in header name."),
|
||||
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 }));
|
||||
|
|
|
|||
Loading…
Reference in New Issue