Host header format validation
This commit is contained in:
parent
bcea8330c4
commit
bfdb48717f
|
|
@ -0,0 +1,115 @@
|
|||
// 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.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||
{
|
||||
public class Http1ConnectionBenchmark
|
||||
{
|
||||
private const int InnerLoopCount = 512;
|
||||
|
||||
private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>();
|
||||
|
||||
private ReadOnlySequence<byte> _buffer;
|
||||
|
||||
public Http1Connection Connection { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var memoryPool = KestrelMemoryPool.Create();
|
||||
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
||||
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||
|
||||
var serviceContext = new ServiceContext
|
||||
{
|
||||
ServerOptions = new KestrelServerOptions(),
|
||||
HttpParser = NullParser<Http1ParsingHandler>.Instance
|
||||
};
|
||||
|
||||
var http1Connection = new Http1Connection(context: new Http1ConnectionContext
|
||||
{
|
||||
ServiceContext = serviceContext,
|
||||
ConnectionFeatures = new FeatureCollection(),
|
||||
MemoryPool = memoryPool,
|
||||
TimeoutControl = new MockTimeoutControl(),
|
||||
Application = pair.Application,
|
||||
Transport = pair.Transport
|
||||
});
|
||||
|
||||
http1Connection.Reset();
|
||||
|
||||
Connection = http1Connection;
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||
public void PlaintextTechEmpower()
|
||||
{
|
||||
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
|
||||
public void LiveAspNet()
|
||||
{
|
||||
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(RequestParsingData.LiveaspnetRequest);
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertData(byte[] data)
|
||||
{
|
||||
_buffer = new ReadOnlySequence<byte>(data);
|
||||
}
|
||||
|
||||
private void ParseData()
|
||||
{
|
||||
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
||||
_buffer = _buffer.Slice(consumed, _buffer.End);
|
||||
|
||||
if (!_parser.ParseHeaders(new Adapter(this), _buffer, out consumed, out examined, out var consumedBytes))
|
||||
{
|
||||
ErrorUtilities.ThrowInvalidRequestHeaders();
|
||||
}
|
||||
|
||||
Connection.EnsureHostHeaderExists();
|
||||
|
||||
Connection.Reset();
|
||||
}
|
||||
|
||||
private struct Adapter : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||
{
|
||||
public Http1ConnectionBenchmark RequestHandler;
|
||||
|
||||
public Adapter(Http1ConnectionBenchmark requestHandler)
|
||||
{
|
||||
RequestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||
=> RequestHandler.Connection.OnHeader(name, value);
|
||||
|
||||
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||
=> RequestHandler.Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Protocols.Abstractions;
|
||||
|
|
@ -354,13 +352,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
private void EnsureHostHeaderExists()
|
||||
internal void EnsureHostHeaderExists()
|
||||
{
|
||||
if (_httpVersion == Http.HttpVersion.Http10)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7230#section-5.4
|
||||
// A server MUST respond with a 400 (Bad Request) status code to any
|
||||
// HTTP/1.1 request message that lacks a Host header field and to any
|
||||
|
|
@ -368,8 +361,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// Host header field with an invalid field-value.
|
||||
|
||||
var host = HttpRequestHeaders.HeaderHost;
|
||||
var hostText = host.ToString();
|
||||
if (host.Count <= 0)
|
||||
{
|
||||
if (_httpVersion == Http.HttpVersion.Http10)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
|
||||
}
|
||||
else if (host.Count > 1)
|
||||
|
|
@ -380,7 +378,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
if (!host.Equals(RawTarget))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, in host);
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||
}
|
||||
}
|
||||
else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
|
||||
|
|
@ -392,13 +390,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
// System.Uri doesn't not tell us if the port was in the original string or not.
|
||||
// When IsDefaultPort = true, we will allow Host: with or without the default port
|
||||
var authorityAndPort = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port;
|
||||
if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort)
|
||||
&& host != authorityAndPort)
|
||||
if (host != _absoluteRequestTarget.Authority)
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, in host);
|
||||
if (!_absoluteRequestTarget.IsDefaultPort
|
||||
|| host != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!HttpUtilities.IsValidHostHeader(hostText))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnReset()
|
||||
|
|
|
|||
|
|
@ -261,6 +261,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
if (_integerDecoder.BeginDecode((byte)(b & ~HuffmanMask), StringLengthPrefix))
|
||||
{
|
||||
OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue);
|
||||
if (_integerDecoder.Value == 0)
|
||||
{
|
||||
ProcessHeaderValue(handler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -272,6 +276,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
if (_integerDecoder.Decode(b))
|
||||
{
|
||||
OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue);
|
||||
if (_integerDecoder.Value == 0)
|
||||
{
|
||||
ProcessHeaderValue(handler);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -280,17 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
|
||||
if (_stringIndex == _stringLength)
|
||||
{
|
||||
OnString(nextState: State.Ready);
|
||||
|
||||
var headerNameSpan = new Span<byte>(_headerName, 0, _headerNameLength);
|
||||
var headerValueSpan = new Span<byte>(_headerValueOctets, 0, _headerValueLength);
|
||||
|
||||
handler.OnHeader(headerNameSpan, headerValueSpan);
|
||||
|
||||
if (_index)
|
||||
{
|
||||
_dynamicTable.Insert(headerNameSpan, headerValueSpan);
|
||||
}
|
||||
ProcessHeaderValue(handler);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -314,6 +312,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
}
|
||||
}
|
||||
|
||||
private void ProcessHeaderValue(IHttpHeadersHandler handler)
|
||||
{
|
||||
OnString(nextState: State.Ready);
|
||||
|
||||
var headerNameSpan = new Span<byte>(_headerName, 0, _headerNameLength);
|
||||
var headerValueSpan = new Span<byte>(_headerValueOctets, 0, _headerValueLength);
|
||||
|
||||
handler.OnHeader(headerNameSpan, headerValueSpan);
|
||||
|
||||
if (_index)
|
||||
{
|
||||
_dynamicTable.Insert(headerNameSpan, headerValueSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler)
|
||||
{
|
||||
var header = GetHeader(index);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
|
|
@ -54,12 +56,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
// We don't need any of the parameters because we don't implement BeginRead to actually
|
||||
// do the reading from a pipeline, nor do we use endConnection to report connection-level errors.
|
||||
|
||||
_httpVersion = Http.HttpVersion.Http2;
|
||||
var methodText = RequestHeaders[":method"];
|
||||
Method = HttpUtilities.GetKnownMethod(methodText);
|
||||
_methodText = methodText;
|
||||
|
||||
Scheme = RequestHeaders[":scheme"];
|
||||
_httpVersion = Http.HttpVersion.Http2;
|
||||
if (!string.Equals(RequestHeaders[":scheme"], Scheme, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
|
||||
}
|
||||
|
||||
var path = RequestHeaders[":path"].ToString();
|
||||
var queryIndex = path.IndexOf('?');
|
||||
|
|
@ -68,7 +72,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
QueryString = queryIndex == -1 ? string.Empty : path.Substring(queryIndex);
|
||||
RawTarget = path;
|
||||
|
||||
RequestHeaders["Host"] = RequestHeaders[":authority"];
|
||||
// https://tools.ietf.org/html/rfc7230#section-5.4
|
||||
// A server MUST respond with a 400 (Bad Request) status code to any
|
||||
// HTTP/1.1 request message that lacks a Host header field and to any
|
||||
// request message that contains more than one Host header field or a
|
||||
// Host header field with an invalid field-value.
|
||||
|
||||
var authority = RequestHeaders[":authority"];
|
||||
var host = HttpRequestHeaders.HeaderHost;
|
||||
if (authority.Count > 0)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
||||
// An intermediary that converts an HTTP/2 request to HTTP/1.1 MUST
|
||||
// create a Host header field if one is not present in a request by
|
||||
// copying the value of the ":authority" pseudo - header field.
|
||||
//
|
||||
// We take this one step further, we don't want mismatched :authority
|
||||
// and Host headers, replace Host if :authority is defined.
|
||||
HttpRequestHeaders.HeaderHost = authority;
|
||||
host = authority;
|
||||
}
|
||||
|
||||
// TODO: OPTIONS * requests?
|
||||
// To ensure that the HTTP / 1.1 request line can be reproduced
|
||||
// accurately, this pseudo - header field MUST be omitted when
|
||||
// translating from an HTTP/ 1.1 request that has a request target in
|
||||
// origin or asterisk form(see[RFC7230], Section 5.3).
|
||||
// https://tools.ietf.org/html/rfc7230#section-5.3
|
||||
|
||||
if (host.Count <= 0)
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
|
||||
}
|
||||
else if (host.Count > 1)
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
|
||||
}
|
||||
|
||||
var hostText = host.ToString();
|
||||
if (!HttpUtilities.IsValidHostHeader(hostText))
|
||||
{
|
||||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||
}
|
||||
|
||||
endConnection = false;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
SetKnownMethod(_mask8Chars, _httpConnectMethodLong, HttpMethod.Connect, 7);
|
||||
SetKnownMethod(_mask8Chars, _httpOptionsMethodLong, HttpMethod.Options, 7);
|
||||
FillKnownMethodsGaps();
|
||||
InitializeHostCharValidity();
|
||||
_methodNames[(byte)HttpMethod.Connect] = HttpMethods.Connect;
|
||||
_methodNames[(byte)HttpMethod.Delete] = HttpMethods.Delete;
|
||||
_methodNames[(byte)HttpMethod.Get] = HttpMethods.Get;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
{
|
||||
public static partial class HttpUtilities
|
||||
{
|
||||
private static readonly bool[] HostCharValidity = new bool[127];
|
||||
|
||||
public const string Http10Version = "HTTP/1.0";
|
||||
public const string Http11Version = "HTTP/1.1";
|
||||
public const string Http2Version = "HTTP/2";
|
||||
|
|
@ -29,6 +31,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
private const ulong _http10VersionLong = 3471766442030158920; // GetAsciiStringAsLong("HTTP/1.0"); const results in better codegen
|
||||
private const ulong _http11VersionLong = 3543824036068086856; // GetAsciiStringAsLong("HTTP/1.1"); const results in better codegen
|
||||
|
||||
// Only called from the static constructor
|
||||
private static void InitializeHostCharValidity()
|
||||
{
|
||||
// Matches Http.Sys
|
||||
// Matches RFC 3986 except "*" / "+" / "," / ";" / "=" and "%" HEXDIG HEXDIG which are not allowed by Http.Sys
|
||||
HostCharValidity['!'] = true;
|
||||
HostCharValidity['$'] = true;
|
||||
HostCharValidity['&'] = true;
|
||||
HostCharValidity['\''] = true;
|
||||
HostCharValidity['('] = true;
|
||||
HostCharValidity[')'] = true;
|
||||
HostCharValidity['-'] = true;
|
||||
HostCharValidity['.'] = true;
|
||||
HostCharValidity['_'] = true;
|
||||
HostCharValidity['~'] = true;
|
||||
for (var ch = '0'; ch <= '9'; ch++)
|
||||
{
|
||||
HostCharValidity[ch] = true;
|
||||
}
|
||||
for (var ch = 'A'; ch <= 'Z'; ch++)
|
||||
{
|
||||
HostCharValidity[ch] = true;
|
||||
}
|
||||
for (var ch = 'a'; ch <= 'z'; ch++)
|
||||
{
|
||||
HostCharValidity[ch] = true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SetKnownMethod(ulong mask, ulong knownMethodUlong, HttpMethod knownMethod, int length)
|
||||
{
|
||||
|
|
@ -394,5 +425,107 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidHostHeader(string hostText)
|
||||
{
|
||||
// The spec allows empty values
|
||||
if (string.IsNullOrEmpty(hostText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hostText[0] == '[')
|
||||
{
|
||||
return IsValidIPv6Host(hostText);
|
||||
}
|
||||
|
||||
if (hostText[0] == ':')
|
||||
{
|
||||
// Only a port
|
||||
return false;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
for (; i < hostText.Length; i++)
|
||||
{
|
||||
if (!IsValidHostChar(hostText[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return IsValidHostPort(hostText, i);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsValidHostChar(char ch)
|
||||
{
|
||||
return ch < HostCharValidity.Length && HostCharValidity[ch];
|
||||
}
|
||||
|
||||
// The lead '[' was already checked
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsValidIPv6Host(string hostText)
|
||||
{
|
||||
for (var i = 1; i < hostText.Length; i++)
|
||||
{
|
||||
var ch = hostText[i];
|
||||
if (ch == ']')
|
||||
{
|
||||
// [::1] is the shortest valid IPv6 host
|
||||
if (i < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsValidHostPort(hostText, i + 1);
|
||||
}
|
||||
|
||||
if (!IsHex(ch) && ch != ':' && ch != '.')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Must contain a ']'
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsValidHostPort(string hostText, int offset)
|
||||
{
|
||||
if (offset == hostText.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hostText[offset] != ':' || hostText.Length == offset + 1)
|
||||
{
|
||||
// Must have at least one number after the colon if present.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = offset + 1; i < hostText.Length; i++)
|
||||
{
|
||||
if (!IsNumeric(hostText[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsNumeric(char ch)
|
||||
{
|
||||
return '0' <= ch && ch <= '9';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsHex(char ch)
|
||||
{
|
||||
return IsNumeric(ch)
|
||||
|| ('a' <= ch && ch <= 'f')
|
||||
|| ('A' <= ch && ch <= 'F');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
|||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -813,6 +814,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
mockMessageBody.Verify(body => body.ConsumeAsync(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Http10HostHeaderNotRequired()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.0";
|
||||
_http1Connection.EnsureHostHeaderExists();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Http10HostHeaderAllowed()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.0";
|
||||
_http1Connection.RequestHeaders[HeaderNames.Host] = "localhost:5000";
|
||||
_http1Connection.EnsureHostHeaderExists();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Http11EmptyHostHeaderAccepted()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.1";
|
||||
_http1Connection.RequestHeaders[HeaderNames.Host] = "";
|
||||
_http1Connection.EnsureHostHeaderExists();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Http11ValidHostHeadersAccepted()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.1";
|
||||
_http1Connection.RequestHeaders[HeaderNames.Host] = "localhost:5000";
|
||||
_http1Connection.EnsureHostHeaderExists();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BadRequestFor10BadHostHeaderFormat()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.0";
|
||||
_http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
|
||||
var ex = Assert.Throws<BadHttpRequestException>(() => _http1Connection.EnsureHostHeaderExists());
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BadRequestFor11BadHostHeaderFormat()
|
||||
{
|
||||
_http1Connection.HttpVersion = "HTTP/1.1";
|
||||
_http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
|
||||
var ex = Assert.Throws<BadHttpRequestException>(() => _http1Connection.EnsureHostHeaderExists());
|
||||
Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
|
||||
}
|
||||
|
||||
private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition)
|
||||
{
|
||||
const int MaxWaitLoop = 150;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
|
|
@ -33,6 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":method", "POST"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "localhost:80"),
|
||||
};
|
||||
|
||||
private static readonly IEnumerable<KeyValuePair<string, string>> _expectContinueRequestHeaders = new[]
|
||||
|
|
@ -40,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":method", "POST"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
|
||||
new KeyValuePair<string, string>(":scheme", "https"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>("expect", "100-continue"),
|
||||
};
|
||||
|
||||
|
|
@ -49,6 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "localhost:80"),
|
||||
new KeyValuePair<string, string>("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"),
|
||||
new KeyValuePair<string, string>("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
|
||||
new KeyValuePair<string, string>("accept-language", "en-US,en;q=0.5"),
|
||||
|
|
@ -67,6 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "localhost:80"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
|
|
@ -78,6 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "localhost:80"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
|
|
@ -110,6 +115,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
private readonly object _abortedStreamIdsLock = new object();
|
||||
|
||||
private readonly RequestDelegate _noopApplication;
|
||||
private readonly RequestDelegate _echoHost;
|
||||
private readonly RequestDelegate _readHeadersApplication;
|
||||
private readonly RequestDelegate _readTrailersApplication;
|
||||
private readonly RequestDelegate _bufferingApplication;
|
||||
|
|
@ -134,6 +140,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
_noopApplication = context => Task.CompletedTask;
|
||||
|
||||
_echoHost = context =>
|
||||
{
|
||||
context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host];
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_readHeadersApplication = context =>
|
||||
{
|
||||
foreach (var header in context.Request.Headers)
|
||||
|
|
@ -1178,6 +1191,311 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_InvalidAuthority_400Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "local=host:80"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("400", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_MissingAuthority_400Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("400", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_TwoHosts_400Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>("Host", "host1"),
|
||||
new KeyValuePair<string, string>("Host", "host2"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("400", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_EmptyAuthority_200Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", ""),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_EmptyAuthorityOverridesHost_200Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", ""),
|
||||
new KeyValuePair<string, string>("Host", "abc"),
|
||||
};
|
||||
await InitializeConnectionAsync(_echoHost);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 62,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||
Assert.Equal("", _decodedHeaders[HeaderNames.Host]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_AuthorityOverridesHost_200Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "def"),
|
||||
new KeyValuePair<string, string>("Host", "abc"),
|
||||
};
|
||||
await InitializeConnectionAsync(_echoHost);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 65,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||
Assert.Equal("def", _decodedHeaders[HeaderNames.Host]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_MissingAuthorityFallsBackToHost_200Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>("Host", "abc"),
|
||||
};
|
||||
await InitializeConnectionAsync(_echoHost);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 65,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||
Assert.Equal("abc", _decodedHeaders[HeaderNames.Host]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_AuthorityOverridesInvalidHost_200Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "def"),
|
||||
new KeyValuePair<string, string>("Host", "a=bc"),
|
||||
};
|
||||
await InitializeConnectionAsync(_echoHost);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 65,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||
Assert.Equal("def", _decodedHeaders[HeaderNames.Host]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HEADERS_Received_InvalidAuthorityWithValidHost_400Status()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(":method", "GET"),
|
||||
new KeyValuePair<string, string>(":path", "/"),
|
||||
new KeyValuePair<string, string>(":scheme", "http"),
|
||||
new KeyValuePair<string, string>(":authority", "d=ef"),
|
||||
new KeyValuePair<string, string>("Host", "abc"),
|
||||
};
|
||||
await InitializeConnectionAsync(_echoHost);
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("400", _decodedHeaders[":status"]);
|
||||
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PRIORITY_Received_StreamIdZero_ConnectionError()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -131,5 +131,100 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(knownString1, expected);
|
||||
Assert.Same(knownString1, knownString2);
|
||||
}
|
||||
|
||||
public static TheoryData<string> HostHeaderData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>() {
|
||||
"z",
|
||||
"1",
|
||||
"y:1",
|
||||
"1:1",
|
||||
"[ABCdef]",
|
||||
"[abcDEF]:0",
|
||||
"[abcdef:127.2355.1246.114]:0",
|
||||
"[::1]:80",
|
||||
"127.0.0.1:80",
|
||||
"900.900.900.900:9523547852",
|
||||
"foo",
|
||||
"foo:234",
|
||||
"foo.bar.baz",
|
||||
"foo.BAR.baz:46245",
|
||||
"foo.ba-ar.baz:46245",
|
||||
"-foo:1234",
|
||||
"xn--asdfaf:134",
|
||||
"-",
|
||||
"_",
|
||||
"~",
|
||||
"!",
|
||||
"$",
|
||||
"'",
|
||||
"(",
|
||||
")",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(HostHeaderData))]
|
||||
public void ValidHostHeadersParsed(string host)
|
||||
{
|
||||
Assert.True(HttpUtilities.IsValidHostHeader(host));
|
||||
}
|
||||
|
||||
public static TheoryData<string> HostHeaderInvalidData
|
||||
{
|
||||
get
|
||||
{
|
||||
// see https://tools.ietf.org/html/rfc7230#section-5.4
|
||||
var data = new TheoryData<string>() {
|
||||
"[]", // Too short
|
||||
"[::]", // Too short
|
||||
"[ghijkl]", // Non-hex
|
||||
"[afd:adf:123", // Incomplete
|
||||
"[afd:adf]123", // Missing :
|
||||
"[afd:adf]:", // Missing port digits
|
||||
"[afd adf]", // Space
|
||||
"[ad-314]", // dash
|
||||
":1234", // Missing host
|
||||
"a:b:c", // Missing []
|
||||
"::1", // Missing []
|
||||
"::", // Missing everything
|
||||
"abcd:1abcd", // Letters in port
|
||||
"abcd:1.2", // Dot in port
|
||||
"1.2.3.4:", // Missing port digits
|
||||
"1.2 .4", // Space
|
||||
};
|
||||
|
||||
// These aren't allowed anywhere in the host header
|
||||
var invalid = "\"#%*+,/;<=>?@[]\\^`{}|";
|
||||
foreach (var ch in invalid)
|
||||
{
|
||||
data.Add(ch.ToString());
|
||||
}
|
||||
|
||||
invalid = "!\"#$%&'()*+,/;<=>?@[]\\^_`{}|~-";
|
||||
foreach (var ch in invalid)
|
||||
{
|
||||
data.Add("[abd" + ch + "]:1234");
|
||||
}
|
||||
|
||||
invalid = "!\"#$%&'()*+,/;<=>?@[]\\^_`{}|~:abcABC-.";
|
||||
foreach (var ch in invalid)
|
||||
{
|
||||
data.Add("a.b.c:" + ch);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(HostHeaderInvalidData))]
|
||||
public void InvalidHostHeadersRejected(string host)
|
||||
{
|
||||
Assert.False(HttpUtilities.IsValidHostHeader(host));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,6 +132,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(host.Trim()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task BadRequestFor10BadHostHeaderFormat()
|
||||
{
|
||||
return TestBadRequest(
|
||||
$"GET / HTTP/1.0\r\nHost: a=b\r\n\r\n",
|
||||
"400 Bad Request",
|
||||
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task BadRequestFor11BadHostHeaderFormat()
|
||||
{
|
||||
return TestBadRequest(
|
||||
$"GET / HTTP/1.1\r\nHost: a=b\r\n\r\n",
|
||||
"400 Bad Request",
|
||||
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BadRequestLogsAreNotHigherThanInformation()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
{
|
||||
var requestTarget = new Uri(requestUrl, UriKind.Absolute);
|
||||
var host = requestTarget.Authority;
|
||||
if (!requestTarget.IsDefaultPort)
|
||||
if (requestTarget.IsDefaultPort)
|
||||
{
|
||||
host += ":" + requestTarget.Port;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
{{
|
||||
{4}
|
||||
FillKnownMethodsGaps();
|
||||
InitializeHostCharValidity();
|
||||
{5}
|
||||
}}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue