Consolidate HTTP charset validation logic

This commit is contained in:
John Luo 2018-07-09 16:38:44 -07:00
parent 864cfeb2aa
commit 6551eae321
17 changed files with 296 additions and 216 deletions

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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);
}
} }
} }

View File

@ -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);

View File

@ -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;
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;
}
} }
} }
} }

View File

@ -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 == '@';
}
}
}

View File

@ -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)

View File

@ -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>

View File

@ -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();

View File

@ -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

View File

@ -84,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{{ {{
{4} {4}
FillKnownMethodsGaps(); FillKnownMethodsGaps();
InitializeHostCharValidity();
{5} {5}
}} }}

View File

@ -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;