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">
|
<data name="Http2ErrorInvalidPreface" xml:space="preserve">
|
||||||
<value>Invalid HTTP/2 connection preface.</value>
|
<value>Invalid HTTP/2 connection preface.</value>
|
||||||
</data>
|
</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">
|
<data name="ConnectionOrStreamAbortedByCancellationToken" xml:space="preserve">
|
||||||
<value>The connection or stream was aborted because a write operation was aborted with a CancellationToken.</value>
|
<value>The connection or stream was aborted because a write operation was aborted with a CancellationToken.</value>
|
||||||
</data>
|
</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
|
// 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.
|
// 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];
|
ThrowRequestTargetRejected(target);
|
||||||
if (!UriUtilities.IsValidAuthorityCharacter(ch))
|
|
||||||
{
|
|
||||||
ThrowRequestTargetRejected(target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The authority-form of request-target is only used for CONNECT
|
// 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)
|
protected override void SetValueFast(string key, in StringValues value)
|
||||||
{
|
{
|
||||||
ValidateHeaderCharacters(value);
|
ValidateHeaderValueCharacters(value);
|
||||||
switch (key.Length)
|
switch (key.Length)
|
||||||
{
|
{
|
||||||
case 13:
|
case 13:
|
||||||
|
|
@ -6167,7 +6167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected override bool AddValueFast(string key, in StringValues value)
|
protected override bool AddValueFast(string key, in StringValues value)
|
||||||
{
|
{
|
||||||
ValidateHeaderCharacters(value);
|
ValidateHeaderValueCharacters(value);
|
||||||
switch (key.Length)
|
switch (key.Length)
|
||||||
{
|
{
|
||||||
case 13:
|
case 13:
|
||||||
|
|
@ -6611,7 +6611,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateHeaderCharacters(key);
|
ValidateHeaderNameCharacters(key);
|
||||||
Unknown.Add(key, value);
|
Unknown.Add(key, value);
|
||||||
// Return true, above will throw and exit for false
|
// Return true, above will throw and exit for false
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
@ -45,6 +46,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
ThrowHeadersReadOnlyException();
|
ThrowHeadersReadOnlyException();
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
ThrowInvalidEmtpyHeaderName();
|
||||||
|
}
|
||||||
if (value.Count == 0)
|
if (value.Count == 0)
|
||||||
{
|
{
|
||||||
RemoveFast(key);
|
RemoveFast(key);
|
||||||
|
|
@ -170,6 +175,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
ThrowHeadersReadOnlyException();
|
ThrowHeadersReadOnlyException();
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
ThrowInvalidEmtpyHeaderName();
|
||||||
|
}
|
||||||
|
|
||||||
if (value.Count > 0 && !AddValueFast(key, value))
|
if (value.Count > 0 && !AddValueFast(key, value))
|
||||||
{
|
{
|
||||||
|
|
@ -241,30 +250,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return TryGetValueFast(key, out value);
|
return TryGetValueFast(key, out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ValidateHeaderCharacters(in StringValues headerValues)
|
public static void ValidateHeaderValueCharacters(in StringValues headerValues)
|
||||||
{
|
{
|
||||||
var count = headerValues.Count;
|
var count = headerValues.Count;
|
||||||
for (var i = 0; i < count; i++)
|
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)
|
if (headerCharacters != null)
|
||||||
{
|
{
|
||||||
foreach (var ch in headerCharacters)
|
var invalid = HttpCharacters.IndexOfInvalidFieldValueChar(headerCharacters);
|
||||||
|
if (invalid >= 0)
|
||||||
{
|
{
|
||||||
if (ch < 0x20 || ch > 0x7E)
|
ThrowInvalidHeaderCharacter(headerCharacters[invalid]);
|
||||||
{
|
|
||||||
ThrowInvalidHeaderCharacter(ch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public static unsafe ConnectionOptions ParseConnection(in StringValues connection)
|
||||||
{
|
{
|
||||||
var connectionOptions = ConnectionOptions.None;
|
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)));
|
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 valueEnd = length - 3;
|
||||||
var nameEnd = FindEndOfName(headerLine, length);
|
var nameEnd = FindEndOfName(headerLine, length);
|
||||||
|
|
||||||
|
// Header name is empty
|
||||||
|
if (nameEnd == 0)
|
||||||
|
{
|
||||||
|
RejectRequestHeader(headerLine, length);
|
||||||
|
}
|
||||||
|
|
||||||
if (headerLine[valueEnd + 2] != ByteLF)
|
if (headerLine[valueEnd + 2] != ByteLF)
|
||||||
{
|
{
|
||||||
RejectRequestHeader(headerLine, length);
|
RejectRequestHeader(headerLine, length);
|
||||||
|
|
@ -437,55 +443,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
|
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
|
||||||
{
|
{
|
||||||
methodLength = 0;
|
var invalidIndex = HttpCharacters.IndexOfInvalidTokenChar(data, length);
|
||||||
for (var i = 0; i < length; i++)
|
|
||||||
|
if (invalidIndex <= 0 || data[invalidIndex] != ByteSpace)
|
||||||
{
|
{
|
||||||
var ch = data[i];
|
RejectRequestLine(data, length);
|
||||||
|
|
||||||
if (ch == ByteSpace)
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
RejectRequestLine(data, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
methodLength = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (!IsValidTokenChar((char)ch))
|
|
||||||
{
|
|
||||||
RejectRequestLine(data, length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
methodLength = invalidIndex;
|
||||||
return new Span<byte>(data, methodLength);
|
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]
|
[StackTraceHidden]
|
||||||
private unsafe void RejectRequestLine(byte* requestLine, int length)
|
private unsafe void RejectRequestLine(byte* requestLine, int length)
|
||||||
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
|
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private void SetValueUnknown(string key, in StringValues value)
|
private void SetValueUnknown(string key, in StringValues value)
|
||||||
{
|
{
|
||||||
ValidateHeaderCharacters(key);
|
ValidateHeaderNameCharacters(key);
|
||||||
Unknown[key] = value;
|
Unknown[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Connections.Abstractions
|
||||||
{
|
{
|
||||||
internal class UrlDecoder
|
internal class UrlDecoder
|
||||||
{
|
{
|
||||||
static bool[] IsAllowed = new bool[0x7F + 1];
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unescape a URL path
|
/// Unescape a URL path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -334,78 +332,5 @@ namespace Microsoft.AspNetCore.Connections.Abstractions
|
||||||
return false;
|
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, _httpConnectMethodLong, HttpMethod.Connect, 7);
|
||||||
SetKnownMethod(_mask8Chars, _httpOptionsMethodLong, HttpMethod.Options, 7);
|
SetKnownMethod(_mask8Chars, _httpOptionsMethodLong, HttpMethod.Options, 7);
|
||||||
FillKnownMethodsGaps();
|
FillKnownMethodsGaps();
|
||||||
InitializeHostCharValidity();
|
|
||||||
_methodNames[(byte)HttpMethod.Connect] = HttpMethods.Connect;
|
_methodNames[(byte)HttpMethod.Connect] = HttpMethods.Connect;
|
||||||
_methodNames[(byte)HttpMethod.Delete] = HttpMethods.Delete;
|
_methodNames[(byte)HttpMethod.Delete] = HttpMethods.Delete;
|
||||||
_methodNames[(byte)HttpMethod.Get] = HttpMethods.Get;
|
_methodNames[(byte)HttpMethod.Get] = HttpMethods.Get;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
public static partial class HttpUtilities
|
public static partial class HttpUtilities
|
||||||
{
|
{
|
||||||
private static readonly bool[] HostCharValidity = new bool[127];
|
|
||||||
|
|
||||||
public const string Http10Version = "HTTP/1.0";
|
public const string Http10Version = "HTTP/1.0";
|
||||||
public const string Http11Version = "HTTP/1.1";
|
public const string Http11Version = "HTTP/1.1";
|
||||||
public const string Http2Version = "HTTP/2";
|
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 _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
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void SetKnownMethod(ulong mask, ulong knownMethodUlong, HttpMethod knownMethod, int length)
|
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);
|
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enregister array
|
var invalid = HttpCharacters.IndexOfInvalidHostChar(hostText);
|
||||||
var hostCharValidity = HostCharValidity;
|
if (invalid >= 0)
|
||||||
for (var i = 0; i < hostText.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (!hostCharValidity[hostText[i]])
|
// Tail call
|
||||||
{
|
ValidateHostPort(hostText, invalid);
|
||||||
// Tail call
|
|
||||||
ValidateHostPort(hostText, i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
Features = new FeatureCollection();
|
||||||
_serverAddresses = new ServerAddressesFeature();
|
_serverAddresses = new ServerAddressesFeature();
|
||||||
Features.Set(_serverAddresses);
|
Features.Set(_serverAddresses);
|
||||||
|
|
||||||
|
HttpCharacters.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory)
|
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory)
|
||||||
|
|
|
||||||
|
|
@ -1904,6 +1904,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
internal static string FormatHttp2ErrorInvalidPreface()
|
internal static string FormatHttp2ErrorInvalidPreface()
|
||||||
=> GetString("Http2ErrorInvalidPreface");
|
=> 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>
|
/// <summary>
|
||||||
/// The connection or stream was aborted because a write operation was aborted with a CancellationToken.
|
/// The connection or stream was aborted because a write operation was aborted with a CancellationToken.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData("Server", "Dašta")]
|
[InlineData("Server", "Dašta")]
|
||||||
[InlineData("Unknownš-Header", "Data")]
|
[InlineData("Unknownš-Header", "Data")]
|
||||||
[InlineData("Seršver", "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)
|
public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string value)
|
||||||
{
|
{
|
||||||
var responseHeaders = new HttpResponseHeaders();
|
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\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 ", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
|
||||||
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r \n", 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
|
public static TheoryData<string, string> HostHeaderData
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{{
|
{{
|
||||||
{4}
|
{4}
|
||||||
FillKnownMethodsGaps();
|
FillKnownMethodsGaps();
|
||||||
InitializeHostCharValidity();
|
|
||||||
{5}
|
{5}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected override void SetValueFast(string key, in StringValues value)
|
protected override void SetValueFast(string key, in StringValues value)
|
||||||
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||||
ValidateHeaderCharacters(value);" : "")}
|
ValidateHeaderValueCharacters(value);" : "")}
|
||||||
switch (key.Length)
|
switch (key.Length)
|
||||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||||
case {byLength.Key}:
|
case {byLength.Key}:
|
||||||
|
|
@ -425,7 +425,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected override bool AddValueFast(string key, in StringValues value)
|
protected override bool AddValueFast(string key, in StringValues value)
|
||||||
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
{{{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||||
ValidateHeaderCharacters(value);" : "")}
|
ValidateHeaderValueCharacters(value);" : "")}
|
||||||
switch (key.Length)
|
switch (key.Length)
|
||||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||||
case {byLength.Key}:
|
case {byLength.Key}:
|
||||||
|
|
@ -451,7 +451,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
break;")}
|
break;")}
|
||||||
}}
|
}}
|
||||||
{(loop.ClassName == "HttpResponseHeaders" ? @"
|
{(loop.ClassName == "HttpResponseHeaders" ? @"
|
||||||
ValidateHeaderCharacters(key);" : "")}
|
ValidateHeaderNameCharacters(key);" : "")}
|
||||||
Unknown.Add(key, value);
|
Unknown.Add(key, value);
|
||||||
// Return true, above will throw and exit for false
|
// Return true, above will throw and exit for false
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue