Consolidate HTTP charset validation logic
This commit is contained in:
parent
864cfeb2aa
commit
6551eae321
|
|
@ -524,6 +524,9 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="Http2ErrorInvalidPreface" xml:space="preserve">
|
||||
<value>Invalid HTTP/2 connection preface.</value>
|
||||
</data>
|
||||
<data name="InvalidEmptyHeaderName" xml:space="preserve">
|
||||
<value>Header name cannot be a null or empty string.</value>
|
||||
</data>
|
||||
<data name="ConnectionOrStreamAbortedByCancellationToken" xml:space="preserve">
|
||||
<value>The connection or stream was aborted because a write operation was aborted with a CancellationToken.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -274,13 +274,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
// This is not complete validation. It is just a quick scan for invalid characters
|
||||
// but doesn't check that the target fully matches the URI spec.
|
||||
for (var i = 0; i < target.Length; i++)
|
||||
if (HttpCharacters.ContainsInvalidAuthorityChar(target))
|
||||
{
|
||||
var ch = target[i];
|
||||
if (!UriUtilities.IsValidAuthorityCharacter(ch))
|
||||
{
|
||||
ThrowRequestTargetRejected(target);
|
||||
}
|
||||
ThrowRequestTargetRejected(target);
|
||||
}
|
||||
|
||||
// The authority-form of request-target is only used for CONNECT
|
||||
|
|
|
|||
|
|
@ -5862,7 +5862,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected override void SetValueFast(string key, in StringValues value)
|
||||
{
|
||||
ValidateHeaderCharacters(value);
|
||||
ValidateHeaderValueCharacters(value);
|
||||
switch (key.Length)
|
||||
{
|
||||
case 13:
|
||||
|
|
@ -6167,7 +6167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected override bool AddValueFast(string key, in StringValues value)
|
||||
{
|
||||
ValidateHeaderCharacters(value);
|
||||
ValidateHeaderValueCharacters(value);
|
||||
switch (key.Length)
|
||||
{
|
||||
case 13:
|
||||
|
|
@ -6611,7 +6611,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
break;
|
||||
}
|
||||
|
||||
ValidateHeaderCharacters(key);
|
||||
ValidateHeaderNameCharacters(key);
|
||||
Unknown.Add(key, value);
|
||||
// Return true, above will throw and exit for false
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
|
|
@ -45,6 +46,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
ThrowHeadersReadOnlyException();
|
||||
}
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
ThrowInvalidEmtpyHeaderName();
|
||||
}
|
||||
if (value.Count == 0)
|
||||
{
|
||||
RemoveFast(key);
|
||||
|
|
@ -170,6 +175,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
ThrowHeadersReadOnlyException();
|
||||
}
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
ThrowInvalidEmtpyHeaderName();
|
||||
}
|
||||
|
||||
if (value.Count > 0 && !AddValueFast(key, value))
|
||||
{
|
||||
|
|
@ -241,30 +250,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return TryGetValueFast(key, out value);
|
||||
}
|
||||
|
||||
public static void ValidateHeaderCharacters(in StringValues headerValues)
|
||||
public static void ValidateHeaderValueCharacters(in StringValues headerValues)
|
||||
{
|
||||
var count = headerValues.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
|
||||
{
|
||||
ValidateHeaderCharacters(headerValues[i]);
|
||||
ValidateHeaderValueCharacters(headerValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateHeaderCharacters(string headerCharacters)
|
||||
public static void ValidateHeaderValueCharacters(string headerCharacters)
|
||||
{
|
||||
if (headerCharacters != null)
|
||||
{
|
||||
foreach (var ch in headerCharacters)
|
||||
var invalid = HttpCharacters.IndexOfInvalidFieldValueChar(headerCharacters);
|
||||
if (invalid >= 0)
|
||||
{
|
||||
if (ch < 0x20 || ch > 0x7E)
|
||||
{
|
||||
ThrowInvalidHeaderCharacter(ch);
|
||||
}
|
||||
ThrowInvalidHeaderCharacter(headerCharacters[invalid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateHeaderNameCharacters(string headerCharacters)
|
||||
{
|
||||
var invalid = HttpCharacters.IndexOfInvalidTokenChar(headerCharacters);
|
||||
if (invalid >= 0)
|
||||
{
|
||||
ThrowInvalidHeaderCharacter(headerCharacters[invalid]);
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe ConnectionOptions ParseConnection(in StringValues connection)
|
||||
{
|
||||
var connectionOptions = ConnectionOptions.None;
|
||||
|
|
@ -440,5 +456,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
throw new InvalidOperationException(CoreStrings.FormatInvalidAsciiOrControlChar(string.Format("0x{0:X4}", (ushort)ch)));
|
||||
}
|
||||
|
||||
private static void ThrowInvalidEmtpyHeaderName()
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.InvalidEmptyHeaderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -363,6 +363,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var valueEnd = length - 3;
|
||||
var nameEnd = FindEndOfName(headerLine, length);
|
||||
|
||||
// Header name is empty
|
||||
if (nameEnd == 0)
|
||||
{
|
||||
RejectRequestHeader(headerLine, length);
|
||||
}
|
||||
|
||||
if (headerLine[valueEnd + 2] != ByteLF)
|
||||
{
|
||||
RejectRequestHeader(headerLine, length);
|
||||
|
|
@ -437,55 +443,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
|
||||
{
|
||||
methodLength = 0;
|
||||
for (var i = 0; i < length; i++)
|
||||
var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(data, length);
|
||||
|
||||
if (invalidIndex <= 0 || data[invalidIndex] != ByteSpace)
|
||||
{
|
||||
var ch = data[i];
|
||||
|
||||
if (ch == ByteSpace)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
|
||||
methodLength = i;
|
||||
break;
|
||||
}
|
||||
else if (!IsValidTokenChar((char)ch))
|
||||
{
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
RejectRequestLine(data, length);
|
||||
}
|
||||
|
||||
methodLength = invalidIndex;
|
||||
return new Span<byte>(data, methodLength);
|
||||
}
|
||||
|
||||
private static bool IsValidTokenChar(char c)
|
||||
{
|
||||
// Determines if a character is valid as a 'token' as defined in the
|
||||
// HTTP spec: https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
return
|
||||
(c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
c == '!' ||
|
||||
c == '#' ||
|
||||
c == '$' ||
|
||||
c == '%' ||
|
||||
c == '&' ||
|
||||
c == '\'' ||
|
||||
c == '*' ||
|
||||
c == '+' ||
|
||||
c == '-' ||
|
||||
c == '.' ||
|
||||
c == '^' ||
|
||||
c == '_' ||
|
||||
c == '`' ||
|
||||
c == '|' ||
|
||||
c == '~';
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
private unsafe void RejectRequestLine(byte* requestLine, int length)
|
||||
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SetValueUnknown(string key, in StringValues value)
|
||||
{
|
||||
ValidateHeaderCharacters(key);
|
||||
ValidateHeaderNameCharacters(key);
|
||||
Unknown[key] = value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Connections.Abstractions
|
|||
{
|
||||
internal class UrlDecoder
|
||||
{
|
||||
static bool[] IsAllowed = new bool[0x7F + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Unescape a URL path
|
||||
/// </summary>
|
||||
|
|
@ -334,78 +332,5 @@ namespace Microsoft.AspNetCore.Connections.Abstractions
|
|||
return false;
|
||||
}
|
||||
|
||||
static UrlDecoder()
|
||||
{
|
||||
// Unreserved
|
||||
IsAllowed['A'] = true;
|
||||
IsAllowed['B'] = true;
|
||||
IsAllowed['C'] = true;
|
||||
IsAllowed['D'] = true;
|
||||
IsAllowed['E'] = true;
|
||||
IsAllowed['F'] = true;
|
||||
IsAllowed['G'] = true;
|
||||
IsAllowed['H'] = true;
|
||||
IsAllowed['I'] = true;
|
||||
IsAllowed['J'] = true;
|
||||
IsAllowed['K'] = true;
|
||||
IsAllowed['L'] = true;
|
||||
IsAllowed['M'] = true;
|
||||
IsAllowed['N'] = true;
|
||||
IsAllowed['O'] = true;
|
||||
IsAllowed['P'] = true;
|
||||
IsAllowed['Q'] = true;
|
||||
IsAllowed['R'] = true;
|
||||
IsAllowed['S'] = true;
|
||||
IsAllowed['T'] = true;
|
||||
IsAllowed['U'] = true;
|
||||
IsAllowed['V'] = true;
|
||||
IsAllowed['W'] = true;
|
||||
IsAllowed['X'] = true;
|
||||
IsAllowed['Y'] = true;
|
||||
IsAllowed['Z'] = true;
|
||||
|
||||
IsAllowed['a'] = true;
|
||||
IsAllowed['b'] = true;
|
||||
IsAllowed['c'] = true;
|
||||
IsAllowed['d'] = true;
|
||||
IsAllowed['e'] = true;
|
||||
IsAllowed['f'] = true;
|
||||
IsAllowed['g'] = true;
|
||||
IsAllowed['h'] = true;
|
||||
IsAllowed['i'] = true;
|
||||
IsAllowed['j'] = true;
|
||||
IsAllowed['k'] = true;
|
||||
IsAllowed['l'] = true;
|
||||
IsAllowed['m'] = true;
|
||||
IsAllowed['n'] = true;
|
||||
IsAllowed['o'] = true;
|
||||
IsAllowed['p'] = true;
|
||||
IsAllowed['q'] = true;
|
||||
IsAllowed['r'] = true;
|
||||
IsAllowed['s'] = true;
|
||||
IsAllowed['t'] = true;
|
||||
IsAllowed['u'] = true;
|
||||
IsAllowed['v'] = true;
|
||||
IsAllowed['w'] = true;
|
||||
IsAllowed['x'] = true;
|
||||
IsAllowed['y'] = true;
|
||||
IsAllowed['z'] = true;
|
||||
|
||||
IsAllowed['0'] = true;
|
||||
IsAllowed['1'] = true;
|
||||
IsAllowed['2'] = true;
|
||||
IsAllowed['3'] = true;
|
||||
IsAllowed['4'] = true;
|
||||
IsAllowed['5'] = true;
|
||||
IsAllowed['6'] = true;
|
||||
IsAllowed['7'] = true;
|
||||
IsAllowed['8'] = true;
|
||||
IsAllowed['9'] = true;
|
||||
|
||||
IsAllowed['-'] = true;
|
||||
IsAllowed['_'] = true;
|
||||
IsAllowed['.'] = true;
|
||||
IsAllowed['~'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
internal static class HttpCharacters
|
||||
{
|
||||
private static readonly int _tableSize = 128;
|
||||
private static readonly bool[] _alphaNumeric = InitializeAlphaNumeric();
|
||||
private static readonly bool[] _authority = InitializeAuthority();
|
||||
private static readonly bool[] _token = InitializeToken();
|
||||
private static readonly bool[] _host = InitializeHost();
|
||||
private static readonly bool[] _fieldValue = InitializeFieldValue();
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
// Access _alphaNumeric to initialize static fields
|
||||
var initialize = _alphaNumeric;
|
||||
}
|
||||
|
||||
private static bool[] InitializeAlphaNumeric()
|
||||
{
|
||||
// ALPHA and DIGIT https://tools.ietf.org/html/rfc5234#appendix-B.1
|
||||
var alphaNumeric = new bool[_tableSize];
|
||||
for (var c = '0'; c <= '9'; c++)
|
||||
{
|
||||
alphaNumeric[c] = true;
|
||||
}
|
||||
for (var c = 'A'; c <= 'Z'; c++)
|
||||
{
|
||||
alphaNumeric[c] = true;
|
||||
}
|
||||
for (var c = 'a'; c <= 'z'; c++)
|
||||
{
|
||||
alphaNumeric[c] = true;
|
||||
}
|
||||
return alphaNumeric;
|
||||
}
|
||||
|
||||
private static bool[] InitializeAuthority()
|
||||
{
|
||||
// Authority https://tools.ietf.org/html/rfc3986#section-3.2
|
||||
// Examples:
|
||||
// microsoft.com
|
||||
// hostname:8080
|
||||
// [::]:8080
|
||||
// [fe80::]
|
||||
// 127.0.0.1
|
||||
// user@host.com
|
||||
// user:password@host.com
|
||||
var authority = new bool[_tableSize];
|
||||
Array.Copy(_alphaNumeric, authority, _tableSize);
|
||||
authority[':'] = true;
|
||||
authority['.'] = true;
|
||||
authority['['] = true;
|
||||
authority[']'] = true;
|
||||
authority['@'] = true;
|
||||
return authority;
|
||||
}
|
||||
|
||||
private static bool[] InitializeToken()
|
||||
{
|
||||
// tchar https://tools.ietf.org/html/rfc7230#appendix-B
|
||||
var token = new bool[_tableSize];
|
||||
Array.Copy(_alphaNumeric, token, _tableSize);
|
||||
token['!'] = true;
|
||||
token['#'] = true;
|
||||
token['$'] = true;
|
||||
token['%'] = true;
|
||||
token['&'] = true;
|
||||
token['\''] = true;
|
||||
token['*'] = true;
|
||||
token['+'] = true;
|
||||
token['-'] = true;
|
||||
token['.'] = true;
|
||||
token['^'] = true;
|
||||
token['_'] = true;
|
||||
token['`'] = true;
|
||||
token['|'] = true;
|
||||
token['~'] = true;
|
||||
return token;
|
||||
}
|
||||
|
||||
private static bool[] InitializeHost()
|
||||
{
|
||||
// Matches Http.Sys
|
||||
// Matches RFC 3986 except "*" / "+" / "," / ";" / "=" and "%" HEXDIG HEXDIG which are not allowed by Http.Sys
|
||||
var host = new bool[_tableSize];
|
||||
Array.Copy(_alphaNumeric, host, _tableSize);
|
||||
host['!'] = true;
|
||||
host['$'] = true;
|
||||
host['&'] = true;
|
||||
host['\''] = true;
|
||||
host['('] = true;
|
||||
host[')'] = true;
|
||||
host['-'] = true;
|
||||
host['.'] = true;
|
||||
host['_'] = true;
|
||||
host['~'] = true;
|
||||
return host;
|
||||
}
|
||||
|
||||
private static bool[] InitializeFieldValue()
|
||||
{
|
||||
// field-value https://tools.ietf.org/html/rfc7230#section-3.2
|
||||
var fieldValue = new bool[_tableSize];
|
||||
for (var c = 0x20; c <= 0x7e; c++) // VCHAR and SP
|
||||
{
|
||||
fieldValue[c] = true;
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ContainsInvalidAuthorityChar(Span<byte> s)
|
||||
{
|
||||
var authority = _authority;
|
||||
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
if (c >= (uint)authority.Length || !authority[c])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int IndexOfInvalidHostChar(string s)
|
||||
{
|
||||
var host = _host;
|
||||
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
if (c >= (uint)host.Length || !host[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int IndexOfInvalidTokenChar(string s)
|
||||
{
|
||||
var token = _token;
|
||||
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
if (c >= (uint)token.Length || !token[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe static int IndexOfInvalidTokenChar(byte* s, int length)
|
||||
{
|
||||
var token = _token;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
if (c >= (uint)token.Length || !token[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int IndexOfInvalidFieldValueChar(string s)
|
||||
{
|
||||
var fieldValue = _fieldValue;
|
||||
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
var c = s[i];
|
||||
if (c >= (uint)fieldValue.Length || !fieldValue[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,6 @@ 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,8 +13,6 @@ 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";
|
||||
|
|
@ -31,35 +29,6 @@ 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)
|
||||
{
|
||||
|
|
@ -448,16 +417,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||
}
|
||||
|
||||
// Enregister array
|
||||
var hostCharValidity = HostCharValidity;
|
||||
for (var i = 0; i < hostText.Length; i++)
|
||||
var invalid = HttpCharacters.IndexOfInvalidHostChar(hostText);
|
||||
if (invalid >= 0)
|
||||
{
|
||||
if (!hostCharValidity[hostText[i]])
|
||||
{
|
||||
// Tail call
|
||||
ValidateHostPort(hostText, i);
|
||||
return;
|
||||
}
|
||||
// Tail call
|
||||
ValidateHostPort(hostText, invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public class UriUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if character is valid in the 'authority' section of a URI.
|
||||
/// <see href="https://tools.ietf.org/html/rfc3986#section-3.2"/>
|
||||
/// </summary>
|
||||
/// <param name="ch">The character</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidAuthorityCharacter(byte ch)
|
||||
{
|
||||
// Examples:
|
||||
// microsoft.com
|
||||
// hostname:8080
|
||||
// [::]:8080
|
||||
// [fe80::]
|
||||
// 127.0.0.1
|
||||
// user@host.com
|
||||
// user:password@host.com
|
||||
return
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= 'a' && ch <= 'z') ||
|
||||
ch == ':' ||
|
||||
ch == '.' ||
|
||||
ch == '[' ||
|
||||
ch == ']' ||
|
||||
ch == '@';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
Features = new FeatureCollection();
|
||||
_serverAddresses = new ServerAddressesFeature();
|
||||
Features.Set(_serverAddresses);
|
||||
|
||||
HttpCharacters.Initialize();
|
||||
}
|
||||
|
||||
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory)
|
||||
|
|
|
|||
|
|
@ -1904,6 +1904,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatHttp2ErrorInvalidPreface()
|
||||
=> GetString("Http2ErrorInvalidPreface");
|
||||
|
||||
/// <summary>
|
||||
/// Header name cannot be a null or empty string.
|
||||
/// </summary>
|
||||
internal static string InvalidEmptyHeaderName
|
||||
{
|
||||
get => GetString("InvalidEmptyHeaderName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Header name cannot be a null or empty string.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidEmptyHeaderName()
|
||||
=> GetString("InvalidEmptyHeaderName");
|
||||
|
||||
/// <summary>
|
||||
/// The connection or stream was aborted because a write operation was aborted with a CancellationToken.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[InlineData("Server", "Dašta")]
|
||||
[InlineData("Unknownš-Header", "Data")]
|
||||
[InlineData("Seršver", "Data")]
|
||||
[InlineData("Server\"", "Data")]
|
||||
[InlineData("Server(", "Data")]
|
||||
[InlineData("Server)", "Data")]
|
||||
[InlineData("Server,", "Data")]
|
||||
[InlineData("Server/", "Data")]
|
||||
[InlineData("Server:", "Data")]
|
||||
[InlineData("Server;", "Data")]
|
||||
[InlineData("Server<", "Data")]
|
||||
[InlineData("Server=", "Data")]
|
||||
[InlineData("Server>", "Data")]
|
||||
[InlineData("Server?", "Data")]
|
||||
[InlineData("Server@", "Data")]
|
||||
[InlineData("Server[", "Data")]
|
||||
[InlineData("Server\\", "Data")]
|
||||
[InlineData("Server]", "Data")]
|
||||
[InlineData("Server{", "Data")]
|
||||
[InlineData("Server}", "Data")]
|
||||
[InlineData("", "Data")]
|
||||
[InlineData(null, "Data")]
|
||||
public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string value)
|
||||
{
|
||||
var responseHeaders = new HttpResponseHeaders();
|
||||
|
|
|
|||
|
|
@ -428,6 +428,9 @@ namespace Microsoft.AspNetCore.Testing
|
|||
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r\r", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
|
||||
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r ", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
|
||||
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r \n", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
|
||||
|
||||
// Empty header name
|
||||
new[] { ": value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@": value\x0D\x0A") },
|
||||
};
|
||||
|
||||
public static TheoryData<string, string> HostHeaderData
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
{{
|
||||
{4}
|
||||
FillKnownMethodsGaps();
|
||||
InitializeHostCharValidity();
|
||||
{5}
|
||||
}}
|
||||
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected override void SetValueFast(string key, in StringValues value)
|
||||
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(value);" : "")}
|
||||
ValidateHeaderValueCharacters(value);" : "")}
|
||||
switch (key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
|
|
@ -425,7 +425,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected override bool AddValueFast(string key, in StringValues value)
|
||||
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(value);" : "")}
|
||||
ValidateHeaderValueCharacters(value);" : "")}
|
||||
switch (key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
|
|
@ -451,7 +451,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
break;")}
|
||||
}}
|
||||
{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(key);" : "")}
|
||||
ValidateHeaderNameCharacters(key);" : "")}
|
||||
Unknown.Add(key, value);
|
||||
// Return true, above will throw and exit for false
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue