Add custom request header decoder API to Kestrel (#23233)

This commit is contained in:
Stephen Halter 2020-06-26 19:49:35 -07:00 committed by GitHub
parent bfbb8b0159
commit b446ab7c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 336 additions and 100 deletions

View File

@ -137,6 +137,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
public bool DisableStringReuse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public bool EnableAltSvc { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits Limits { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
public System.Func<string, System.Text.Encoding> RequestHeaderEncodingSelector { get { throw null; } set { } }
public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Configure() { throw null; }
public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Configure(Microsoft.Extensions.Configuration.IConfiguration config) { throw null; }
public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader Configure(Microsoft.Extensions.Configuration.IConfiguration config, bool reloadOnChange) { throw null; }

View File

@ -18,14 +18,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private const string EndpointDefaultsKey = "EndpointDefaults";
private const string EndpointsKey = "Endpoints";
private const string UrlKey = "Url";
private const string Latin1RequestHeadersKey = "Latin1RequestHeaders";
private readonly IConfiguration _configuration;
private IDictionary<string, CertificateConfig> _certificates;
private EndpointDefaults _endpointDefaults;
private IEnumerable<EndpointConfig> _endpoints;
private bool? _latin1RequestHeaders;
public ConfigurationReader(IConfiguration configuration)
{
@ -35,7 +33,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public IDictionary<string, CertificateConfig> Certificates => _certificates ??= ReadCertificates();
public EndpointDefaults EndpointDefaults => _endpointDefaults ??= ReadEndpointDefaults();
public IEnumerable<EndpointConfig> Endpoints => _endpoints ??= ReadEndpoints();
public bool Latin1RequestHeaders => _latin1RequestHeaders ??= _configuration.GetValue<bool>(Latin1RequestHeadersKey);
private IDictionary<string, CertificateConfig> ReadCertificates()
{

View File

@ -6270,6 +6270,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public unsafe void Append(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
{
ref byte nameStart = ref MemoryMarshal.GetReference(name);
var nameStr = string.Empty;
ref StringValues values = ref Unsafe.AsRef<StringValues>(null);
var flag = 0L;
@ -6281,6 +6282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x20000000000L;
values = ref _headers._TE;
nameStr = HeaderNames.TE;
}
break;
case 3:
@ -6289,11 +6291,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x100000000000L;
values = ref _headers._DNT;
nameStr = HeaderNames.DNT;
}
else if ((firstTerm3 == 0x4956u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)2) & 0xdfu) == 0x41u))
{
flag = 0x100L;
values = ref _headers._Via;
nameStr = HeaderNames.Via;
}
break;
case 4:
@ -6302,16 +6306,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x80000000L;
values = ref _headers._Host;
nameStr = HeaderNames.Host;
}
else if ((firstTerm4 == 0x45544144u))
{
flag = 0x4L;
values = ref _headers._Date;
nameStr = HeaderNames.Date;
}
else if ((firstTerm4 == 0x4d4f5246u))
{
flag = 0x40000000L;
values = ref _headers._From;
nameStr = HeaderNames.From;
}
break;
case 5:
@ -6319,16 +6326,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x200000L;
values = ref _headers._Path;
nameStr = HeaderNames.Path;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x4f4c4c41u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x57u))
{
flag = 0x400L;
values = ref _headers._Allow;
nameStr = HeaderNames.Allow;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x474e4152u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)4) & 0xdfu) == 0x45u))
{
flag = 0x10000000000L;
values = ref _headers._Range;
nameStr = HeaderNames.Range;
}
break;
case 6:
@ -6337,26 +6347,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x800000L;
values = ref _headers._Accept;
nameStr = HeaderNames.Accept;
}
else if ((firstTerm6 == 0x4b4f4f43u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4549u))
{
flag = 0x10000000L;
values = ref _headers._Cookie;
nameStr = HeaderNames.Cookie;
}
else if ((firstTerm6 == 0x45505845u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x5443u))
{
flag = 0x20000000L;
values = ref _headers._Expect;
nameStr = HeaderNames.Expect;
}
else if ((firstTerm6 == 0x4749524fu) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u))
{
flag = 0x4000000000000L;
values = ref _headers._Origin;
nameStr = HeaderNames.Origin;
}
else if ((firstTerm6 == 0x47415250u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x414du))
{
flag = 0x10L;
values = ref _headers._Pragma;
nameStr = HeaderNames.Pragma;
}
break;
case 7:
@ -6364,36 +6379,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x100000L;
values = ref _headers._Method;
nameStr = HeaderNames.Method;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfffu) == 0x4843533au) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4d45u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x45u))
{
flag = 0x400000L;
values = ref _headers._Scheme;
nameStr = HeaderNames.Scheme;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x49505845u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x53u))
{
flag = 0x20000L;
values = ref _headers._Expires;
nameStr = HeaderNames.Expires;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x45464552u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4552u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u))
{
flag = 0x8000000000L;
values = ref _headers._Referer;
nameStr = HeaderNames.Referer;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x49415254u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x454cu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x52u))
{
flag = 0x20L;
values = ref _headers._Trailer;
nameStr = HeaderNames.Trailer;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x52475055u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4441u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x45u))
{
flag = 0x80L;
values = ref _headers._Upgrade;
nameStr = HeaderNames.Upgrade;
}
else if (((Unsafe.ReadUnaligned<uint>(ref nameStart) & 0xdfdfdfdfu) == 0x4e524157u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(ushort)))) & 0xdfdfu) == 0x4e49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)6) & 0xdfu) == 0x47u))
{
flag = 0x200L;
values = ref _headers._Warning;
nameStr = HeaderNames.Warning;
}
break;
case 8:
@ -6402,11 +6424,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x100000000L;
values = ref _headers._IfMatch;
nameStr = HeaderNames.IfMatch;
}
else if ((firstTerm8 == 0x45474e41522d4649uL))
{
flag = 0x800000000L;
values = ref _headers._IfRange;
nameStr = HeaderNames.IfRange;
}
break;
case 9:
@ -6414,6 +6438,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x40000000000L;
values = ref _headers._Translate;
nameStr = HeaderNames.Translate;
}
break;
case 10:
@ -6421,31 +6446,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x80000L;
values = ref _headers._Authority;
nameStr = HeaderNames.Authority;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x495443454e4e4f43uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e4fu))
{
flag = 0x2L;
values = ref _headers._Connection;
nameStr = HeaderNames.Connection;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x4547412d52455355uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x544eu))
{
flag = 0x80000000000L;
values = ref _headers._UserAgent;
nameStr = HeaderNames.UserAgent;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x494c412d5045454buL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4556u))
{
flag = 0x8L;
values = ref _headers._KeepAlive;
nameStr = HeaderNames.KeepAlive;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d54534555514552uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4449u))
{
flag = 0x400000000000L;
values = ref _headers._RequestId;
nameStr = HeaderNames.RequestId;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x4154534543415254uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4554u))
{
flag = 0x2000000000000L;
values = ref _headers._TraceState;
nameStr = HeaderNames.TraceState;
}
break;
case 11:
@ -6453,11 +6484,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x8000L;
values = ref _headers._ContentMD5;
nameStr = HeaderNames.ContentMD5;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x5241504543415254uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e45u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)10) & 0xdfu) == 0x54u))
{
flag = 0x1000000000000L;
values = ref _headers._TraceParent;
nameStr = HeaderNames.TraceParent;
}
break;
case 12:
@ -6465,11 +6498,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x800L;
values = ref _headers._ContentType;
nameStr = HeaderNames.ContentType;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfffdfdfdfuL) == 0x57524f462d58414duL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x53445241u))
{
flag = 0x2000000000L;
values = ref _headers._MaxForwards;
nameStr = HeaderNames.MaxForwards;
}
break;
case 13:
@ -6477,26 +6512,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x8000000L;
values = ref _headers._Authorization;
nameStr = HeaderNames.Authorization;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x4f432d4548434143uL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4f52544eu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x4cu))
{
flag = 0x1L;
values = ref _headers._CacheControl;
nameStr = HeaderNames.CacheControl;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d544e45544e4f43uL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x474e4152u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x45u))
{
flag = 0x10000L;
values = ref _headers._ContentRange;
nameStr = HeaderNames.ContentRange;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xffdfdfdfdfffdfdfuL) == 0x2d454e4f4e2d4649uL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x4354414du) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x48u))
{
flag = 0x400000000L;
values = ref _headers._IfNoneMatch;
nameStr = HeaderNames.IfNoneMatch;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x444f4d2d5453414cuL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x45494649u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)12) & 0xdfu) == 0x44u))
{
flag = 0x40000L;
values = ref _headers._LastModified;
nameStr = HeaderNames.LastModified;
}
break;
case 14:
@ -6504,10 +6544,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x1000000L;
values = ref _headers._AcceptCharset;
nameStr = HeaderNames.AcceptCharset;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d544e45544e4f43uL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x474e454cu) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x4854u))
{
AppendContentLength(value);
if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector))
{
AppendContentLength(value);
}
else
{
AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength));
}
return;
}
break;
@ -6517,11 +6565,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x2000000L;
values = ref _headers._AcceptEncoding;
nameStr = HeaderNames.AcceptEncoding;
}
else if ((firstTerm15 == 0x4c2d545045434341uL) && ((Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x55474e41u) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(6 * sizeof(ushort)))) & 0xdfdfu) == 0x4741u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)14) & 0xdfu) == 0x45u))
{
flag = 0x4000000L;
values = ref _headers._AcceptLanguage;
nameStr = HeaderNames.AcceptLanguage;
}
break;
case 16:
@ -6532,16 +6582,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x1000L;
values = ref _headers._ContentEncoding;
nameStr = HeaderNames.ContentEncoding;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x45474155474e414cuL))
{
flag = 0x2000L;
values = ref _headers._ContentLanguage;
nameStr = HeaderNames.ContentLanguage;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x4e4f495441434f4cuL))
{
flag = 0x4000L;
values = ref _headers._ContentLocation;
nameStr = HeaderNames.ContentLocation;
}
}
break;
@ -6550,11 +6603,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x200000000L;
values = ref _headers._IfModifiedSince;
nameStr = HeaderNames.IfModifiedSince;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x524546534e415254uL) && ((Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfffuL) == 0x4e49444f434e452duL) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)16) & 0xdfu) == 0x47u))
{
flag = 0x40L;
values = ref _headers._TransferEncoding;
nameStr = HeaderNames.TransferEncoding;
}
break;
case 19:
@ -6562,16 +6617,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x800000000000L;
values = ref _headers._CorrelationContext;
nameStr = HeaderNames.CorrelationContext;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfdfdfdfffdfdfuL) == 0x444f4d4e552d4649uL) && ((Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfffdfdfdfdfdfuL) == 0x49532d4445494649uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(8 * sizeof(ushort)))) & 0xdfdfu) == 0x434eu) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)18) & 0xdfu) == 0x45u))
{
flag = 0x1000000000L;
values = ref _headers._IfUnmodifiedSince;
nameStr = HeaderNames.IfUnmodifiedSince;
}
else if (((Unsafe.ReadUnaligned<ulong>(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x55412d59584f5250uL) && ((Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x54415a49524f4854uL) && ((Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(8 * sizeof(ushort)))) & 0xdfdfu) == 0x4f49u) && ((Unsafe.AddByteOffset(ref nameStart, (IntPtr)18) & 0xdfu) == 0x4eu))
{
flag = 0x4000000000L;
values = ref _headers._ProxyAuthorization;
nameStr = HeaderNames.ProxyAuthorization;
}
break;
case 25:
@ -6579,6 +6637,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x200000000000L;
values = ref _headers._UpgradeInsecureRequests;
nameStr = HeaderNames.UpgradeInsecureRequests;
}
break;
case 29:
@ -6586,6 +6645,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x8000000000000L;
values = ref _headers._AccessControlRequestMethod;
nameStr = HeaderNames.AccessControlRequestMethod;
}
break;
case 30:
@ -6593,6 +6653,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
flag = 0x10000000000000L;
values = ref _headers._AccessControlRequestHeaders;
nameStr = HeaderNames.AccessControlRequestHeaders;
}
break;
}
@ -6622,7 +6683,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
// We didn't have a previous matching header value, or have already added a header, so get the string for this value.
var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1);
var valueStr = value.GetRequestHeaderString(nameStr, EncodingSelector);
if ((_bits & flag) == 0)
{
// We didn't already have a header set, so add a new one.
@ -6640,8 +6701,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// The header was not one of the "known" headers.
// Convert value to string first, because passing two spans causes 8 bytes stack zeroing in
// this method with rep stosd, which is slower than necessary.
var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1);
AppendUnknownHeaders(name, valueStr);
nameStr = name.GetHeaderName();
var valueStr = value.GetRequestHeaderString(nameStr, EncodingSelector);
AppendUnknownHeaders(nameStr, valueStr);
}
}

View File

@ -369,7 +369,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
ConnectionIdFeature = ConnectionId;
HttpRequestHeaders.Reset();
HttpRequestHeaders.UseLatin1 = ServerOptions.Latin1RequestHeaders;
HttpRequestHeaders.EncodingSelector = ServerOptions.RequestHeaderEncodingSelector;
HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse;
HttpResponseHeaders.Reset();
RequestHeaders = HttpRequestHeaders;
@ -532,7 +532,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
string key = name.GetHeaderName();
var valueStr = value.GetRequestHeaderStringNonNullCharacters(ServerOptions.Latin1RequestHeaders);
var valueStr = value.GetRequestHeaderString(key, HttpRequestHeaders.EncodingSelector);
RequestTrailers.Append(key, valueStr);
}

View File

@ -5,7 +5,9 @@ using System;
using System.Buffers.Text;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
@ -17,12 +19,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private long _previousBits = 0;
public bool ReuseHeaderValues { get; set; }
public bool UseLatin1 { get; set; }
public Func<string, Encoding> EncodingSelector { get; set; }
public HttpRequestHeaders(bool reuseHeaderValues = true, bool useLatin1 = false)
public HttpRequestHeaders(bool reuseHeaderValues = true, Func<string, Encoding> encodingSelector = null)
{
ReuseHeaderValues = reuseHeaderValues;
UseLatin1 = useLatin1;
EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultRequestHeaderEncodingSelector;
}
public void OnHeadersComplete()
@ -87,7 +89,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
parsed < 0 ||
consumed != value.Length)
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderStringNonNullCharacters(UseLatin1));
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderString(HeaderNames.ContentLength, EncodingSelector));
}
_contentLength = parsed;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendContentLengthCustomEncoding(ReadOnlySpan<byte> value, Encoding customEncoding)
{
if (_contentLength.HasValue)
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths);
}
// long.MaxValue = 9223372036854775807 (19 chars)
Span<char> decodedChars = stackalloc char[20];
var numChars = customEncoding.GetChars(value, decodedChars);
long parsed = -1;
if (numChars > 19 ||
!long.TryParse(decodedChars.Slice(0, numChars), NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ||
parsed < 0)
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value.GetRequestHeaderString(HeaderNames.ContentLength, EncodingSelector));
}
_contentLength = parsed;
@ -108,11 +133,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void AppendUnknownHeaders(ReadOnlySpan<byte> name, string valueString)
private unsafe void AppendUnknownHeaders(string name, string valueString)
{
string key = name.GetHeaderName();
Unknown.TryGetValue(key, out var existing);
Unknown[key] = AppendValue(existing, valueString);
Unknown.TryGetValue(name, out var existing);
Unknown[name] = AppendValue(existing, valueString);
}
public Enumerator GetEnumerator()

