From 27f60a4a2b3c8da99d58640d0aa6631459b3f372 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 18 Jun 2020 18:36:56 -0700 Subject: [PATCH 1/8] Add custom request header decoder API to Kestrel --- .../Internal/Http/HttpHeaders.Generated.cs | 71 +++++++++++++++++-- .../Core/src/Internal/Http/HttpProtocol.cs | 4 +- .../src/Internal/Http/HttpRequestHeaders.cs | 41 ++++++++--- .../Internal/Infrastructure/HttpUtilities.cs | 45 ++++++++---- .../Infrastructure/UTF8EncodingSealed.cs | 16 +++++ src/Servers/Kestrel/Core/src/KestrelServer.cs | 23 +++++- .../Kestrel/Core/src/KestrelServerOptions.cs | 53 +++++++++++--- .../Core/test/HttpRequestHeadersTests.cs | 8 ++- src/Servers/Kestrel/Core/test/UTF8Decoding.cs | 6 +- .../BytesToStringBenchmark.cs | 9 ++- src/Servers/Kestrel/shared/KnownHeaders.cs | 46 ++++++++---- .../Http2/Http2TestBase.cs | 3 +- .../Http3/Http3TestBase.cs | 2 +- .../ServerInfrastructure/StringUtilities.cs | 12 ++-- 14 files changed, 270 insertions(+), 69 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs 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..43940a295202 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,19 @@ 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) + || ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector)) + { + AppendContentLength(value); + } + else + { + AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength)); + } return; } break; @@ -6517,11 +6566,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 +6583,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 +6604,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 +6618,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 +6638,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x200000000000L; values = ref _headers._UpgradeInsecureRequests; + nameStr = HeaderNames.UpgradeInsecureRequests; } break; case 29: @@ -6586,6 +6646,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x8000000000000L; values = ref _headers._AccessControlRequestMethod; + nameStr = HeaderNames.AccessControlRequestMethod; } break; case 30: @@ -6593,6 +6654,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) { flag = 0x10000000000000L; values = ref _headers._AccessControlRequestHeaders; + nameStr = HeaderNames.AccessControlRequestHeaders; } break; } @@ -6622,7 +6684,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 +6702,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..8e5d0be62dbd 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.GetRequestHeaderEncodingSelector(); 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..84af2eadcb82 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,12 +89,36 @@ 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; + } + + [MethodImpl(MethodImplOptions.NoInlining)] private void SetValueUnknown(string key, StringValues value) { @@ -108,11 +134,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..357e3acc783f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using System.Text; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure @@ -27,7 +28,6 @@ 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 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, KestrelServerOptions.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(KestrelServerOptions.DefaultRequestHeaderEncoding); + } + + var encoding = encodingSelector(name); + + if (encoding is null) + { + return span.GetAsciiOrUTF8StringNonNullCharacters(KestrelServerOptions.DefaultRequestHeaderEncoding); + } + + if (ReferenceEquals(encoding, Encoding.Latin1)) + { + return span.GetLatin1StringNonNullCharacters(); + } + + try + { + return encoding.GetString(span); + } + catch (DecoderFallbackException) + { + throw new InvalidOperationException(); + } + } public static string GetAsciiStringEscaped(this ReadOnlySpan span, int maxChars) { @@ -529,13 +552,5 @@ private static bool IsHex(char ch) // Check if less than 6 representing chars 'a' - 'f' || (uint)((ch | 32) - 'a') < 6u; } - - // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) - private sealed class UTF8EncodingSealed : UTF8Encoding - { - public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } - - public override byte[] GetPreamble() => Array.Empty(); - } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs new file mode 100644 index 000000000000..f159cc886ae5 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs @@ -0,0 +1,16 @@ +// 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.Text; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) + internal sealed class UTF8EncodingSealed : UTF8Encoding + { + public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } + + public override byte[] GetPreamble() => Array.Empty(); + } +} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 16d9e34f0131..81d65e59b8a8 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -35,12 +37,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 +61,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) { @@ -362,6 +374,11 @@ private void ValidateOptions() throw new InvalidOperationException( CoreStrings.FormatMaxRequestBufferSmallerThanRequestHeaderBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestHeadersTotalSize)); } + + if (Options.RequestHeaderEncodingSelector is null) + { + throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be null."); + } } private static ConnectionDelegate EnforceConnectionLimit(ConnectionDelegate innerDelegate, long? connectionLimit, IKestrelTrace trace) diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index e643478c11f8..a7072b39b297 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -1,15 +1,19 @@ // 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. +#nullable enable + using System; using System.Collections.Generic; 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; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +26,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public class KestrelServerOptions { + // Internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. + internal static readonly UTF8EncodingSealed DefaultRequestHeaderEncoding = new UTF8EncodingSealed(); + internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => DefaultRequestHeaderEncoding; + internal static readonly Func DefaultLatin1RequestHeaderEncodingSelector = _ => Encoding.Latin1; + // 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,11 +74,27 @@ 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. + /// + /// + /// Defaults to returning a for all headers. + /// + public Func RequestHeaderEncodingSelector { get; set; } = DefaultRequestHeaderEncodingSelector; + /// /// Enables the Listen options callback to resolve and use services registered by the application during startup. /// Typically initialized by UseKestrel()"/>. /// - public IServiceProvider ApplicationServices { get; set; } + public IServiceProvider? ApplicationServices { get; set; } /// /// Provides access to request limit options. @@ -80,12 +105,7 @@ public class KestrelServerOptions /// Provides a configuration source where endpoints will be loaded from on server start. /// The default is null. /// - 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; + public KestrelConfigurationLoader? ConfigurationLoader { get; set; } /// /// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs. @@ -100,7 +120,7 @@ public class KestrelServerOptions /// /// The default server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options. /// - internal X509Certificate2 DefaultCertificate { get; set; } + internal X509Certificate2? DefaultCertificate { get; set; } /// /// Has the default dev certificate load been attempted? @@ -121,9 +141,19 @@ public void ConfigureEndpointDefaults(Action configureOptions) EndpointDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions)); } + internal Func GetRequestHeaderEncodingSelector() + { + if (ReferenceEquals(RequestHeaderEncodingSelector, DefaultRequestHeaderEncodingSelector) && Latin1RequestHeaders) + { + return DefaultLatin1RequestHeaderEncodingSelector; + } + + return RequestHeaderEncodingSelector; + } + internal void ApplyEndpointDefaults(ListenOptions listenOptions) { - listenOptions.KestrelServerOptions = this; + listenOptions.KestrelServerOptions = this; ConfigurationLoader?.ApplyConfigurationDefaults(listenOptions); EndpointDefaults(listenOptions); } @@ -159,7 +189,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,10 +250,11 @@ 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. + public KestrelConfigurationLoader Configure(IConfiguration config, bool reloadOnChange) { var loader = new KestrelConfigurationLoader(this, config, reloadOnChange); diff --git a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs index 098f6fc0ed79..959af0d7a814 100644 --- a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs @@ -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, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector); var headerValue = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < headerValue.Length; i++) @@ -541,7 +541,11 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH [MemberData(nameof(KnownRequestHeaders))] public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header) { - var headers = new HttpRequestHeaders(useLatin1: useLatin1); + var selector = useLatin1 ? + KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector : + KestrelServerOptions.DefaultRequestHeaderEncodingSelector; + + var headers = new HttpRequestHeaders(encodingSelector: selector); var valueArray = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < valueArray.Length; i++) 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/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..f3466af3b97e 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,40 @@ 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} || ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector)) + {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 +1004,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 +1036,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 +1054,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/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index c379a4f4dd0a..f3332dbe351f 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.GetRequestHeaderEncodingSelector()); } 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/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'; From 602353b3b201e84a64832e7db82e80817dc3d889 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 22 Jun 2020 12:31:56 -0700 Subject: [PATCH 2/8] Add tests --- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- .../Kestrel/Core/src/KestrelServerOptions.cs | 3 +- .../Core/test/HttpRequestHeadersTests.cs | 79 ++++++++++++++++--- .../Kestrel/Core/test/KestrelServerTests.cs | 16 ++++ .../test/KestrelConfigurationLoaderTests.cs | 28 +++++++ .../InMemory.FunctionalTests/RequestTests.cs | 36 +++++++++ 6 files changed, 149 insertions(+), 15 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 81d65e59b8a8..7680c4457d9b 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -377,7 +377,7 @@ private void ValidateOptions() if (Options.RequestHeaderEncodingSelector is null) { - throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be null."); + throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be set to null."); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index a7072b39b297..73a4a79a2ec2 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -148,7 +148,8 @@ public void ConfigureEndpointDefaults(Action configureOptions) return DefaultLatin1RequestHeaderEncodingSelector; } - return RequestHeaderEncodingSelector; + return RequestHeaderEncodingSelector + ?? throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(RequestHeaderEncodingSelector)} must not be set to null."); } internal void ApplyEndpointDefaults(ListenOptions listenOptions) diff --git a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs index 959af0d7a814..33cb1be0439b 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))); @@ -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,11 +546,12 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH [MemberData(nameof(KnownRequestHeaders))] public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header) { - var selector = useLatin1 ? - KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector : - KestrelServerOptions.DefaultRequestHeaderEncodingSelector; + var kso = new KestrelServerOptions + { + Latin1RequestHeaders = useLatin1, + }; - var headers = new HttpRequestHeaders(encodingSelector: selector); + var headers = new HttpRequestHeaders(encodingSelector: kso.GetRequestHeaderEncodingSelector()); var valueArray = new char[127]; // 64 + 32 + 16 + 8 + 4 + 2 + 1 for (var i = 0; i < valueArray.Length; i++) @@ -573,6 +579,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/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 4f41ddbe1900..e1d4ccc08cdc 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -245,6 +245,22 @@ public void StartWithMultipleTransportFactoriesDoesNotThrow() StartDummyApplication(server); } + [Fact] + public void StartWithNullRequestHeaderEncodingSelectorThrows() + { + var kso = CreateServerOptions(); + kso.RequestHeaderEncodingSelector = null; + + var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; + + using var server = CreateServer(kso, testLogger); + + var ex = Assert.Throws(() => StartDummyApplication(server)); + Assert.Contains(nameof(KestrelServerOptions.RequestHeaderEncodingSelector), ex.Message); + + Assert.Equal(1, testLogger.CriticalErrorsLogged); + } + [Fact] public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() { diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 8d93023a467c..29321e616248 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; @@ -606,6 +607,33 @@ public void Latin1RequestHeadersReadFromConfig() Assert.False(options.Latin1RequestHeaders); options.Configure(config).Load(); Assert.True(options.Latin1RequestHeaders); + Assert.Same(KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); + } + + [Fact] + public void Latin1RequestHeadersReadFromConfigCanBeOverriddenBySettingRequestHeaderEncodingSelector() + { + 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); + + Assert.NotSame(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); + + options.RequestHeaderEncodingSelector = _ => Encoding.ASCII; + Assert.Same(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 3ada9a09a6cf..7cd2c4222970 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -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 From 7c34ed1817f66ee4f1e319988972409ee31560c2 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 22 Jun 2020 12:57:55 -0700 Subject: [PATCH 3/8] Improve doc comment --- src/Servers/Kestrel/Core/src/KestrelServerOptions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index 73a4a79a2ec2..8088f011bc8b 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -83,10 +83,11 @@ public class KestrelServerOptions public bool EnableAltSvc { get; set; } = false; /// - /// Gets or sets a callback that returns the to decode the value for the specified request header name. + /// Gets or sets a callback that returns the to decode the value for the specified request header name, + /// or to use the default . /// /// - /// Defaults to returning a for all headers. + /// Defaults to returning a for all headers. /// public Func RequestHeaderEncodingSelector { get; set; } = DefaultRequestHeaderEncodingSelector; @@ -103,7 +104,7 @@ 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; } From 032d02e30369035063997afbfc59051dd84b54f5 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 22 Jun 2020 14:27:24 -0700 Subject: [PATCH 4/8] Update ref assembly --- .../Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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..9feee6c531e2 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 @@ -132,11 +132,12 @@ public KestrelServerOptions() { } public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool AllowResponseHeaderCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.IServiceProvider? ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader? ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } 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 { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] 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; } From d009eb12e9c63a41648f1711047e6b0400f8a4d3 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 22 Jun 2020 17:09:39 -0700 Subject: [PATCH 5/8] Address PR feedback --- .../src/Internal/Http/HttpRequestHeaders.cs | 1 - .../Internal/Infrastructure/HttpUtilities.cs | 15 +++++++++--- .../Infrastructure/UTF8EncodingSealed.cs | 16 ------------- src/Servers/Kestrel/Core/src/KestrelServer.cs | 7 ------ .../Kestrel/Core/src/KestrelServerOptions.cs | 23 +++++++++---------- .../Core/test/KestrelServerOptionsTests.cs | 10 ++++++++ .../Kestrel/Core/test/KestrelServerTests.cs | 16 ------------- 7 files changed, 33 insertions(+), 55 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs index 84af2eadcb82..bfc135c34a29 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs @@ -118,7 +118,6 @@ private void AppendContentLengthCustomEncoding(ReadOnlySpan value, Encodin _contentLength = parsed; } - [MethodImpl(MethodImplOptions.NoInlining)] private void SetValueUnknown(string key, StringValues value) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index 357e3acc783f..e39e1fff56d6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -28,6 +28,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 DefaultRequestHeaderEncoding = new UTF8EncodingSealed(); private static readonly SpanAction _getHeaderName = GetHeaderName; private static readonly SpanAction _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters; @@ -121,7 +122,7 @@ public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan span) - => StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, KestrelServerOptions.DefaultRequestHeaderEncoding); + => StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, DefaultRequestHeaderEncoding); private static unsafe void GetAsciiStringNonNullCharacters(Span buffer, IntPtr state) { @@ -140,14 +141,14 @@ public static string GetRequestHeaderString(this ReadOnlySpan span, string { if (ReferenceEquals(KestrelServerOptions.DefaultRequestHeaderEncodingSelector, encodingSelector)) { - return span.GetAsciiOrUTF8StringNonNullCharacters(KestrelServerOptions.DefaultRequestHeaderEncoding); + return span.GetAsciiOrUTF8StringNonNullCharacters(DefaultRequestHeaderEncoding); } var encoding = encodingSelector(name); if (encoding is null) { - return span.GetAsciiOrUTF8StringNonNullCharacters(KestrelServerOptions.DefaultRequestHeaderEncoding); + return span.GetAsciiOrUTF8StringNonNullCharacters(DefaultRequestHeaderEncoding); } if (ReferenceEquals(encoding, Encoding.Latin1)) @@ -552,5 +553,13 @@ private static bool IsHex(char ch) // Check if less than 6 representing chars 'a' - 'f' || (uint)((ch | 32) - 'a') < 6u; } + + // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) + private sealed class UTF8EncodingSealed : UTF8Encoding + { + public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } + + public override byte[] GetPreamble() => Array.Empty(); + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs deleted file mode 100644 index f159cc886ae5..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/UTF8EncodingSealed.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Text; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) - internal sealed class UTF8EncodingSealed : UTF8Encoding - { - public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } - - public override byte[] GetPreamble() => Array.Empty(); - } -} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 7680c4457d9b..e38860ad4ebd 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -374,11 +372,6 @@ private void ValidateOptions() throw new InvalidOperationException( CoreStrings.FormatMaxRequestBufferSmallerThanRequestHeaderBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestHeadersTotalSize)); } - - if (Options.RequestHeaderEncodingSelector is null) - { - throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.RequestHeaderEncodingSelector)} must not be set to null."); - } } private static ConnectionDelegate EnforceConnectionLimit(ConnectionDelegate innerDelegate, long? connectionLimit, IKestrelTrace trace) diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index 8088f011bc8b..a07c0ef88fbc 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Certificates.Generation; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -26,11 +25,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public class KestrelServerOptions { - // Internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. - internal static readonly UTF8EncodingSealed DefaultRequestHeaderEncoding = new UTF8EncodingSealed(); - internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => DefaultRequestHeaderEncoding; + // internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. + internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => null; internal static readonly Func DefaultLatin1RequestHeaderEncodingSelector = _ => Encoding.Latin1; + 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(); @@ -86,10 +86,11 @@ public class KestrelServerOptions /// Gets or sets a callback that returns the to decode the value for the specified request header name, /// or to use the default . /// - /// - /// Defaults to returning a for all headers. - /// - public Func RequestHeaderEncodingSelector { get; set; } = DefaultRequestHeaderEncodingSelector; + 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. @@ -144,13 +145,12 @@ public void ConfigureEndpointDefaults(Action configureOptions) internal Func GetRequestHeaderEncodingSelector() { - if (ReferenceEquals(RequestHeaderEncodingSelector, DefaultRequestHeaderEncodingSelector) && Latin1RequestHeaders) + if (ReferenceEquals(_requestHeaderEncodingSelector, DefaultRequestHeaderEncodingSelector) && Latin1RequestHeaders) { return DefaultLatin1RequestHeaderEncodingSelector; } - return RequestHeaderEncodingSelector - ?? throw new InvalidOperationException($"{nameof(KestrelServerOptions)}.{nameof(RequestHeaderEncodingSelector)} must not be set to null."); + return _requestHeaderEncodingSelector; } internal void ApplyEndpointDefaults(ListenOptions listenOptions) @@ -256,7 +256,6 @@ private void EnsureDefaultCert() /// 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. - public KestrelConfigurationLoader Configure(IConfiguration config, bool reloadOnChange) { var loader = new KestrelConfigurationLoader(this, config, reloadOnChange); 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/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index e1d4ccc08cdc..4f41ddbe1900 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -245,22 +245,6 @@ public void StartWithMultipleTransportFactoriesDoesNotThrow() StartDummyApplication(server); } - [Fact] - public void StartWithNullRequestHeaderEncodingSelectorThrows() - { - var kso = CreateServerOptions(); - kso.RequestHeaderEncodingSelector = null; - - var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; - - using var server = CreateServer(kso, testLogger); - - var ex = Assert.Throws(() => StartDummyApplication(server)); - Assert.Contains(nameof(KestrelServerOptions.RequestHeaderEncodingSelector), ex.Message); - - Assert.Equal(1, testLogger.CriticalErrorsLogged); - } - [Fact] public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() { From 4cf663aec36fbaedc9ff2810ba0696639b002bc2 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 23 Jun 2020 18:05:02 -0700 Subject: [PATCH 6/8] Remove nullable annotations for now --- ....AspNetCore.Server.Kestrel.Core.netcoreapp.cs | 6 +++--- .../Kestrel/Core/src/KestrelServerOptions.cs | 16 +++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) 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 9feee6c531e2..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 @@ -132,12 +132,12 @@ public KestrelServerOptions() { } public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool AllowResponseHeaderCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public System.IServiceProvider? ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader? ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } 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 { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + 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/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index a07c0ef88fbc..eb973dc0ba8c 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -1,8 +1,6 @@ // 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. -#nullable enable - using System; using System.Collections.Generic; using System.IO; @@ -26,10 +24,10 @@ 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; + internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => null; internal static readonly Func DefaultLatin1RequestHeaderEncodingSelector = _ => Encoding.Latin1; - private Func _requestHeaderEncodingSelector = DefaultRequestHeaderEncodingSelector; + 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(); @@ -86,7 +84,7 @@ public class KestrelServerOptions /// 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 + public Func RequestHeaderEncodingSelector { get => _requestHeaderEncodingSelector; set => _requestHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value)); @@ -96,7 +94,7 @@ public class KestrelServerOptions /// Enables the Listen options callback to resolve and use services registered by the application during startup. /// Typically initialized by UseKestrel()"/>. /// - public IServiceProvider? ApplicationServices { get; set; } + public IServiceProvider ApplicationServices { get; set; } /// /// Provides access to request limit options. @@ -107,7 +105,7 @@ public class KestrelServerOptions /// Provides a configuration source where endpoints will be loaded from on server start. /// The default is . /// - public KestrelConfigurationLoader? ConfigurationLoader { get; set; } + public KestrelConfigurationLoader ConfigurationLoader { get; set; } /// /// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs. @@ -122,7 +120,7 @@ public class KestrelServerOptions /// /// The default server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options. /// - internal X509Certificate2? DefaultCertificate { get; set; } + internal X509Certificate2 DefaultCertificate { get; set; } /// /// Has the default dev certificate load been attempted? @@ -143,7 +141,7 @@ public void ConfigureEndpointDefaults(Action configureOptions) EndpointDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions)); } - internal Func GetRequestHeaderEncodingSelector() + internal Func GetRequestHeaderEncodingSelector() { if (ReferenceEquals(_requestHeaderEncodingSelector, DefaultRequestHeaderEncodingSelector) && Latin1RequestHeaders) { From 39f8ada48aaed6da6fdd370907f0933da643fa4b Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 24 Jun 2020 11:30:58 -0700 Subject: [PATCH 7/8] Cleanup whitespace and pass inner exception --- .../src/Internal/Infrastructure/HttpUtilities.cs | 12 ++++++------ src/Servers/Kestrel/Core/src/KestrelServerOptions.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index e39e1fff56d6..496307f91df2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -160,9 +160,9 @@ public static string GetRequestHeaderString(this ReadOnlySpan span, string { return encoding.GetString(span); } - catch (DecoderFallbackException) + catch (DecoderFallbackException ex) { - throw new InvalidOperationException(); + throw new InvalidOperationException(ex.Message, ex); } } @@ -555,11 +555,11 @@ private static bool IsHex(char ch) } // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) - private sealed class UTF8EncodingSealed : UTF8Encoding - { - public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } + private sealed class UTF8EncodingSealed : UTF8Encoding + { + public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } - public override byte[] GetPreamble() => Array.Empty(); + public override byte[] GetPreamble() => Array.Empty(); } } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index eb973dc0ba8c..d5642d30ddd7 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -88,7 +88,7 @@ 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. @@ -153,7 +153,7 @@ internal Func GetRequestHeaderEncodingSelector() internal void ApplyEndpointDefaults(ListenOptions listenOptions) { - listenOptions.KestrelServerOptions = this; + listenOptions.KestrelServerOptions = this; ConfigurationLoader?.ApplyConfigurationDefaults(listenOptions); EndpointDefaults(listenOptions); } From b9404b03ad150bbeb3c99b19aabf716969c5bbb5 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 24 Jun 2020 11:49:52 -0700 Subject: [PATCH 8/8] Remove "Latin1RequestHeaders" config --- .../Core/src/Internal/ConfigurationReader.cs | 3 -- .../Internal/Http/HttpHeaders.Generated.cs | 3 +- .../Core/src/Internal/Http/HttpProtocol.cs | 2 +- .../Internal/Infrastructure/HttpUtilities.cs | 3 +- .../Core/src/KestrelConfigurationLoader.cs | 2 - .../Kestrel/Core/src/KestrelServerOptions.cs | 16 ------- .../Core/test/HttpRequestHeadersTests.cs | 9 +--- .../test/KestrelConfigurationLoaderTests.cs | 48 ------------------- src/Servers/Kestrel/shared/KnownHeaders.cs | 3 +- .../Http2/Http2StreamTests.cs | 2 +- .../Http2/Http2TestBase.cs | 2 +- .../InMemory.FunctionalTests/RequestTests.cs | 2 +- 12 files changed, 9 insertions(+), 86 deletions(-) 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 43940a295202..03886c20400b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -6548,8 +6548,7 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value) } 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)) { - if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector) - || ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector)) + if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)) { AppendContentLength(value); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 8e5d0be62dbd..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.EncodingSelector = ServerOptions.GetRequestHeaderEncodingSelector(); + HttpRequestHeaders.EncodingSelector = ServerOptions.RequestHeaderEncodingSelector; HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse; HttpResponseHeaders.Reset(); RequestHeaders = HttpRequestHeaders; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index 496307f91df2..56235dd2a293 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; using System.Text; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure @@ -554,7 +553,7 @@ private static bool IsHex(char ch) || (uint)((ch | 32) - 'a') < 6u; } - // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) + // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230) private sealed class UTF8EncodingSealed : UTF8Encoding { public UTF8EncodingSealed() : base(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) { } 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/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index d5642d30ddd7..917c1c8a75f8 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -25,7 +25,6 @@ public class KestrelServerOptions { // internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => null; - internal static readonly Func DefaultLatin1RequestHeaderEncodingSelector = _ => Encoding.Latin1; private Func _requestHeaderEncodingSelector = DefaultRequestHeaderEncodingSelector; @@ -127,11 +126,6 @@ public Func RequestHeaderEncodingSelector /// 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. @@ -141,16 +135,6 @@ public void ConfigureEndpointDefaults(Action configureOptions) EndpointDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions)); } - internal Func GetRequestHeaderEncodingSelector() - { - if (ReferenceEquals(_requestHeaderEncodingSelector, DefaultRequestHeaderEncodingSelector) && Latin1RequestHeaders) - { - return DefaultLatin1RequestHeaderEncodingSelector; - } - - return _requestHeaderEncodingSelector; - } - internal void ApplyEndpointDefaults(ListenOptions listenOptions) { listenOptions.KestrelServerOptions = this; diff --git a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs index 33cb1be0439b..93d28dea7495 100644 --- a/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpRequestHeadersTests.cs @@ -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, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector); + 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++) @@ -546,12 +546,7 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH [MemberData(nameof(KnownRequestHeaders))] public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeader header) { - var kso = new KestrelServerOptions - { - Latin1RequestHeaders = useLatin1, - }; - - var headers = new HttpRequestHeaders(encodingSelector: kso.GetRequestHeaderEncodingSelector()); + 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++) diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 29321e616248..fb0b182cdd28 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -588,54 +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); - Assert.Same(KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); - } - - [Fact] - public void Latin1RequestHeadersReadFromConfigCanBeOverriddenBySettingRequestHeaderEncodingSelector() - { - 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); - - Assert.NotSame(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); - - options.RequestHeaderEncodingSelector = _ => Encoding.ASCII; - Assert.Same(options.RequestHeaderEncodingSelector, options.GetRequestHeaderEncodingSelector()); - } - [Fact] public void Reload_IdentifiesEndpointsToStartAndStop() { diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index f3466af3b97e..5295f8ce5b9a 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -236,8 +236,7 @@ string GenerateIfBody(KnownHeader header, string extraIndent = "") if (header.Identifier == "ContentLength") { return $@" - {extraIndent}if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector) - {extraIndent} || ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultLatin1RequestHeaderEncodingSelector)) + {extraIndent}if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)) {extraIndent}{{ {extraIndent} AppendContentLength(value); {extraIndent}}} 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 f3332dbe351f..adae3b99c0bb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -407,7 +407,7 @@ public override void Dispose() void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { var nameStr = name.GetHeaderName(); - _decodedHeaders[nameStr] = value.GetRequestHeaderString(nameStr, _serviceContext.ServerOptions.GetRequestHeaderEncodingSelector()); + _decodedHeaders[nameStr] = value.GetRequestHeaderString(nameStr, _serviceContext.ServerOptions.RequestHeaderEncodingSelector); } void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 7cd2c4222970..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 => {