diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs index 31565455670b..c59df1e7ac3a 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs @@ -137,6 +137,7 @@ public KestrelServerOptions() { } 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 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; } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs index 19ec02774a68..40ac8cd9ffc7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs @@ -18,14 +18,12 @@ internal class ConfigurationReader 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 _certificates; private EndpointDefaults _endpointDefaults; private IEnumerable _endpoints; - private bool? _latin1RequestHeaders; public ConfigurationReader(IConfiguration configuration) { @@ -35,7 +33,6 @@ public ConfigurationReader(IConfiguration configuration) public IDictionary Certificates => _certificates ??= ReadCertificates(); public EndpointDefaults EndpointDefaults => _endpointDefaults ??= ReadEndpointDefaults(); public IEnumerable Endpoints => _endpoints ??= ReadEndpoints(); - public bool Latin1RequestHeaders => _latin1RequestHeaders ??= _configuration.GetValue(Latin1RequestHeadersKey); private IDictionary ReadCertificates() { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index 516d7c32ecca..03886c20400b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -6270,6 +6270,7 @@ protected override bool CopyToFast(KeyValuePair[] array, i public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { ref byte nameStart = ref MemoryMarshal.GetReference(name); + var nameStr = string.Empty; ref StringValues values = ref Unsafe.AsRef(null); var flag = 0L; @@ -6281,6 +6282,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x20000000000L; values = ref _headers._TE; + nameStr = HeaderNames.TE; } break; case 3: @@ -6289,11 +6291,13 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { 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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { 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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x200000L; values = ref _headers._Path; + nameStr = HeaderNames.Path; } else if (((Unsafe.ReadUnaligned(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(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x800000L; values = ref _headers._Accept; + nameStr = HeaderNames.Accept; } else if ((firstTerm6 == 0x4b4f4f43u) && ((Unsafe.ReadUnaligned(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(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(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(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x100000L; values = ref _headers._Method; + nameStr = HeaderNames.Method; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffu) == 0x4843533au) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfdfu) == 0x49505845u) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfdfu) == 0x45464552u) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfdfu) == 0x49415254u) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfdfu) == 0x52475055u) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfdfu) == 0x4e524157u) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { 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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x40000000000L; values = ref _headers._Translate; + nameStr = HeaderNames.Translate; } break; case 10: @@ -6421,31 +6446,37 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x80000L; values = ref _headers._Authority; + nameStr = HeaderNames.Authority; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x495443454e4e4f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4e4fu)) { flag = 0x2L; values = ref _headers._Connection; + nameStr = HeaderNames.Connection; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x4547412d52455355uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x544eu)) { flag = 0x80000000000L; values = ref _headers._UserAgent; + nameStr = HeaderNames.UserAgent; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x494c412d5045454buL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4556u)) { flag = 0x8L; values = ref _headers._KeepAlive; + nameStr = HeaderNames.KeepAlive; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d54534555514552uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(4 * sizeof(ushort)))) & 0xdfdfu) == 0x4449u)) { flag = 0x400000000000L; values = ref _headers._RequestId; + nameStr = HeaderNames.RequestId; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x4154534543415254uL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x8000L; values = ref _headers._ContentMD5; + nameStr = HeaderNames.ContentMD5; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x5241504543415254uL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x800L; values = ref _headers._ContentType; + nameStr = HeaderNames.ContentType; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfffdfdfdfuL) == 0x57524f462d58414duL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x8000000L; values = ref _headers._Authorization; + nameStr = HeaderNames.Authorization; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x4f432d4548434143uL) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d544e45544e4f43uL) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xffdfdfdfdfffdfdfuL) == 0x2d454e4f4e2d4649uL) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfdfffdfdfdfdfuL) == 0x444f4d2d5453414cuL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x1000000L; values = ref _headers._AcceptCharset; + nameStr = HeaderNames.AcceptCharset; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xffdfdfdfdfdfdfdfuL) == 0x2d544e45544e4f43uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x474e454cu) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x2000000L; values = ref _headers._AcceptEncoding; + nameStr = HeaderNames.AcceptEncoding; } else if ((firstTerm15 == 0x4c2d545045434341uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)(2 * sizeof(uint)))) & 0xdfdfdfdfu) == 0x55474e41u) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x1000L; values = ref _headers._ContentEncoding; + nameStr = HeaderNames.ContentEncoding; } else if (((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x45474155474e414cuL)) { flag = 0x2000L; values = ref _headers._ContentLanguage; + nameStr = HeaderNames.ContentLanguage; } else if (((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x4e4f495441434f4cuL)) { flag = 0x4000L; values = ref _headers._ContentLocation; + nameStr = HeaderNames.ContentLocation; } } break; @@ -6550,11 +6603,13 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x200000000L; values = ref _headers._IfModifiedSince; + nameStr = HeaderNames.IfModifiedSince; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfdfdfdfuL) == 0x524546534e415254uL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x800000000000L; values = ref _headers._CorrelationContext; + nameStr = HeaderNames.CorrelationContext; } else if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfdfdfdfffdfdfuL) == 0x444f4d4e552d4649uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfffdfdfdfdfdfuL) == 0x49532d4445494649uL) && ((Unsafe.ReadUnaligned(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(ref nameStart) & 0xdfdfffdfdfdfdfdfuL) == 0x55412d59584f5250uL) && ((Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref nameStart, (IntPtr)sizeof(ulong))) & 0xdfdfdfdfdfdfdfdfuL) == 0x54415a49524f4854uL) && ((Unsafe.ReadUnaligned(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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x200000000000L; values = ref _headers._UpgradeInsecureRequests; + nameStr = HeaderNames.UpgradeInsecureRequests; } break; case 29: @@ -6586,6 +6645,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x8000000000000L; values = ref _headers._AccessControlRequestMethod; + nameStr = HeaderNames.AccessControlRequestMethod; } break; case 30: @@ -6593,6 +6653,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x10000000000000L; values = ref _headers._AccessControlRequestHeaders; + nameStr = HeaderNames.AccessControlRequestHeaders; } break; } @@ -6622,7 +6683,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) } // 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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) // 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); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 1fe3f1f7531a..13065f095e43 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -369,7 +369,7 @@ public void Reset() ConnectionIdFeature = ConnectionId; HttpRequestHeaders.Reset(); - HttpRequestHeaders.UseLatin1 = ServerOptions.Latin1RequestHeaders; + HttpRequestHeaders.EncodingSelector = ServerOptions.RequestHeaderEncodingSelector; HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse; HttpResponseHeaders.Reset(); RequestHeaders = HttpRequestHeaders; @@ -532,7 +532,7 @@ public void OnTrailer(ReadOnlySpan name, ReadOnlySpan value) } string key = name.GetHeaderName(); - var valueStr = value.GetRequestHeaderStringNonNullCharacters(ServerOptions.Latin1RequestHeaders); + var valueStr = value.GetRequestHeaderString(key, HttpRequestHeaders.EncodingSelector); RequestTrailers.Append(key, valueStr); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs index c2a75857c2b9..bfc135c34a29 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs @@ -5,7 +5,9 @@ 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 @@ internal sealed partial class HttpRequestHeaders : HttpHeaders private long _previousBits = 0; public bool ReuseHeaderValues { get; set; } - public bool UseLatin1 { get; set; } + public Func EncodingSelector { get; set; } - public HttpRequestHeaders(bool reuseHeaderValues = true, bool useLatin1 = false) + public HttpRequestHeaders(bool reuseHeaderValues = true, Func encodingSelector = null) { ReuseHeaderValues = reuseHeaderValues; - UseLatin1 = useLatin1; + EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultRequestHeaderEncodingSelector; } public void OnHeadersComplete() @@ -87,7 +89,30 @@ private void AppendContentLength(ReadOnlySpan value) 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 value, Encoding customEncoding) + { + if (_contentLength.HasValue) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths); + } + + // long.MaxValue = 9223372036854775807 (19 chars) + Span 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 @@ private bool AddValueUnknown(string key, StringValues value) } [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void AppendUnknownHeaders(ReadOnlySpan 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() diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index ede9d52e2eab..56235dd2a293 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -27,7 +27,7 @@ internal static partial class HttpUtilities 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 _getHeaderName = GetHeaderName; private static readonly SpanAction _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters; @@ -120,11 +120,8 @@ public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan span) - => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span); - public static string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span) - => StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, HeaderValueEncoding); + => StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, DefaultRequestHeaderEncoding); private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) { @@ -139,8 +136,34 @@ private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, In } } - public static string GetRequestHeaderStringNonNullCharacters(this ReadOnlySpan span, bool useLatin1) => - useLatin1 ? span.GetLatin1StringNonNullCharacters() : span.GetAsciiOrUTF8StringNonNullCharacters(HeaderValueEncoding); + public static string GetRequestHeaderString(this ReadOnlySpan span, string name, Func 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 span, int maxChars) { diff --git a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs index 193d07ca3202..ad7210b6d655 100644 --- a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs @@ -255,8 +255,6 @@ public void Load() ConfigurationReader = new ConfigurationReader(Configuration); - Options.Latin1RequestHeaders = ConfigurationReader.Latin1RequestHeaders; - LoadDefaultCert(ConfigurationReader); foreach (var endpoint in ConfigurationReader.Endpoints) diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 16d9e34f0131..e38860ad4ebd 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -35,12 +35,19 @@ public class KestrelServer : IServer private IDisposable _configChangedRegistration; - public KestrelServer(IOptions options, IEnumerable transportFactories, ILoggerFactory loggerFactory) + public KestrelServer( + IOptions options, + IEnumerable transportFactories, + ILoggerFactory loggerFactory) : this(transportFactories, null, CreateServiceContext(options, loggerFactory)) { } - public KestrelServer(IOptions options, IEnumerable transportFactories, IEnumerable multiplexedFactories, ILoggerFactory loggerFactory) + public KestrelServer( + IOptions options, + IEnumerable transportFactories, + IEnumerable multiplexedFactories, + ILoggerFactory loggerFactory) : this(transportFactories, multiplexedFactories, CreateServiceContext(options, loggerFactory)) { } @@ -52,7 +59,10 @@ internal KestrelServer(IEnumerable transportFactorie } // For testing - internal KestrelServer(IEnumerable transportFactories, IEnumerable multiplexedFactories, ServiceContext serviceContext) + internal KestrelServer( + IEnumerable transportFactories, + IEnumerable multiplexedFactories, + ServiceContext serviceContext) { if (transportFactories == null) { diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index e643478c11f8..917c1c8a75f8 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -7,6 +7,7 @@ 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 /// public class KestrelServerOptions { + // internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. + internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => null; + + private Func _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 CodeBackedListenOptions { get; } = new List(); internal List ConfigurationBackedListenOptions { get; } = new List(); @@ -65,6 +71,24 @@ public class KestrelServerOptions /// public bool DisableStringReuse { get; set; } = false; + /// + /// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3 + /// + /// + /// Defaults to false. + /// + public bool EnableAltSvc { get; set; } = false; + + /// + /// Gets or sets a callback that returns the to decode the value for the specified request header name, + /// or to use the default . + /// + public Func RequestHeaderEncodingSelector + { + get => _requestHeaderEncodingSelector; + set => _requestHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value)); + } + /// /// Enables the Listen options callback to resolve and use services registered by the application during startup. /// Typically initialized by UseKestrel()"/>. @@ -78,15 +102,10 @@ public class KestrelServerOptions /// /// Provides a configuration source where endpoints will be loaded from on server start. - /// The default is null. + /// The default is . /// public KestrelConfigurationLoader ConfigurationLoader { get; set; } - /// - /// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3 - /// - public bool EnableAltSvc { get; set; } = false; - /// /// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs. /// @@ -107,11 +126,6 @@ public class KestrelServerOptions /// internal bool IsDevCertLoaded { get; set; } - /// - /// Treat request headers as Latin-1 or ISO/IEC 8859-1 instead of UTF-8. - /// - internal bool Latin1RequestHeaders { get; set; } - /// /// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace /// the prior action. @@ -159,7 +173,7 @@ private void EnsureDefaultCert() if (DefaultCertificate == null && !IsDevCertLoaded) { IsDevCertLoaded = true; // Only try once - var logger = ApplicationServices.GetRequiredService>(); + var logger = ApplicationServices!.GetRequiredService>(); try { DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true) @@ -220,7 +234,7 @@ private void EnsureDefaultCert() /// /// The configuration section for Kestrel. /// - /// If , Kestrel will dynamically update endpoint bindings when configuration changes. + /// If , Kestrel will dynamically update endpoint bindings when configuration changes. /// This will only reload endpoints defined in the "Endpoints" section of your . Endpoints defined in code will not be reloaded. /// /// A for further endpoint configuration. diff --git a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs index 098f6fc0ed79..93d28dea7495 100644 --- a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs @@ -8,6 +8,7 @@ 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 @@ public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters() 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( #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 @@ public void ValueReuseLatin1NotConfusedForUtf16AndStillRejected(bool reuseValue, Assert.Throws(() => { 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 @@ public void ValueReuseLatin1NotConfusedForUtf16AndStillRejected(bool reuseValue, [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 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH 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 parsedHeaderValue1 = ((IHeaderDictionary)headers)[header.Name].ToString(); + + headers.Reset(); headers.Append(headerName, latinValueSpan); headers.OnHeadersComplete(); - var parsedHeaderValue = ((IHeaderDictionary)headers)[header.Name].ToString(); + var parsedHeaderValue2 = ((IHeaderDictionary)headers)[header.Name].ToString(); - Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue); - Assert.NotSame(headerValueUtf16Latin1CrossOver, parsedHeaderValue); + Assert.Equal(headerValueUtf16Latin1CrossOver, parsedHeaderValue1); + Assert.Equal(parsedHeaderValue1, parsedHeaderValue2); + Assert.NotSame(parsedHeaderValue1, parsedHeaderValue2); } // Reset back to Ascii @@ -541,7 +546,7 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH [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)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 @@ public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeade } } + [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(() => 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(() => + new HttpRequestHeaders().Append(contentLengthNameBytes, contentLengthValueBytes)); + } + [Fact] public void ValueReuseNeverWhenUnknownHeader() { diff --git a/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs index f070266ec536..956859b27962 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerOptionsTests.cs @@ -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 @@ public void CanCallListenAfterConfigure() // https://github.com/dotnet/aspnetcore/issues/21423 options.ListenLocalhost(5000); } + + [Fact] + public void SettingRequestHeaderEncodingSelecterThrowsArgumentNullException() + { + var options = new KestrelServerOptions(); + + var ex = Assert.Throws(() => options.RequestHeaderEncodingSelector = null); + Assert.Equal("value", ex.ParamName); + } } } diff --git a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs index 35043d8322dc..5083ba7d16f5 100644 --- a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs +++ b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs @@ -5,6 +5,7 @@ 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 @@ public class UTF8DecodingTests [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 @@ private void ExceptionThrownForZeroOrNonAscii(byte[] bytes) var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray(); Array.Copy(bytes, 0, byteRange, position, bytes.Length); - Assert.Throws(() => HttpUtilities.GetRequestHeaderStringNonNullCharacters(byteRange.AsSpan(), useLatin1: false)); + Assert.Throws(() => + HttpUtilities.GetRequestHeaderString(byteRange.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)); } } } diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 8d93023a467c..fb0b182cdd28 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -7,6 +7,7 @@ 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 @@ public void DefaultEndpointConfigureSection_ConfigureHttpsDefaultsCanOverrideSsl 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("Latin1RequestHeaders", "true"), - }).Build(); - - Assert.False(options.Latin1RequestHeaders); - options.Configure(config).Load(); - Assert.True(options.Latin1RequestHeaders); - } - [Fact] public void Reload_IdentifiesEndpointsToStartAndStop() { diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/BytesToStringBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/BytesToStringBenchmark.cs index cd6d19b75c91..a38bf2dc48aa 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/BytesToStringBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/BytesToStringBenchmark.cs @@ -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 @@ public class BytesToStringBenchmark { private const int Iterations = 50; + private string _headerName; private byte[] _asciiBytes; private byte[] _utf8Bytes; @@ -27,24 +30,28 @@ public void Setup() 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 @@ public void Utf8BytesToString() { for (uint i = 0; i < Iterations; i++) { - HttpUtilities.GetRequestHeaderStringNonNullCharacters(_utf8Bytes, useLatin1: false); + HttpUtilities.GetRequestHeaderString(_utf8Bytes, _headerName, KestrelServerOptions.DefaultRequestHeaderEncodingSelector); } } diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index e6389a675eb5..5295f8ce5b9a 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using Microsoft.Net.Http.Headers; namespace CodeGenerator { @@ -230,23 +231,39 @@ static string AppendSwitchSection(int length, IOrderedEnumerable va 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 @@ internal unsafe void CopyToFast(ref BufferWriter output) public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) {{ ref byte nameStart = ref MemoryMarshal.GetReference(name); + var nameStr = string.Empty; ref StringValues values = ref Unsafe.AsRef(null); var flag = 0L; @@ -1017,7 +1035,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) }} // 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 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) // 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); }} }}" : "")} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 56f42251ee63..3ca2b86c14b0 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -4609,7 +4609,7 @@ await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, expectedErrorMessage: [Fact] public async Task HEADERS_Received_Latin1_AcceptedWhenLatin1OptionIsConfigured() { - _serviceContext.ServerOptions.Latin1RequestHeaders = true; + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.Latin1; await InitializeConnectionAsync(context => { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index c379a4f4dd0a..adae3b99c0bb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -406,7 +406,8 @@ public override void Dispose() void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan 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) { } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 1419c9cc8ef1..07afb75f2853 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -356,7 +356,7 @@ public void OnHeadersComplete(bool endHeaders) public void OnStaticIndexedHeader(int index) { var knownHeader = H3StaticTable.GetHeaderFieldAt(index); - _decodedHeaders[((Span)knownHeader.Name).GetAsciiStringNonNullCharacters()] = HttpUtilities.GetAsciiOrUTF8StringNonNullCharacters(knownHeader.Value); + _decodedHeaders[((Span)knownHeader.Name).GetAsciiStringNonNullCharacters()] = HttpUtilities.GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)knownHeader.Value); } public void OnStaticIndexedHeader(int index, ReadOnlySpan value) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 3ada9a09a6cf..6257858c1c30 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -2001,7 +2001,7 @@ public async Task Latin1HeaderValueAcceptedWhenLatin1OptionIsConfigured() { 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 @@ await connection.ReceiveEnd( } } + [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 HostHeaderData => HttpParsingData.HostHeaderData; private class IntAsClass diff --git a/src/Shared/ServerInfrastructure/StringUtilities.cs b/src/Shared/ServerInfrastructure/StringUtilities.cs index 8f6b01027a88..0edcea2cf7df 100644 --- a/src/Shared/ServerInfrastructure/StringUtilities.cs +++ b/src/Shared/ServerInfrastructure/StringUtilities.cs @@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { internal static class StringUtilities { + private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiStringNonNullCharacters; + private static string GetAsciiOrUTF8StringNonNullCharacters(this Span span, Encoding defaultEncoding) => GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan)span, defaultEncoding); @@ -52,15 +54,13 @@ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlyS } } - private static readonly SpanAction s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiOrUTF8StringNonNullCharacters; - - private static unsafe void GetAsciiOrUTF8StringNonNullCharacters(Span buffer, IntPtr state) + private static unsafe void GetAsciiStringNonNullCharacters(Span 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';