View File

@ -27,7 +27,7 @@ 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
private static readonly UTF8EncodingSealed HeaderValueEncoding = new UTF8EncodingSealed();
private static readonly UTF8EncodingSealed DefaultRequestHeaderEncoding = new UTF8EncodingSealed();
private static readonly SpanAction<char, IntPtr> _getHeaderName = GetHeaderName;
private static readonly SpanAction<char, IntPtr> _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters;
@ -120,11 +120,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
}
}
public static string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span)
=> GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span);
public static string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span)
=> StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, HeaderValueEncoding);
=> StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, DefaultRequestHeaderEncoding);
private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, IntPtr state)
{
@ -139,8 +136,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
}
}
public static string GetRequestHeaderStringNonNullCharacters(this ReadOnlySpan<byte> span, bool useLatin1) =>
useLatin1 ? span.GetLatin1StringNonNullCharacters() : span.GetAsciiOrUTF8StringNonNullCharacters(HeaderValueEncoding);
public static string GetRequestHeaderString(this ReadOnlySpan<byte> span, string name, Func<string, Encoding> encodingSelector)
{
if (ReferenceEquals(KestrelServerOptions.DefaultRequestHeaderEncodingSelector, encodingSelector))
{
return span.GetAsciiOrUTF8StringNonNullCharacters(DefaultRequestHeaderEncoding);
}
var encoding = encodingSelector(name);
if (encoding is null)
{
return span.GetAsciiOrUTF8StringNonNullCharacters(DefaultRequestHeaderEncoding);
}
if (ReferenceEquals(encoding, Encoding.Latin1))
{
return span.GetLatin1StringNonNullCharacters();
}
try
{
return encoding.GetString(span);
}
catch (DecoderFallbackException ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
public static string GetAsciiStringEscaped(this ReadOnlySpan<byte> span, int maxChars)
{

View File

@ -255,8 +255,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
ConfigurationReader = new ConfigurationReader(Configuration);
Options.Latin1RequestHeaders = ConfigurationReader.Latin1RequestHeaders;
LoadDefaultCert(ConfigurationReader);
foreach (var endpoint in ConfigurationReader.Endpoints)

View File

@ -35,12 +35,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private IDisposable _configChangedRegistration;
public KestrelServer(IOptions<KestrelServerOptions> options, IEnumerable<IConnectionListenerFactory> transportFactories, ILoggerFactory loggerFactory)
public KestrelServer(
IOptions<KestrelServerOptions> options,
IEnumerable<IConnectionListenerFactory> transportFactories,
ILoggerFactory loggerFactory)
: this(transportFactories, null, CreateServiceContext(options, loggerFactory))
{
}
public KestrelServer(IOptions<KestrelServerOptions> options, IEnumerable<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories, ILoggerFactory loggerFactory)
public KestrelServer(
IOptions<KestrelServerOptions> options,
IEnumerable<IConnectionListenerFactory> transportFactories,
IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories,
ILoggerFactory loggerFactory)
: this(transportFactories, multiplexedFactories, CreateServiceContext(options, loggerFactory))
{
}
@ -52,7 +59,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
// For testing
internal KestrelServer(IEnumerable<IConnectionListenerFactory> transportFactories, IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories, ServiceContext serviceContext)
internal KestrelServer(
IEnumerable<IConnectionListenerFactory> transportFactories,
IEnumerable<IMultiplexedConnectionListenerFactory> multiplexedFactories,
ServiceContext serviceContext)
{
if (transportFactories == null)
{

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
@ -22,6 +23,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public class KestrelServerOptions
{
// internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged.
internal static readonly Func<string, Encoding> DefaultRequestHeaderEncodingSelector = _ => null;
private Func<string, Encoding> _requestHeaderEncodingSelector = DefaultRequestHeaderEncodingSelector;
// The following two lists configure the endpoints that Kestrel should listen to. If both lists are empty, the "urls" config setting (e.g. UseUrls) is used.
internal List<ListenOptions> CodeBackedListenOptions { get; } = new List<ListenOptions>();
internal List<ListenOptions> ConfigurationBackedListenOptions { get; } = new List<ListenOptions>();
@ -65,6 +71,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </remarks>
public bool DisableStringReuse { get; set; } = false;
/// <summary>
/// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3
/// </summary>
/// <remarks>
/// Defaults to false.
/// </remarks>
public bool EnableAltSvc { get; set; } = false;
/// <summary>
/// Gets or sets a callback that returns the <see cref="Encoding"/> to decode the value for the specified request header name,
/// or <see langword="null"/> to use the default <see cref="UTF8Encoding"/>.
/// </summary>
public Func<string, Encoding> RequestHeaderEncodingSelector
{
get => _requestHeaderEncodingSelector;
set => _requestHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
/// Typically initialized by UseKestrel()"/>.
@ -78,15 +102,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// <summary>
/// Provides a configuration source where endpoints will be loaded from on server start.
/// The default is null.
/// The default is <see langword="null"/>.
/// </summary>
public KestrelConfigurationLoader ConfigurationLoader { get; set; }
/// <summary>
/// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3
/// </summary>
public bool EnableAltSvc { get; set; } = false;
/// <summary>
/// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs.
/// </summary>
@ -107,11 +126,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
internal bool IsDevCertLoaded { get; set; }
/// <summary>
/// Treat request headers as Latin-1 or ISO/IEC 8859-1 instead of UTF-8.
/// </summary>
internal bool Latin1RequestHeaders { get; set; }
/// <summary>
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
/// the prior action.
@ -159,7 +173,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
if (DefaultCertificate == null && !IsDevCertLoaded)
{
IsDevCertLoaded = true; // Only try once
var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
var logger = ApplicationServices!.GetRequiredService<ILogger<KestrelServer>>();
try
{
DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true)
@ -220,7 +234,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
/// <param name="config">The configuration section for Kestrel.</param>
/// <param name="reloadOnChange">
/// If <see langword="true" />, Kestrel will dynamically update endpoint bindings when configuration changes.
/// If <see langword="true"/>, Kestrel will dynamically update endpoint bindings when configuration changes.
/// This will only reload endpoints defined in the "Endpoints" section of your <paramref name="config"/>. Endpoints defined in code will not be reloaded.
/// </param>
/// <returns>A <see cref="KestrelConfigurationLoader"/> for further endpoint configuration.</returns>

View File

@ -8,6 +8,7 @@ using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit;
using static CodeGenerator.KnownHeaders;
@ -307,11 +308,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var headers = new HttpRequestHeaders();
const string key = "\u00141\u00F3d\017c";
var encoding = Encoding.GetEncoding("iso-8859-1");
#pragma warning disable CS0618 // Type or member is obsolete
var exception = Assert.Throws<BadHttpRequestException>(
#pragma warning restore CS0618 // Type or member is obsolete
() => headers.Append(encoding.GetBytes(key), Encoding.ASCII.GetBytes("value")));
() => headers.Append(Encoding.Latin1.GetBytes(key), Encoding.ASCII.GetBytes("value")));
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
}
@ -473,7 +473,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Throws<InvalidOperationException>(() =>
{
var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan();
var nextSpan = Encoding.GetEncoding("iso-8859-1").GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
var nextSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
Assert.False(nextSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver)));
@ -490,7 +490,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[MemberData(nameof(KnownRequestHeaders))]
public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownHeader header)
{
var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue, useLatin1: true);
var headers = new HttpRequestHeaders(reuseHeaderValues: reuseValue, _ => Encoding.Latin1);
var headerValue = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1
for (var i = 0; i < headerValue.Length; i++)
@ -517,19 +517,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
headerValueUtf16Latin1CrossOver = new string(headerValue.AsSpan().Slice(0, i + 1));
}
headers.Reset();
var headerName = Encoding.ASCII.GetBytes(header.Name).AsSpan();
var latinValueSpan = Encoding.GetEncoding("iso-8859-1").GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
var latinValueSpan = Encoding.Latin1.GetBytes(headerValueUtf16Latin1CrossOver).AsSpan();
Assert.False(latinValueSpan.SequenceEqual(Encoding.ASCII.GetBytes(headerValueUtf16Latin1CrossOver)));
headers.Reset();
headers.Append(headerName, latinValueSpan);
headers.OnHeadersComplete();
var parsedHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString();
var parsedHeaderValue1 = ((IHeaderDictionary)headers)[header.Name].ToString();
Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue);
Assert.NotSame(headerValueUtf16Latin1CrossOver, parsedHeaderValue);
headers.Reset();
headers.Append(headerName, latinValueSpan);
headers.OnHeadersComplete();
var parsedHeaderValue2 = ((IHeaderDictionary)headers)[header.Name].ToString();
Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue1);
Assert.Equal(parsedHeaderValue1, parsedHeaderValue2);
Assert.NotSame(parsedHeaderValue1, parsedHeaderValue2);
}
// Reset back to Ascii
@ -541,7 +546,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[MemberData(nameof(KnownRequestHeaders))]
public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header)
{
var headers = new HttpRequestHeaders(useLatin1: useLatin1);
var headers = new HttpRequestHeaders(encodingSelector: useLatin1 ? _ => Encoding.Latin1 : (Func<string, Encoding>)null);
var valueArray = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1
for (var i = 0; i < valueArray.Length; i++)
@ -569,6 +574,53 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
}
[Fact]
public void CanSpecifyEncodingBasedOnHeaderName()
{
const string headerValue = "Hello \u03a0";
var acceptNameBytes = Encoding.ASCII.GetBytes(HeaderNames.Accept);
var cookieNameBytes = Encoding.ASCII.GetBytes(HeaderNames.Cookie);
var headerValueBytes = Encoding.UTF8.GetBytes(headerValue);
var headers = new HttpRequestHeaders(encodingSelector: headerName =>
{
// For known headers, the HeaderNames value is passed in.
if (ReferenceEquals(headerName, HeaderNames.Accept))
{
return Encoding.GetEncoding("ASCII", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
}
return Encoding.UTF8;
});
Assert.Throws<InvalidOperationException>(() => headers.Append(acceptNameBytes, headerValueBytes));
headers.Append(cookieNameBytes, headerValueBytes);
headers.OnHeadersComplete();
var parsedAcceptHeaderValue = ((IHeaderDictionary)headers)[HeaderNames.Accept].ToString();
var parsedCookieHeaderValue = ((IHeaderDictionary)headers)[HeaderNames.Cookie].ToString();
Assert.Empty(parsedAcceptHeaderValue);
Assert.Equal(headerValue, parsedCookieHeaderValue);
}
[Fact]
public void CanSpecifyEncodingForContentLength()
{
var contentLengthNameBytes = Encoding.ASCII.GetBytes(HeaderNames.ContentLength);
// Always 32 bits per code point, so not a superset of ASCII
var contentLengthValueBytes = Encoding.UTF32.GetBytes("1337");
var headers = new HttpRequestHeaders(encodingSelector: _ => Encoding.UTF32);
headers.Append(contentLengthNameBytes, contentLengthValueBytes);
headers.OnHeadersComplete();
Assert.Equal(1337, headers.ContentLength);
Assert.Throws<InvalidOperationException>(() =>
new HttpRequestHeaders().Append(contentLengthNameBytes, contentLengthValueBytes));
}
[Fact]
public void ValueReuseNeverWhenUnknownHeader()
{

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using Xunit;
@ -60,5 +61,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// https://github.com/dotnet/aspnetcore/issues/21423
options.ListenLocalhost(5000);
}
[Fact]
public void SettingRequestHeaderEncodingSelecterThrowsArgumentNullException()
{
var options = new KestrelServerOptions();
var ex = Assert.Throws<ArgumentNullException>(() => options.RequestHeaderEncodingSelector = null);
Assert.Equal("value", ex.ParamName);
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Numerics;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -17,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[InlineData(new byte[] { 0xef, 0xbf, 0xbd })] // 3 bytes: Replacement character, highest UTF-8 character currently encoded in the UTF-8 code page
private void FullUTF8RangeSupported(byte[] encodedBytes)
{
var s = HttpUtilities.GetRequestHeaderStringNonNullCharacters(encodedBytes.AsSpan(), useLatin1: false);
var s = HttpUtilities.GetRequestHeaderString(encodedBytes.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultRequestHeaderEncodingSelector);
Assert.Equal(1, s.Length);
}
@ -35,7 +36,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray();
Array.Copy(bytes, 0, byteRange, position, bytes.Length);
Assert.Throws<InvalidOperationException>(() => HttpUtilities.GetRequestHeaderStringNonNullCharacters(byteRange.AsSpan(), useLatin1: false));
Assert.Throws<InvalidOperationException>(() =>
HttpUtilities.GetRequestHeaderString(byteRange.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultRequestHeaderEncodingSelector));
}
}
}

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
@ -587,27 +588,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
Assert.True(ran1);
}
[Fact]
public void Latin1RequestHeadersReadFromConfig()
{
var options = CreateServerOptions();
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
Assert.False(options.Latin1RequestHeaders);
options.Configure(config).Load();
Assert.False(options.Latin1RequestHeaders);
options = CreateServerOptions();
config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Latin1RequestHeaders", "true"),
}).Build();
Assert.False(options.Latin1RequestHeaders);
options.Configure(config).Load();
Assert.True(options.Latin1RequestHeaders);
}
[Fact]
public void Reload_IdentifiesEndpointsToStartAndStop()
{

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
@ -10,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
private const int Iterations = 50;
private string _headerName;
private byte[] _asciiBytes;
private byte[] _utf8Bytes;
@ -27,24 +30,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
switch (Type)
{
case BenchmarkTypes.KeepAlive:
_headerName = HeaderNames.Connection;
// keep-alive
_asciiBytes = new byte[] { 0x6b, 0x65, 0x65, 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65 };
// kéép-álivé
_utf8Bytes = new byte[] { 0x6b, 0xc3, 0xa9, 0xc3, 0xa9, 0x70, 0x2d, 0xc3, 0xa1, 0x6c, 0x69, 0x76, 0xc3, 0xa9 };
break;
case BenchmarkTypes.Accept:
_headerName = HeaderNames.Accept;
// text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7
_asciiBytes = new byte[] { 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x39, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x39, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x38, 0x2c, 0x2a, 0x2f, 0x2a, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x37 };
// téxt/pláin,téxt/html;q=0.9,ápplicátion/xhtml+xml;q=0.9,ápplicátion/xml;q=0.8,*/*;q=0.7
_utf8Bytes = new byte[] { 0x74, 0xc3, 0xa9, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0xc3, 0xa1, 0x69, 0x6e, 0x2c, 0x74, 0xc3, 0xa9, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x39, 0x2c, 0xc3, 0xa1, 0x70, 0x70, 0x6c, 0x69, 0x63, 0xc3, 0xa1, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x39, 0x2c, 0xc3, 0xa1, 0x70, 0x70, 0x6c, 0x69, 0x63, 0xc3, 0xa1, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x38, 0x2c, 0x2a, 0x2f, 0x2a, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x37 };
break;
case BenchmarkTypes.UserAgent:
_headerName = HeaderNames.UserAgent;
// Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
_asciiBytes = new byte[] { 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2f, 0x35, 0x2e, 0x30, 0x20, 0x28, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x4e, 0x54, 0x20, 0x31, 0x30, 0x2e, 0x30, 0x3b, 0x20, 0x57, 0x4f, 0x57, 0x36, 0x34, 0x29, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x57, 0x65, 0x62, 0x4b, 0x69, 0x74, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36, 0x20, 0x28, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x29, 0x20, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x35, 0x34, 0x2e, 0x30, 0x2e, 0x32, 0x38, 0x34, 0x30, 0x2e, 0x39, 0x39, 0x20, 0x53, 0x61, 0x66, 0x61, 0x72, 0x69, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36 };
// Mozillá/5.0 (Windows NT 10.0; WOW64) áppléWébKit/537.36 (KHTML, liké Gécko) Chromé/54.0.2840.99 Sáfári/537.36
_utf8Bytes = new byte[] { 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0xc3, 0xa1, 0x2f, 0x35, 0x2e, 0x30, 0x20, 0x28, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x4e, 0x54, 0x20, 0x31, 0x30, 0x2e, 0x30, 0x3b, 0x20, 0x57, 0x4f, 0x57, 0x36, 0x34, 0x29, 0x20, 0xc3, 0xa1, 0x70, 0x70, 0x6c, 0xc3, 0xa9, 0x57, 0xc3, 0xa9, 0x62, 0x4b, 0x69, 0x74, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36, 0x20, 0x28, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0xc3, 0xa9, 0x20, 0x47, 0xc3, 0xa9, 0x63, 0x6b, 0x6f, 0x29, 0x20, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0xc3, 0xa9, 0x2f, 0x35, 0x34, 0x2e, 0x30, 0x2e, 0x32, 0x38, 0x34, 0x30, 0x2e, 0x39, 0x39, 0x20, 0x53, 0xc3, 0xa1, 0x66, 0xc3, 0xa1, 0x72, 0x69, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36 };
break;
case BenchmarkTypes.Cookie:
_headerName = HeaderNames.Cookie;
// prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric
_asciiBytes = new byte[] { 0x70, 0x72, 0x6f, 0x76, 0x3d, 0x32, 0x30, 0x36, 0x32, 0x39, 0x63, 0x63, 0x64, 0x2d, 0x38, 0x62, 0x30, 0x66, 0x2d, 0x65, 0x38, 0x65, 0x66, 0x2d, 0x32, 0x39, 0x33, 0x35, 0x2d, 0x63, 0x64, 0x32, 0x36, 0x36, 0x30, 0x39, 0x66, 0x63, 0x30, 0x62, 0x63, 0x3b, 0x20, 0x5f, 0x5f, 0x71, 0x63, 0x61, 0x3d, 0x50, 0x30, 0x2d, 0x31, 0x35, 0x39, 0x31, 0x30, 0x36, 0x35, 0x37, 0x33, 0x32, 0x2d, 0x31, 0x34, 0x37, 0x39, 0x31, 0x36, 0x37, 0x33, 0x35, 0x33, 0x34, 0x34, 0x32, 0x3b, 0x20, 0x5f, 0x67, 0x61, 0x3d, 0x47, 0x41, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x32, 0x39, 0x38, 0x38, 0x39, 0x38, 0x33, 0x37, 0x36, 0x2e, 0x31, 0x34, 0x37, 0x39, 0x31, 0x36, 0x37, 0x33, 0x35, 0x34, 0x3b, 0x20, 0x5f, 0x67, 0x61, 0x74, 0x3d, 0x31, 0x3b, 0x20, 0x73, 0x67, 0x74, 0x3d, 0x69, 0x64, 0x3d, 0x39, 0x35, 0x31, 0x39, 0x67, 0x66, 0x64, 0x65, 0x5f, 0x33, 0x33, 0x34, 0x37, 0x5f, 0x34, 0x37, 0x36, 0x32, 0x5f, 0x38, 0x37, 0x36, 0x32, 0x5f, 0x64, 0x66, 0x35, 0x31, 0x34, 0x35, 0x38, 0x63, 0x38, 0x65, 0x63, 0x32, 0x3b, 0x20, 0x61, 0x63, 0x63, 0x74, 0x3d, 0x74, 0x3d, 0x77, 0x68, 0x79, 0x2d, 0x69, 0x73, 0x2d, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x37, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x38, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x39, 0x2d, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x26, 0x73, 0x3d, 0x77, 0x68, 0x79, 0x2d, 0x69, 0x73, 0x2d, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x37, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x38, 0x25, 0x65, 0x30, 0x25, 0x61, 0x35, 0x25, 0x61, 0x39, 0x2d, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63 };
// prov=20629ccd-8b0f-é8éf-2935-cd26609fc0bc; __qcá=P0-1591065732-1479167353442; _gá=Gá1.2.1298898376.1479167354; _gát=1; sgt=id=9519gfdé_3347_4762_8762_df51458c8éc2; ácct=t=why-is-%é0%á5%á7%é0%á5%á8%é0%á5%á9-numéric&s=why-is-%é0%á5%á7%é0%á5%á8%é0%á5%á9-numéric
@ -67,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
for (uint i = 0; i < Iterations; i++)
{
HttpUtilities.GetRequestHeaderStringNonNullCharacters(_utf8Bytes, useLatin1: false);
HttpUtilities.GetRequestHeaderString(_utf8Bytes, _headerName, KestrelServerOptions.DefaultRequestHeaderEncodingSelector);
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Net.Http.Headers;
namespace CodeGenerator
{
@ -230,23 +231,39 @@ namespace CodeGenerator
firstTermVar = "";
}
string GenerateIfBody(KnownHeader header, string extraIndent = "")
{
if (header.Identifier == "ContentLength")
{
return $@"
{extraIndent}if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector))
{extraIndent}{{
{extraIndent} AppendContentLength(value);
{extraIndent}}}
{extraIndent}else
{extraIndent}{{
{extraIndent} AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength));
{extraIndent}}}
{extraIndent}return;";
}
else
{
return $@"
{extraIndent}flag = {header.FlagBit()};
{extraIndent}values = ref _headers._{header.Identifier};
{extraIndent}nameStr = HeaderNames.{header.Identifier};";
}
}
var groups = values.GroupBy(header => header.EqualIgnoreCaseBytesFirstTerm());
return start + $@"{Each(groups, (byFirstTerm, i) => $@"{(byFirstTerm.Count() == 1 ? $@"{Each(byFirstTerm, header => $@"
{(i > 0 ? "else " : "")}if ({header.EqualIgnoreCaseBytes(firstTermVar)})
{{{(header.Identifier == "ContentLength" ? $@"
AppendContentLength(value);
return;" : $@"
flag = {header.FlagBit()};
values = ref _headers._{header.Identifier};")}
{{{GenerateIfBody(header)}
}}")}" : $@"
if ({byFirstTerm.Key.Replace(firstTermVarExpression, firstTermVar)})
{{{Each(byFirstTerm, (header, i) => $@"
{(i > 0 ? "else " : "")}if ({header.EqualIgnoreCaseBytesSecondTermOnwards()})
{{{(header.Identifier == "ContentLength" ? $@"
AppendContentLength(value);
return;" : $@"
flag = {header.FlagBit()};
values = ref _headers._{header.Identifier};")}
{{{GenerateIfBody(header, extraIndent: " ")}
}}")}
}}")}")}";
}
@ -986,6 +1003,7 @@ $@" private void Clear(long bitsToClear)
public unsafe void Append(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
{{
ref byte nameStart = ref MemoryMarshal.GetReference(name);
var nameStr = string.Empty;
ref StringValues values = ref Unsafe.AsRef<StringValues>(null);
var flag = 0L;
@ -1017,7 +1035,7 @@ $@" private void Clear(long bitsToClear)
}}
// We didn't have a previous matching header value, or have already added a header, so get the string for this value.
var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1);
var valueStr = value.GetRequestHeaderString(nameStr, EncodingSelector);
if ((_bits & flag) == 0)
{{
// We didn't already have a header set, so add a new one.
@ -1035,8 +1053,9 @@ $@" private void Clear(long bitsToClear)
// The header was not one of the ""known"" headers.
// Convert value to string first, because passing two spans causes 8 bytes stack zeroing in
// this method with rep stosd, which is slower than necessary.
var valueStr = value.GetRequestHeaderStringNonNullCharacters(UseLatin1);
AppendUnknownHeaders(name, valueStr);
nameStr = name.GetHeaderName();
var valueStr = value.GetRequestHeaderString(nameStr, EncodingSelector);
AppendUnknownHeaders(nameStr, valueStr);
}}
}}" : "")}

View File

@ -4609,7 +4609,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public async Task HEADERS_Received_Latin1_AcceptedWhenLatin1OptionIsConfigured()
{
_serviceContext.ServerOptions.Latin1RequestHeaders = true;
_serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.Latin1;
await InitializeConnectionAsync(context =>
{

View File

@ -406,7 +406,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
void IHttpHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
{
_decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetRequestHeaderStringNonNullCharacters(useLatin1: _serviceContext.ServerOptions.Latin1RequestHeaders);
var nameStr = name.GetHeaderName();
_decodedHeaders[nameStr] = value.GetRequestHeaderString(nameStr, _serviceContext.ServerOptions.RequestHeaderEncodingSelector);
}
void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { }

View File

@ -356,7 +356,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public void OnStaticIndexedHeader(int index)
{
var knownHeader = H3StaticTable.GetHeaderFieldAt(index);
_decodedHeaders[((Span<byte>)knownHeader.Name).GetAsciiStringNonNullCharacters()] = HttpUtilities.GetAsciiOrUTF8StringNonNullCharacters(knownHeader.Value);
_decodedHeaders[((Span<byte>)knownHeader.Name).GetAsciiStringNonNullCharacters()] = HttpUtilities.GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)knownHeader.Value);
}
public void OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)

View File

@ -2001,7 +2001,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
var testContext = new TestServiceContext(LoggerFactory);
testContext.ServerOptions.Latin1RequestHeaders = true;
testContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.Latin1;
await using (var server = new TestServer(context =>
{
@ -2059,6 +2059,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
}
}
[Fact]
public async Task CustomRequestHeaderEncodingSelectorCanBeConfigured()
{
var testContext = new TestServiceContext(LoggerFactory);
testContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF32;
await using (var server = new TestServer(context =>
{
Assert.Equal("£", context.Request.Headers["X-Test"]);
return Task.CompletedTask;
}, testContext))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"X-Test: ");
await connection.Stream.WriteAsync(Encoding.UTF32.GetBytes("£")).DefaultTimeout();
await connection.Send("",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
public static TheoryData<string, string> HostHeaderData => HttpParsingData.HostHeaderData;
private class IntAsClass

View File

@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{
internal static class StringUtilities
{
private static readonly SpanAction<char, IntPtr> s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiStringNonNullCharacters;
private static string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span, Encoding defaultEncoding)
=> GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span, defaultEncoding);
@ -52,15 +54,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
}
}
private static readonly SpanAction<char, IntPtr> s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiOrUTF8StringNonNullCharacters;
private static unsafe void GetAsciiOrUTF8StringNonNullCharacters(Span<char> buffer, IntPtr state)
private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, IntPtr state)
{
fixed (char* output = &MemoryMarshal.GetReference(buffer))
{
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
// in the string
if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
// This version if AsciiUtilities returns false if there are any null ('\0') or non-Ascii
// character (> 127) in the string.
if (!TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
{
// Mark resultString for UTF-8 encoding
output[0] = '\0';