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 b335d7837694..1b265ee474d7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -9,6 +9,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Microsoft.AspNetCore.Http; @@ -1567,7 +1568,6 @@ StringValues IHeaderDictionary.AcceptRanges set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AcceptRanges, value); } } @@ -1585,7 +1585,6 @@ StringValues IHeaderDictionary.AccessControlAllowCredentials set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlAllowCredentials, value); } } @@ -1603,7 +1602,6 @@ StringValues IHeaderDictionary.AccessControlAllowHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlAllowHeaders, value); } } @@ -1621,7 +1619,6 @@ StringValues IHeaderDictionary.AccessControlAllowMethods set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlAllowMethods, value); } } @@ -1639,7 +1636,6 @@ StringValues IHeaderDictionary.AccessControlAllowOrigin set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlAllowOrigin, value); } } @@ -1657,7 +1653,6 @@ StringValues IHeaderDictionary.AccessControlExposeHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlExposeHeaders, value); } } @@ -1675,7 +1670,6 @@ StringValues IHeaderDictionary.AccessControlMaxAge set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AccessControlMaxAge, value); } } @@ -1693,7 +1687,6 @@ StringValues IHeaderDictionary.Age set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Age, value); } } @@ -1711,7 +1704,6 @@ StringValues IHeaderDictionary.Allow set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Allow, value); } } @@ -1729,7 +1721,6 @@ StringValues IHeaderDictionary.AltSvc set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.AltSvc, value); } } @@ -1747,7 +1738,6 @@ StringValues IHeaderDictionary.ContentDisposition set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentDisposition, value); } } @@ -1765,7 +1755,6 @@ StringValues IHeaderDictionary.ContentEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentEncoding, value); } } @@ -1783,7 +1772,6 @@ StringValues IHeaderDictionary.ContentLanguage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentLanguage, value); } } @@ -1801,7 +1789,6 @@ StringValues IHeaderDictionary.ContentLocation set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentLocation, value); } } @@ -1819,7 +1806,6 @@ StringValues IHeaderDictionary.ContentMD5 set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentMD5, value); } } @@ -1837,7 +1823,6 @@ StringValues IHeaderDictionary.ContentRange set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentRange, value); } } @@ -1855,7 +1840,6 @@ StringValues IHeaderDictionary.ContentSecurityPolicy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentSecurityPolicy, value); } } @@ -1873,7 +1857,6 @@ StringValues IHeaderDictionary.ContentSecurityPolicyReportOnly set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ContentSecurityPolicyReportOnly, value); } } @@ -1891,7 +1874,6 @@ StringValues IHeaderDictionary.ETag set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ETag, value); } } @@ -1909,7 +1891,6 @@ StringValues IHeaderDictionary.Expires set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Expires, value); } } @@ -1927,7 +1908,6 @@ StringValues IHeaderDictionary.GrpcMessage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.GrpcMessage, value); } } @@ -1945,7 +1925,6 @@ StringValues IHeaderDictionary.GrpcStatus set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.GrpcStatus, value); } } @@ -1963,7 +1942,6 @@ StringValues IHeaderDictionary.LastModified set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.LastModified, value); } } @@ -1981,7 +1959,6 @@ StringValues IHeaderDictionary.Link set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Link, value); } } @@ -1999,7 +1976,6 @@ StringValues IHeaderDictionary.Location set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Location, value); } } @@ -2017,7 +1993,6 @@ StringValues IHeaderDictionary.ProxyAuthenticate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ProxyAuthenticate, value); } } @@ -2035,7 +2010,6 @@ StringValues IHeaderDictionary.ProxyConnection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.ProxyConnection, value); } } @@ -2053,7 +2027,6 @@ StringValues IHeaderDictionary.RetryAfter set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.RetryAfter, value); } } @@ -2071,7 +2044,6 @@ StringValues IHeaderDictionary.SecWebSocketAccept set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SecWebSocketAccept, value); } } @@ -2089,7 +2061,6 @@ StringValues IHeaderDictionary.SecWebSocketKey set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SecWebSocketKey, value); } } @@ -2107,7 +2078,6 @@ StringValues IHeaderDictionary.SecWebSocketProtocol set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SecWebSocketProtocol, value); } } @@ -2125,7 +2095,6 @@ StringValues IHeaderDictionary.SecWebSocketVersion set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SecWebSocketVersion, value); } } @@ -2143,7 +2112,6 @@ StringValues IHeaderDictionary.SecWebSocketExtensions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SecWebSocketExtensions, value); } } @@ -2161,7 +2129,6 @@ StringValues IHeaderDictionary.Server set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Server, value); } } @@ -2179,7 +2146,6 @@ StringValues IHeaderDictionary.SetCookie set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.SetCookie, value); } } @@ -2197,7 +2163,6 @@ StringValues IHeaderDictionary.StrictTransportSecurity set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.StrictTransportSecurity, value); } } @@ -2215,7 +2180,6 @@ StringValues IHeaderDictionary.Trailer set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Trailer, value); } } @@ -2233,7 +2197,6 @@ StringValues IHeaderDictionary.Vary set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.Vary, value); } } @@ -2251,7 +2214,6 @@ StringValues IHeaderDictionary.WebSocketSubProtocols set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.WebSocketSubProtocols, value); } } @@ -2269,7 +2231,6 @@ StringValues IHeaderDictionary.WWWAuthenticate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.WWWAuthenticate, value); } } @@ -2287,7 +2248,6 @@ StringValues IHeaderDictionary.XContentTypeOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XContentTypeOptions, value); } } @@ -2305,7 +2265,6 @@ StringValues IHeaderDictionary.XFrameOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XFrameOptions, value); } } @@ -2323,7 +2282,6 @@ StringValues IHeaderDictionary.XPoweredBy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XPoweredBy, value); } } @@ -2341,7 +2299,6 @@ StringValues IHeaderDictionary.XRequestedWith set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XRequestedWith, value); } } @@ -2359,7 +2316,6 @@ StringValues IHeaderDictionary.XUACompatible set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XUACompatible, value); } } @@ -2377,7 +2333,6 @@ StringValues IHeaderDictionary.XXSSProtection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - SetValueUnknown(HeaderNames.XXSSProtection, value); } } @@ -7354,13 +7309,15 @@ 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)) + var customEncoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : EncodingSelector(HeaderNames.ContentLength); + if (customEncoding == null) { AppendContentLength(value); } else { - AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength)); + AppendContentLengthCustomEncoding(value, customEncoding); } return; } @@ -7561,13 +7518,15 @@ public unsafe bool TryHPackAppend(int index, ReadOnlySpan value) nameStr = HeaderNames.CacheControl; break; case 28: - if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)) + var customEncoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : EncodingSelector(HeaderNames.ContentLength); + if (customEncoding == null) { AppendContentLength(value); } else { - AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength)); + AppendContentLengthCustomEncoding(value, customEncoding); } return true; case 31: @@ -8333,6 +8292,7 @@ StringValues IHeaderDictionary.Connection var flag = 0x1L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Connection, value, EncodingSelector); _bits |= flag; _headers._Connection = value; } @@ -8362,6 +8322,7 @@ StringValues IHeaderDictionary.ContentType var flag = 0x2L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentType, value, EncodingSelector); _bits |= flag; _headers._ContentType = value; } @@ -8390,6 +8351,7 @@ StringValues IHeaderDictionary.Date var flag = 0x4L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Date, value, EncodingSelector); _bits |= flag; _headers._Date = value; } @@ -8419,6 +8381,7 @@ StringValues IHeaderDictionary.Server var flag = 0x8L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Server, value, EncodingSelector); _bits |= flag; _headers._Server = value; } @@ -8448,6 +8411,7 @@ StringValues IHeaderDictionary.AcceptRanges var flag = 0x10L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AcceptRanges, value, EncodingSelector); _bits |= flag; _headers._AcceptRanges = value; } @@ -8476,6 +8440,7 @@ StringValues IHeaderDictionary.AccessControlAllowCredentials var flag = 0x20L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowCredentials, value, EncodingSelector); _bits |= flag; _headers._AccessControlAllowCredentials = value; } @@ -8504,6 +8469,7 @@ StringValues IHeaderDictionary.AccessControlAllowHeaders var flag = 0x40L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowHeaders, value, EncodingSelector); _bits |= flag; _headers._AccessControlAllowHeaders = value; } @@ -8532,6 +8498,7 @@ StringValues IHeaderDictionary.AccessControlAllowMethods var flag = 0x80L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowMethods, value, EncodingSelector); _bits |= flag; _headers._AccessControlAllowMethods = value; } @@ -8560,6 +8527,7 @@ StringValues IHeaderDictionary.AccessControlAllowOrigin var flag = 0x100L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowOrigin, value, EncodingSelector); _bits |= flag; _headers._AccessControlAllowOrigin = value; } @@ -8588,6 +8556,7 @@ StringValues IHeaderDictionary.AccessControlExposeHeaders var flag = 0x200L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlExposeHeaders, value, EncodingSelector); _bits |= flag; _headers._AccessControlExposeHeaders = value; } @@ -8616,6 +8585,7 @@ StringValues IHeaderDictionary.AccessControlMaxAge var flag = 0x400L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AccessControlMaxAge, value, EncodingSelector); _bits |= flag; _headers._AccessControlMaxAge = value; } @@ -8644,6 +8614,7 @@ StringValues IHeaderDictionary.Age var flag = 0x800L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Age, value, EncodingSelector); _bits |= flag; _headers._Age = value; } @@ -8672,6 +8643,7 @@ StringValues IHeaderDictionary.Allow var flag = 0x1000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Allow, value, EncodingSelector); _bits |= flag; _headers._Allow = value; } @@ -8700,6 +8672,7 @@ StringValues IHeaderDictionary.AltSvc var flag = 0x2000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.AltSvc, value, EncodingSelector); _bits |= flag; _headers._AltSvc = value; } @@ -8728,6 +8701,7 @@ StringValues IHeaderDictionary.CacheControl var flag = 0x4000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.CacheControl, value, EncodingSelector); _bits |= flag; _headers._CacheControl = value; } @@ -8756,6 +8730,7 @@ StringValues IHeaderDictionary.ContentEncoding var flag = 0x8000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentEncoding, value, EncodingSelector); _bits |= flag; _headers._ContentEncoding = value; } @@ -8784,6 +8759,7 @@ StringValues IHeaderDictionary.ContentLanguage var flag = 0x10000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentLanguage, value, EncodingSelector); _bits |= flag; _headers._ContentLanguage = value; } @@ -8812,6 +8788,7 @@ StringValues IHeaderDictionary.ContentLocation var flag = 0x20000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentLocation, value, EncodingSelector); _bits |= flag; _headers._ContentLocation = value; } @@ -8840,6 +8817,7 @@ StringValues IHeaderDictionary.ContentMD5 var flag = 0x40000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentMD5, value, EncodingSelector); _bits |= flag; _headers._ContentMD5 = value; } @@ -8868,6 +8846,7 @@ StringValues IHeaderDictionary.ContentRange var flag = 0x80000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ContentRange, value, EncodingSelector); _bits |= flag; _headers._ContentRange = value; } @@ -8896,6 +8875,7 @@ StringValues IHeaderDictionary.ETag var flag = 0x100000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ETag, value, EncodingSelector); _bits |= flag; _headers._ETag = value; } @@ -8924,6 +8904,7 @@ StringValues IHeaderDictionary.Expires var flag = 0x200000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Expires, value, EncodingSelector); _bits |= flag; _headers._Expires = value; } @@ -8952,6 +8933,7 @@ StringValues IHeaderDictionary.GrpcEncoding var flag = 0x400000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.GrpcEncoding, value, EncodingSelector); _bits |= flag; _headers._GrpcEncoding = value; } @@ -8980,6 +8962,7 @@ StringValues IHeaderDictionary.KeepAlive var flag = 0x800000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.KeepAlive, value, EncodingSelector); _bits |= flag; _headers._KeepAlive = value; } @@ -9008,6 +8991,7 @@ StringValues IHeaderDictionary.LastModified var flag = 0x1000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.LastModified, value, EncodingSelector); _bits |= flag; _headers._LastModified = value; } @@ -9036,6 +9020,7 @@ StringValues IHeaderDictionary.Location var flag = 0x2000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Location, value, EncodingSelector); _bits |= flag; _headers._Location = value; } @@ -9064,6 +9049,7 @@ StringValues IHeaderDictionary.Pragma var flag = 0x4000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Pragma, value, EncodingSelector); _bits |= flag; _headers._Pragma = value; } @@ -9092,6 +9078,7 @@ StringValues IHeaderDictionary.ProxyAuthenticate var flag = 0x8000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ProxyAuthenticate, value, EncodingSelector); _bits |= flag; _headers._ProxyAuthenticate = value; } @@ -9120,6 +9107,7 @@ StringValues IHeaderDictionary.ProxyConnection var flag = 0x10000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ProxyConnection, value, EncodingSelector); _bits |= flag; _headers._ProxyConnection = value; } @@ -9148,6 +9136,7 @@ StringValues IHeaderDictionary.RetryAfter var flag = 0x20000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.RetryAfter, value, EncodingSelector); _bits |= flag; _headers._RetryAfter = value; } @@ -9176,6 +9165,7 @@ StringValues IHeaderDictionary.SetCookie var flag = 0x40000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.SetCookie, value, EncodingSelector); _bits |= flag; _headers._SetCookie = value; } @@ -9204,6 +9194,7 @@ StringValues IHeaderDictionary.Trailer var flag = 0x80000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Trailer, value, EncodingSelector); _bits |= flag; _headers._Trailer = value; } @@ -9232,6 +9223,7 @@ StringValues IHeaderDictionary.TransferEncoding var flag = 0x100000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.TransferEncoding, value, EncodingSelector); _bits |= flag; _headers._TransferEncoding = value; } @@ -9261,6 +9253,7 @@ StringValues IHeaderDictionary.Upgrade var flag = 0x200000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Upgrade, value, EncodingSelector); _bits |= flag; _headers._Upgrade = value; } @@ -9289,6 +9282,7 @@ StringValues IHeaderDictionary.Vary var flag = 0x400000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Vary, value, EncodingSelector); _bits |= flag; _headers._Vary = value; } @@ -9317,6 +9311,7 @@ StringValues IHeaderDictionary.Via var flag = 0x800000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Via, value, EncodingSelector); _bits |= flag; _headers._Via = value; } @@ -9345,6 +9340,7 @@ StringValues IHeaderDictionary.Warning var flag = 0x1000000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.Warning, value, EncodingSelector); _bits |= flag; _headers._Warning = value; } @@ -9373,6 +9369,7 @@ StringValues IHeaderDictionary.WWWAuthenticate var flag = 0x2000000000L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.WWWAuthenticate, value, EncodingSelector); _bits |= flag; _headers._WWWAuthenticate = value; } @@ -9398,7 +9395,7 @@ StringValues IHeaderDictionary.Accept set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Accept, value, EncodingSelector); SetValueUnknown(HeaderNames.Accept, value); } } @@ -9416,7 +9413,7 @@ StringValues IHeaderDictionary.AcceptCharset set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptCharset, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptCharset, value); } } @@ -9434,7 +9431,7 @@ StringValues IHeaderDictionary.AcceptEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptEncoding, value); } } @@ -9452,7 +9449,7 @@ StringValues IHeaderDictionary.AcceptLanguage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptLanguage, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptLanguage, value); } } @@ -9470,7 +9467,7 @@ StringValues IHeaderDictionary.AccessControlRequestHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlRequestHeaders, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlRequestHeaders, value); } } @@ -9488,7 +9485,7 @@ StringValues IHeaderDictionary.AccessControlRequestMethod set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlRequestMethod, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlRequestMethod, value); } } @@ -9506,7 +9503,7 @@ StringValues IHeaderDictionary.Authorization set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Authorization, value, EncodingSelector); SetValueUnknown(HeaderNames.Authorization, value); } } @@ -9524,7 +9521,7 @@ StringValues IHeaderDictionary.Baggage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Baggage, value, EncodingSelector); SetValueUnknown(HeaderNames.Baggage, value); } } @@ -9542,7 +9539,7 @@ StringValues IHeaderDictionary.ContentDisposition set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentDisposition, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentDisposition, value); } } @@ -9560,7 +9557,7 @@ StringValues IHeaderDictionary.ContentSecurityPolicy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentSecurityPolicy, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentSecurityPolicy, value); } } @@ -9578,7 +9575,7 @@ StringValues IHeaderDictionary.ContentSecurityPolicyReportOnly set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentSecurityPolicyReportOnly, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentSecurityPolicyReportOnly, value); } } @@ -9596,7 +9593,7 @@ StringValues IHeaderDictionary.CorrelationContext set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.CorrelationContext, value, EncodingSelector); SetValueUnknown(HeaderNames.CorrelationContext, value); } } @@ -9614,7 +9611,7 @@ StringValues IHeaderDictionary.Cookie set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Cookie, value, EncodingSelector); SetValueUnknown(HeaderNames.Cookie, value); } } @@ -9632,7 +9629,7 @@ StringValues IHeaderDictionary.Expect set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Expect, value, EncodingSelector); SetValueUnknown(HeaderNames.Expect, value); } } @@ -9650,7 +9647,7 @@ StringValues IHeaderDictionary.From set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.From, value, EncodingSelector); SetValueUnknown(HeaderNames.From, value); } } @@ -9668,7 +9665,7 @@ StringValues IHeaderDictionary.GrpcAcceptEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcAcceptEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcAcceptEncoding, value); } } @@ -9686,7 +9683,7 @@ StringValues IHeaderDictionary.GrpcMessage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcMessage, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcMessage, value); } } @@ -9704,7 +9701,7 @@ StringValues IHeaderDictionary.GrpcStatus set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcStatus, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcStatus, value); } } @@ -9722,7 +9719,7 @@ StringValues IHeaderDictionary.GrpcTimeout set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcTimeout, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcTimeout, value); } } @@ -9740,7 +9737,7 @@ StringValues IHeaderDictionary.Host set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Host, value, EncodingSelector); SetValueUnknown(HeaderNames.Host, value); } } @@ -9758,7 +9755,7 @@ StringValues IHeaderDictionary.IfMatch set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfMatch, value, EncodingSelector); SetValueUnknown(HeaderNames.IfMatch, value); } } @@ -9776,7 +9773,7 @@ StringValues IHeaderDictionary.IfModifiedSince set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfModifiedSince, value, EncodingSelector); SetValueUnknown(HeaderNames.IfModifiedSince, value); } } @@ -9794,7 +9791,7 @@ StringValues IHeaderDictionary.IfNoneMatch set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfNoneMatch, value, EncodingSelector); SetValueUnknown(HeaderNames.IfNoneMatch, value); } } @@ -9812,7 +9809,7 @@ StringValues IHeaderDictionary.IfRange set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfRange, value, EncodingSelector); SetValueUnknown(HeaderNames.IfRange, value); } } @@ -9830,7 +9827,7 @@ StringValues IHeaderDictionary.IfUnmodifiedSince set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfUnmodifiedSince, value, EncodingSelector); SetValueUnknown(HeaderNames.IfUnmodifiedSince, value); } } @@ -9848,7 +9845,7 @@ StringValues IHeaderDictionary.Link set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Link, value, EncodingSelector); SetValueUnknown(HeaderNames.Link, value); } } @@ -9866,7 +9863,7 @@ StringValues IHeaderDictionary.MaxForwards set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.MaxForwards, value, EncodingSelector); SetValueUnknown(HeaderNames.MaxForwards, value); } } @@ -9884,7 +9881,7 @@ StringValues IHeaderDictionary.Origin set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Origin, value, EncodingSelector); SetValueUnknown(HeaderNames.Origin, value); } } @@ -9902,7 +9899,7 @@ StringValues IHeaderDictionary.ProxyAuthorization set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ProxyAuthorization, value, EncodingSelector); SetValueUnknown(HeaderNames.ProxyAuthorization, value); } } @@ -9920,7 +9917,7 @@ StringValues IHeaderDictionary.Range set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Range, value, EncodingSelector); SetValueUnknown(HeaderNames.Range, value); } } @@ -9938,7 +9935,7 @@ StringValues IHeaderDictionary.Referer set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Referer, value, EncodingSelector); SetValueUnknown(HeaderNames.Referer, value); } } @@ -9956,7 +9953,7 @@ StringValues IHeaderDictionary.RequestId set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.RequestId, value, EncodingSelector); SetValueUnknown(HeaderNames.RequestId, value); } } @@ -9974,7 +9971,7 @@ StringValues IHeaderDictionary.SecWebSocketAccept set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketAccept, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketAccept, value); } } @@ -9992,7 +9989,7 @@ StringValues IHeaderDictionary.SecWebSocketKey set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketKey, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketKey, value); } } @@ -10010,7 +10007,7 @@ StringValues IHeaderDictionary.SecWebSocketProtocol set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketProtocol, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketProtocol, value); } } @@ -10028,7 +10025,7 @@ StringValues IHeaderDictionary.SecWebSocketVersion set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketVersion, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketVersion, value); } } @@ -10046,7 +10043,7 @@ StringValues IHeaderDictionary.SecWebSocketExtensions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketExtensions, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketExtensions, value); } } @@ -10064,7 +10061,7 @@ StringValues IHeaderDictionary.StrictTransportSecurity set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.StrictTransportSecurity, value, EncodingSelector); SetValueUnknown(HeaderNames.StrictTransportSecurity, value); } } @@ -10082,7 +10079,7 @@ StringValues IHeaderDictionary.TE set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TE, value, EncodingSelector); SetValueUnknown(HeaderNames.TE, value); } } @@ -10100,7 +10097,7 @@ StringValues IHeaderDictionary.Translate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Translate, value, EncodingSelector); SetValueUnknown(HeaderNames.Translate, value); } } @@ -10118,7 +10115,7 @@ StringValues IHeaderDictionary.TraceParent set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TraceParent, value, EncodingSelector); SetValueUnknown(HeaderNames.TraceParent, value); } } @@ -10136,7 +10133,7 @@ StringValues IHeaderDictionary.TraceState set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TraceState, value, EncodingSelector); SetValueUnknown(HeaderNames.TraceState, value); } } @@ -10154,7 +10151,7 @@ StringValues IHeaderDictionary.UpgradeInsecureRequests set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.UpgradeInsecureRequests, value, EncodingSelector); SetValueUnknown(HeaderNames.UpgradeInsecureRequests, value); } } @@ -10172,7 +10169,7 @@ StringValues IHeaderDictionary.UserAgent set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.UserAgent, value, EncodingSelector); SetValueUnknown(HeaderNames.UserAgent, value); } } @@ -10190,7 +10187,7 @@ StringValues IHeaderDictionary.WebSocketSubProtocols set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.WebSocketSubProtocols, value, EncodingSelector); SetValueUnknown(HeaderNames.WebSocketSubProtocols, value); } } @@ -10208,7 +10205,7 @@ StringValues IHeaderDictionary.XContentTypeOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XContentTypeOptions, value, EncodingSelector); SetValueUnknown(HeaderNames.XContentTypeOptions, value); } } @@ -10226,7 +10223,7 @@ StringValues IHeaderDictionary.XFrameOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XFrameOptions, value, EncodingSelector); SetValueUnknown(HeaderNames.XFrameOptions, value); } } @@ -10244,7 +10241,7 @@ StringValues IHeaderDictionary.XPoweredBy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XPoweredBy, value, EncodingSelector); SetValueUnknown(HeaderNames.XPoweredBy, value); } } @@ -10262,7 +10259,7 @@ StringValues IHeaderDictionary.XRequestedWith set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XRequestedWith, value, EncodingSelector); SetValueUnknown(HeaderNames.XRequestedWith, value); } } @@ -10280,7 +10277,7 @@ StringValues IHeaderDictionary.XUACompatible set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XUACompatible, value, EncodingSelector); SetValueUnknown(HeaderNames.XUACompatible, value); } } @@ -10298,7 +10295,7 @@ StringValues IHeaderDictionary.XXSSProtection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XXSSProtection, value, EncodingSelector); SetValueUnknown(HeaderNames.XXSSProtection, value); } } @@ -11141,7 +11138,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) protected override void SetValueFast(string key, StringValues value) { - ValidateHeaderValueCharacters(value); + ValidateHeaderValueCharacters(key, value, EncodingSelector); switch (key.Length) { case 3: @@ -11720,7 +11717,7 @@ protected override void SetValueFast(string key, StringValues value) protected override bool AddValueFast(string key, StringValues value) { - ValidateHeaderValueCharacters(value); + ValidateHeaderValueCharacters(key, value, EncodingSelector); switch (key.Length) { case 3: @@ -14282,6 +14279,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) { int keyStart; int keyLength; + var headerName = string.Empty; switch (next) { case 0: // Header: "Connection" @@ -14299,6 +14297,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) values = ref _headers._Connection; keyStart = 0; keyLength = 14; + headerName = HeaderNames.Connection; } break; // OutputHeader @@ -14324,6 +14323,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) values = ref _headers._Date; keyStart = 30; keyLength = 8; + headerName = HeaderNames.Date; } break; // OutputHeader @@ -14342,6 +14342,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) values = ref _headers._Server; keyStart = 38; keyLength = 10; + headerName = HeaderNames.Server; } break; // OutputHeader @@ -14556,6 +14557,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) values = ref _headers._TransferEncoding; keyStart = 562; keyLength = 21; + headerName = HeaderNames.TransferEncoding; } break; // OutputHeader @@ -14603,6 +14605,8 @@ internal unsafe void CopyToFast(ref BufferWriter output) { // Clear bit tempBits ^= (1UL << next); + var encoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : EncodingSelector(headerName); var valueCount = values.Count; Debug.Assert(valueCount > 0); @@ -14613,7 +14617,14 @@ internal unsafe void CopyToFast(ref BufferWriter output) if (value != null) { output.Write(headerKey); - output.WriteAscii(value); + if (encoding is null) + { + output.WriteAscii(value); + } + else + { + output.WriteEncoded(value, encoding); + } } } // Set exact next @@ -15113,6 +15124,7 @@ StringValues IHeaderDictionary.ETag var flag = 0x1L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.ETag, value, EncodingSelector); _bits |= flag; _headers._ETag = value; } @@ -15141,6 +15153,7 @@ StringValues IHeaderDictionary.GrpcMessage var flag = 0x2L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.GrpcMessage, value, EncodingSelector); _bits |= flag; _headers._GrpcMessage = value; } @@ -15169,6 +15182,7 @@ StringValues IHeaderDictionary.GrpcStatus var flag = 0x4L; if (value.Count > 0) { + ValidateHeaderValueCharacters(HeaderNames.GrpcStatus, value, EncodingSelector); _bits |= flag; _headers._GrpcStatus = value; } @@ -15194,7 +15208,7 @@ StringValues IHeaderDictionary.Accept set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Accept, value, EncodingSelector); SetValueUnknown(HeaderNames.Accept, value); } } @@ -15212,7 +15226,7 @@ StringValues IHeaderDictionary.AcceptCharset set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptCharset, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptCharset, value); } } @@ -15230,7 +15244,7 @@ StringValues IHeaderDictionary.AcceptEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptEncoding, value); } } @@ -15248,7 +15262,7 @@ StringValues IHeaderDictionary.AcceptLanguage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptLanguage, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptLanguage, value); } } @@ -15266,7 +15280,7 @@ StringValues IHeaderDictionary.AcceptRanges set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AcceptRanges, value, EncodingSelector); SetValueUnknown(HeaderNames.AcceptRanges, value); } } @@ -15284,7 +15298,7 @@ StringValues IHeaderDictionary.AccessControlAllowCredentials set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowCredentials, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlAllowCredentials, value); } } @@ -15302,7 +15316,7 @@ StringValues IHeaderDictionary.AccessControlAllowHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowHeaders, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlAllowHeaders, value); } } @@ -15320,7 +15334,7 @@ StringValues IHeaderDictionary.AccessControlAllowMethods set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowMethods, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlAllowMethods, value); } } @@ -15338,7 +15352,7 @@ StringValues IHeaderDictionary.AccessControlAllowOrigin set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlAllowOrigin, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlAllowOrigin, value); } } @@ -15356,7 +15370,7 @@ StringValues IHeaderDictionary.AccessControlExposeHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlExposeHeaders, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlExposeHeaders, value); } } @@ -15374,7 +15388,7 @@ StringValues IHeaderDictionary.AccessControlMaxAge set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlMaxAge, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlMaxAge, value); } } @@ -15392,7 +15406,7 @@ StringValues IHeaderDictionary.AccessControlRequestHeaders set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlRequestHeaders, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlRequestHeaders, value); } } @@ -15410,7 +15424,7 @@ StringValues IHeaderDictionary.AccessControlRequestMethod set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AccessControlRequestMethod, value, EncodingSelector); SetValueUnknown(HeaderNames.AccessControlRequestMethod, value); } } @@ -15428,7 +15442,7 @@ StringValues IHeaderDictionary.Age set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Age, value, EncodingSelector); SetValueUnknown(HeaderNames.Age, value); } } @@ -15446,7 +15460,7 @@ StringValues IHeaderDictionary.Allow set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Allow, value, EncodingSelector); SetValueUnknown(HeaderNames.Allow, value); } } @@ -15464,7 +15478,7 @@ StringValues IHeaderDictionary.AltSvc set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.AltSvc, value, EncodingSelector); SetValueUnknown(HeaderNames.AltSvc, value); } } @@ -15482,7 +15496,7 @@ StringValues IHeaderDictionary.Authorization set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Authorization, value, EncodingSelector); SetValueUnknown(HeaderNames.Authorization, value); } } @@ -15500,7 +15514,7 @@ StringValues IHeaderDictionary.Baggage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Baggage, value, EncodingSelector); SetValueUnknown(HeaderNames.Baggage, value); } } @@ -15518,7 +15532,7 @@ StringValues IHeaderDictionary.CacheControl set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.CacheControl, value, EncodingSelector); SetValueUnknown(HeaderNames.CacheControl, value); } } @@ -15536,7 +15550,7 @@ StringValues IHeaderDictionary.Connection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Connection, value, EncodingSelector); SetValueUnknown(HeaderNames.Connection, value); } } @@ -15554,7 +15568,7 @@ StringValues IHeaderDictionary.ContentDisposition set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentDisposition, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentDisposition, value); } } @@ -15572,7 +15586,7 @@ StringValues IHeaderDictionary.ContentEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentEncoding, value); } } @@ -15590,7 +15604,7 @@ StringValues IHeaderDictionary.ContentLanguage set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentLanguage, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentLanguage, value); } } @@ -15608,7 +15622,7 @@ StringValues IHeaderDictionary.ContentLocation set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentLocation, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentLocation, value); } } @@ -15626,7 +15640,7 @@ StringValues IHeaderDictionary.ContentMD5 set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentMD5, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentMD5, value); } } @@ -15644,7 +15658,7 @@ StringValues IHeaderDictionary.ContentRange set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentRange, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentRange, value); } } @@ -15662,7 +15676,7 @@ StringValues IHeaderDictionary.ContentSecurityPolicy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentSecurityPolicy, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentSecurityPolicy, value); } } @@ -15680,7 +15694,7 @@ StringValues IHeaderDictionary.ContentSecurityPolicyReportOnly set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentSecurityPolicyReportOnly, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentSecurityPolicyReportOnly, value); } } @@ -15698,7 +15712,7 @@ StringValues IHeaderDictionary.ContentType set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ContentType, value, EncodingSelector); SetValueUnknown(HeaderNames.ContentType, value); } } @@ -15716,7 +15730,7 @@ StringValues IHeaderDictionary.CorrelationContext set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.CorrelationContext, value, EncodingSelector); SetValueUnknown(HeaderNames.CorrelationContext, value); } } @@ -15734,7 +15748,7 @@ StringValues IHeaderDictionary.Cookie set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Cookie, value, EncodingSelector); SetValueUnknown(HeaderNames.Cookie, value); } } @@ -15752,7 +15766,7 @@ StringValues IHeaderDictionary.Date set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Date, value, EncodingSelector); SetValueUnknown(HeaderNames.Date, value); } } @@ -15770,7 +15784,7 @@ StringValues IHeaderDictionary.Expires set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Expires, value, EncodingSelector); SetValueUnknown(HeaderNames.Expires, value); } } @@ -15788,7 +15802,7 @@ StringValues IHeaderDictionary.Expect set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Expect, value, EncodingSelector); SetValueUnknown(HeaderNames.Expect, value); } } @@ -15806,7 +15820,7 @@ StringValues IHeaderDictionary.From set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.From, value, EncodingSelector); SetValueUnknown(HeaderNames.From, value); } } @@ -15824,7 +15838,7 @@ StringValues IHeaderDictionary.GrpcAcceptEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcAcceptEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcAcceptEncoding, value); } } @@ -15842,7 +15856,7 @@ StringValues IHeaderDictionary.GrpcEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcEncoding, value); } } @@ -15860,7 +15874,7 @@ StringValues IHeaderDictionary.GrpcTimeout set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.GrpcTimeout, value, EncodingSelector); SetValueUnknown(HeaderNames.GrpcTimeout, value); } } @@ -15878,7 +15892,7 @@ StringValues IHeaderDictionary.Host set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Host, value, EncodingSelector); SetValueUnknown(HeaderNames.Host, value); } } @@ -15896,7 +15910,7 @@ StringValues IHeaderDictionary.KeepAlive set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.KeepAlive, value, EncodingSelector); SetValueUnknown(HeaderNames.KeepAlive, value); } } @@ -15914,7 +15928,7 @@ StringValues IHeaderDictionary.IfMatch set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfMatch, value, EncodingSelector); SetValueUnknown(HeaderNames.IfMatch, value); } } @@ -15932,7 +15946,7 @@ StringValues IHeaderDictionary.IfModifiedSince set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfModifiedSince, value, EncodingSelector); SetValueUnknown(HeaderNames.IfModifiedSince, value); } } @@ -15950,7 +15964,7 @@ StringValues IHeaderDictionary.IfNoneMatch set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfNoneMatch, value, EncodingSelector); SetValueUnknown(HeaderNames.IfNoneMatch, value); } } @@ -15968,7 +15982,7 @@ StringValues IHeaderDictionary.IfRange set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfRange, value, EncodingSelector); SetValueUnknown(HeaderNames.IfRange, value); } } @@ -15986,7 +16000,7 @@ StringValues IHeaderDictionary.IfUnmodifiedSince set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.IfUnmodifiedSince, value, EncodingSelector); SetValueUnknown(HeaderNames.IfUnmodifiedSince, value); } } @@ -16004,7 +16018,7 @@ StringValues IHeaderDictionary.LastModified set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.LastModified, value, EncodingSelector); SetValueUnknown(HeaderNames.LastModified, value); } } @@ -16022,7 +16036,7 @@ StringValues IHeaderDictionary.Link set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Link, value, EncodingSelector); SetValueUnknown(HeaderNames.Link, value); } } @@ -16040,7 +16054,7 @@ StringValues IHeaderDictionary.Location set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Location, value, EncodingSelector); SetValueUnknown(HeaderNames.Location, value); } } @@ -16058,7 +16072,7 @@ StringValues IHeaderDictionary.MaxForwards set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.MaxForwards, value, EncodingSelector); SetValueUnknown(HeaderNames.MaxForwards, value); } } @@ -16076,7 +16090,7 @@ StringValues IHeaderDictionary.Origin set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Origin, value, EncodingSelector); SetValueUnknown(HeaderNames.Origin, value); } } @@ -16094,7 +16108,7 @@ StringValues IHeaderDictionary.Pragma set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Pragma, value, EncodingSelector); SetValueUnknown(HeaderNames.Pragma, value); } } @@ -16112,7 +16126,7 @@ StringValues IHeaderDictionary.ProxyAuthenticate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ProxyAuthenticate, value, EncodingSelector); SetValueUnknown(HeaderNames.ProxyAuthenticate, value); } } @@ -16130,7 +16144,7 @@ StringValues IHeaderDictionary.ProxyAuthorization set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ProxyAuthorization, value, EncodingSelector); SetValueUnknown(HeaderNames.ProxyAuthorization, value); } } @@ -16148,7 +16162,7 @@ StringValues IHeaderDictionary.ProxyConnection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.ProxyConnection, value, EncodingSelector); SetValueUnknown(HeaderNames.ProxyConnection, value); } } @@ -16166,7 +16180,7 @@ StringValues IHeaderDictionary.Range set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Range, value, EncodingSelector); SetValueUnknown(HeaderNames.Range, value); } } @@ -16184,7 +16198,7 @@ StringValues IHeaderDictionary.Referer set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Referer, value, EncodingSelector); SetValueUnknown(HeaderNames.Referer, value); } } @@ -16202,7 +16216,7 @@ StringValues IHeaderDictionary.RetryAfter set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.RetryAfter, value, EncodingSelector); SetValueUnknown(HeaderNames.RetryAfter, value); } } @@ -16220,7 +16234,7 @@ StringValues IHeaderDictionary.RequestId set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.RequestId, value, EncodingSelector); SetValueUnknown(HeaderNames.RequestId, value); } } @@ -16238,7 +16252,7 @@ StringValues IHeaderDictionary.SecWebSocketAccept set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketAccept, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketAccept, value); } } @@ -16256,7 +16270,7 @@ StringValues IHeaderDictionary.SecWebSocketKey set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketKey, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketKey, value); } } @@ -16274,7 +16288,7 @@ StringValues IHeaderDictionary.SecWebSocketProtocol set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketProtocol, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketProtocol, value); } } @@ -16292,7 +16306,7 @@ StringValues IHeaderDictionary.SecWebSocketVersion set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketVersion, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketVersion, value); } } @@ -16310,7 +16324,7 @@ StringValues IHeaderDictionary.SecWebSocketExtensions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SecWebSocketExtensions, value, EncodingSelector); SetValueUnknown(HeaderNames.SecWebSocketExtensions, value); } } @@ -16328,7 +16342,7 @@ StringValues IHeaderDictionary.Server set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Server, value, EncodingSelector); SetValueUnknown(HeaderNames.Server, value); } } @@ -16346,7 +16360,7 @@ StringValues IHeaderDictionary.SetCookie set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.SetCookie, value, EncodingSelector); SetValueUnknown(HeaderNames.SetCookie, value); } } @@ -16364,7 +16378,7 @@ StringValues IHeaderDictionary.StrictTransportSecurity set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.StrictTransportSecurity, value, EncodingSelector); SetValueUnknown(HeaderNames.StrictTransportSecurity, value); } } @@ -16382,7 +16396,7 @@ StringValues IHeaderDictionary.TE set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TE, value, EncodingSelector); SetValueUnknown(HeaderNames.TE, value); } } @@ -16400,7 +16414,7 @@ StringValues IHeaderDictionary.Trailer set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Trailer, value, EncodingSelector); SetValueUnknown(HeaderNames.Trailer, value); } } @@ -16418,7 +16432,7 @@ StringValues IHeaderDictionary.TransferEncoding set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TransferEncoding, value, EncodingSelector); SetValueUnknown(HeaderNames.TransferEncoding, value); } } @@ -16436,7 +16450,7 @@ StringValues IHeaderDictionary.Translate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Translate, value, EncodingSelector); SetValueUnknown(HeaderNames.Translate, value); } } @@ -16454,7 +16468,7 @@ StringValues IHeaderDictionary.TraceParent set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TraceParent, value, EncodingSelector); SetValueUnknown(HeaderNames.TraceParent, value); } } @@ -16472,7 +16486,7 @@ StringValues IHeaderDictionary.TraceState set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.TraceState, value, EncodingSelector); SetValueUnknown(HeaderNames.TraceState, value); } } @@ -16490,7 +16504,7 @@ StringValues IHeaderDictionary.Upgrade set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Upgrade, value, EncodingSelector); SetValueUnknown(HeaderNames.Upgrade, value); } } @@ -16508,7 +16522,7 @@ StringValues IHeaderDictionary.UpgradeInsecureRequests set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.UpgradeInsecureRequests, value, EncodingSelector); SetValueUnknown(HeaderNames.UpgradeInsecureRequests, value); } } @@ -16526,7 +16540,7 @@ StringValues IHeaderDictionary.UserAgent set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.UserAgent, value, EncodingSelector); SetValueUnknown(HeaderNames.UserAgent, value); } } @@ -16544,7 +16558,7 @@ StringValues IHeaderDictionary.Vary set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Vary, value, EncodingSelector); SetValueUnknown(HeaderNames.Vary, value); } } @@ -16562,7 +16576,7 @@ StringValues IHeaderDictionary.Via set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Via, value, EncodingSelector); SetValueUnknown(HeaderNames.Via, value); } } @@ -16580,7 +16594,7 @@ StringValues IHeaderDictionary.Warning set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.Warning, value, EncodingSelector); SetValueUnknown(HeaderNames.Warning, value); } } @@ -16598,7 +16612,7 @@ StringValues IHeaderDictionary.WebSocketSubProtocols set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.WebSocketSubProtocols, value, EncodingSelector); SetValueUnknown(HeaderNames.WebSocketSubProtocols, value); } } @@ -16616,7 +16630,7 @@ StringValues IHeaderDictionary.WWWAuthenticate set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.WWWAuthenticate, value, EncodingSelector); SetValueUnknown(HeaderNames.WWWAuthenticate, value); } } @@ -16634,7 +16648,7 @@ StringValues IHeaderDictionary.XContentTypeOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XContentTypeOptions, value, EncodingSelector); SetValueUnknown(HeaderNames.XContentTypeOptions, value); } } @@ -16652,7 +16666,7 @@ StringValues IHeaderDictionary.XFrameOptions set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XFrameOptions, value, EncodingSelector); SetValueUnknown(HeaderNames.XFrameOptions, value); } } @@ -16670,7 +16684,7 @@ StringValues IHeaderDictionary.XPoweredBy set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XPoweredBy, value, EncodingSelector); SetValueUnknown(HeaderNames.XPoweredBy, value); } } @@ -16688,7 +16702,7 @@ StringValues IHeaderDictionary.XRequestedWith set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XRequestedWith, value, EncodingSelector); SetValueUnknown(HeaderNames.XRequestedWith, value); } } @@ -16706,7 +16720,7 @@ StringValues IHeaderDictionary.XUACompatible set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XUACompatible, value, EncodingSelector); SetValueUnknown(HeaderNames.XUACompatible, value); } } @@ -16724,7 +16738,7 @@ StringValues IHeaderDictionary.XXSSProtection set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } - + ValidateHeaderValueCharacters(HeaderNames.XXSSProtection, value, EncodingSelector); SetValueUnknown(HeaderNames.XXSSProtection, value); } } @@ -16815,7 +16829,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) protected override void SetValueFast(string key, StringValues value) { - ValidateHeaderValueCharacters(value); + ValidateHeaderValueCharacters(key, value, EncodingSelector); switch (key.Length) { case 4: @@ -16876,7 +16890,7 @@ protected override void SetValueFast(string key, StringValues value) protected override bool AddValueFast(string key, StringValues value) { - ValidateHeaderValueCharacters(value); + ValidateHeaderValueCharacters(key, value, EncodingSelector); switch (key.Length) { case 4: diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs index cd1b711d63a6..e27b38651920 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Primitives; @@ -260,21 +261,25 @@ bool IDictionary.TryGetValue(string key, out StringValues return TryGetValueFast(key, out value); } - public static void ValidateHeaderValueCharacters(StringValues headerValues) + public static void ValidateHeaderValueCharacters(string headerName, StringValues headerValues, Func encodingSelector) { + var requireAscii = ReferenceEquals(encodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + || encodingSelector(headerName) == null; + var count = headerValues.Count; for (var i = 0; i < count; i++) { - ValidateHeaderValueCharacters(headerValues[i]); + ValidateHeaderValueCharacters(headerValues[i], requireAscii); } } - public static void ValidateHeaderValueCharacters(string headerCharacters) + public static void ValidateHeaderValueCharacters(string headerCharacters, bool requireAscii) { if (headerCharacters != null) { - var invalid = HttpCharacters.IndexOfInvalidFieldValueChar(headerCharacters); + var invalid = requireAscii ? HttpCharacters.IndexOfInvalidFieldValueChar(headerCharacters) + : HttpCharacters.IndexOfInvalidFieldValueCharExtended(headerCharacters); if (invalid >= 0) { ThrowInvalidHeaderCharacter(headerCharacters[invalid]); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 99be0b19494a..d163b737b1cb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -374,6 +374,7 @@ public void Reset() HttpRequestHeaders.EncodingSelector = ServerOptions.RequestHeaderEncodingSelector; HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse; HttpResponseHeaders.Reset(); + HttpResponseHeaders.EncodingSelector = ServerOptions.ResponseHeaderEncodingSelector; RequestHeaders = HttpRequestHeaders; ResponseHeaders = HttpResponseHeaders; RequestTrailers.Clear(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs index b9289e36807b..bf159d0f7b04 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs @@ -24,7 +24,7 @@ internal sealed partial class HttpRequestHeaders : HttpHeaders public HttpRequestHeaders(bool reuseHeaderValues = true, Func? encodingSelector = null) { ReuseHeaderValues = reuseHeaderValues; - EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultRequestHeaderEncodingSelector; + EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultHeaderEncodingSelector; } public void OnHeadersComplete() @@ -97,7 +97,7 @@ private void AppendContentLength(ReadOnlySpan value) [MethodImpl(MethodImplOptions.NoInlining)] [SkipLocalsInit] - private void AppendContentLengthCustomEncoding(ReadOnlySpan value, Encoding? customEncoding) + private void AppendContentLengthCustomEncoding(ReadOnlySpan value, Encoding customEncoding) { if (_contentLength.HasValue) { @@ -106,7 +106,7 @@ private void AppendContentLengthCustomEncoding(ReadOnlySpan value, Encodin // long.MaxValue = 9223372036854775807 (19 chars) Span decodedChars = stackalloc char[20]; - var numChars = customEncoding!.GetChars(value, decodedChars); + var numChars = customEncoding.GetChars(value, decodedChars); long parsed = -1; if (numChars > 19 || diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs index ae51e3c535e9..0e7b2e70f8c2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs @@ -4,10 +4,11 @@ using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; -using System.IO.Pipelines; using System.Collections; using System.Collections.Generic; +using System.IO.Pipelines; using System.Runtime.CompilerServices; +using System.Text; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -19,6 +20,13 @@ internal sealed partial class HttpResponseHeaders : HttpHeaders private static ReadOnlySpan CrLf => new[] { (byte)'\r', (byte)'\n' }; private static ReadOnlySpan ColonSpace => new[] { (byte)':', (byte)' ' }; + public Func EncodingSelector { get; set; } + + public HttpResponseHeaders(Func? encodingSelector = null) + { + EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultHeaderEncodingSelector; + } + public Enumerator GetEnumerator() { return new Enumerator(this); @@ -34,10 +42,18 @@ internal void CopyTo(ref BufferWriter buffer) CopyToFast(ref buffer); var extraHeaders = MaybeUnknown; + // Only reserve stack space for the enumerators if there are extra headers if (extraHeaders != null && extraHeaders.Count > 0) { - // Only reserve stack space for the enumartors if there are extra headers - CopyExtraHeaders(ref buffer, extraHeaders); + var encodingSelector = EncodingSelector; + if (ReferenceEquals(encodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)) + { + CopyExtraHeaders(ref buffer, extraHeaders); + } + else + { + CopyExtraHeadersCustomEncoding(ref buffer, extraHeaders, encodingSelector); + } } static void CopyExtraHeaders(ref BufferWriter buffer, Dictionary headers) @@ -56,6 +72,33 @@ static void CopyExtraHeaders(ref BufferWriter buffer, Dictionary buffer, Dictionary headers, + Func encodingSelector) + { + foreach (var kv in headers) + { + var encoding = encodingSelector(kv.Key); + foreach (var value in kv.Value) + { + if (value != null) + { + buffer.Write(CrLf); + buffer.WriteAscii(kv.Key); + buffer.Write(ColonSpace); + + if (encoding is null) + { + buffer.WriteAscii(value); + } + else + { + buffer.WriteEncoded(value, encoding); + } + } + } + } + } } private static long ParseContentLength(string value) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs index 3fb3cef5e385..4bb97f7e11b5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs @@ -5,12 +5,20 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Text; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal partial class HttpResponseTrailers : HttpHeaders { + public Func EncodingSelector { get; set; } + + public HttpResponseTrailers(Func? encodingSelector = null) + { + EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultHeaderEncodingSelector; + } + public Enumerator GetEnumerator() { return new Enumerator(this); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs index c57f1e28f518..1267e26326ab 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs @@ -87,7 +87,7 @@ private static bool EncodeStatusHeader(int statusCode, DynamicHPackEncoder hpack default: const string name = ":status"; var value = StatusCodes.ToStatusString(statusCode); - return hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length); + return hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, valueEncoding: null, out length); } } @@ -99,6 +99,9 @@ private static bool EncodeHeadersCore(DynamicHPackEncoder hpackEncoder, Http2Hea var staticTableId = headersEnumerator.HPackStaticTableId; var name = headersEnumerator.Current.Key; var value = headersEnumerator.Current.Value; + var valueEncoding = + ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : headersEnumerator.EncodingSelector(name); var hint = ResolveHeaderEncodingHint(staticTableId, name); @@ -108,6 +111,7 @@ private static bool EncodeHeadersCore(DynamicHPackEncoder hpackEncoder, Http2Hea hint, name, value, + valueEncoding, out var headerLength)) { // If the header wasn't written, and no headers have been written, then the header is too large. diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 89d23dd5edf5..bb5baf254e96 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -189,11 +189,13 @@ public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrame var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength); FinishWritingHeaders(streamId, payloadLength, done); } - catch (HPackEncodingException hex) + // Any exception from the HPack encoder can leave the dynamic table in a corrupt state. + // Since we allow custom header encoders we don't know what type of exceptions to expect. + catch (Exception ex) { - _log.HPackEncodingError(_connectionId, streamId, hex); - _http2Connection.Abort(new ConnectionAbortedException(hex.Message, hex)); - throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write. + _log.HPackEncodingError(_connectionId, streamId, ex); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex)); + throw new InvalidOperationException(ex.Message, ex); // Report the error to the user if this was the first write. } } } @@ -215,10 +217,12 @@ public ValueTask WriteResponseTrailersAsync(int streamId, HttpRespo var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength); FinishWritingHeaders(streamId, payloadLength, done); } - catch (HPackEncodingException hex) + // Any exception from the HPack encoder can leave the dynamic table in a corrupt state. + // Since we allow custom header encoders we don't know what type of exceptions to expect. + catch (Exception ex) { - _log.HPackEncodingError(_connectionId, streamId, hex); - _http2Connection.Abort(new ConnectionAbortedException(hex.Message, hex)); + _log.HPackEncodingError(_connectionId, streamId, ex); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex)); } return TimeFlushUnsynchronizedAsync(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs similarity index 96% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs rename to src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs index c77836ab4c41..19b7399f28ff 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs @@ -1,9 +1,11 @@ // 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.Collections; using System.Collections.Generic; using System.Net.Http.HPack; +using System.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.Extensions.Primitives; @@ -25,16 +27,15 @@ private enum HeadersType : byte private bool _hasMultipleValues; private KnownHeaderType _knownHeaderType; + public Func EncodingSelector { get; set; } = KestrelServerOptions.DefaultHeaderEncodingSelector; + public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType); public KeyValuePair Current { get; private set; } object IEnumerator.Current => Current; - public Http2HeadersEnumerator() - { - } - public void Initialize(HttpResponseHeaders headers) { + EncodingSelector = headers.EncodingSelector; _headersEnumerator = headers.GetEnumerator(); _headersType = HeadersType.Headers; _hasMultipleValues = false; @@ -42,6 +43,7 @@ public void Initialize(HttpResponseHeaders headers) public void Initialize(HttpResponseTrailers headers) { + EncodingSelector = headers.EncodingSelector; _trailersEnumerator = headers.GetEnumerator(); _headersType = HeadersType.Trailers; _hasMultipleValues = false; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.FeatureCollection.cs index c848c9e46d5e..c38b525462f5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.FeatureCollection.cs @@ -25,7 +25,7 @@ IHeaderDictionary IHttpResponseTrailersFeature.Trailers { if (ResponseTrailers == null) { - ResponseTrailers = new HttpResponseTrailers(); + ResponseTrailers = new HttpResponseTrailers(ServerOptions.ResponseHeaderEncodingSelector); if (HasResponseCompleted) { ResponseTrailers.SetReadOnly(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs index a7f40e0fbe1e..1584d9af16d4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -8,6 +8,7 @@ using System.IO.Pipelines; using System.Net.Http; using System.Net.Http.QPack; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -44,7 +45,7 @@ internal class Http3FrameWriter // Write headers to a buffer that can grow. Possible performance improvement // by writing directly to output writer (difficult as frame length is prefixed). private readonly ArrayBufferWriter _headerEncodingBuffer; - private IEnumerator>? _headersEnumerator; + private Http3HeadersEnumerator _headersEnumerator = new(); private int _headersTotalSize; private long _unflushedBytes; @@ -271,7 +272,7 @@ public ValueTask WriteResponseTrailersAsync(long streamId, HttpResp try { - _headersEnumerator = EnumerateHeaders(headers).GetEnumerator(); + _headersEnumerator.Initialize(headers); _headersTotalSize = 0; _headerEncodingBuffer.Clear(); @@ -280,9 +281,12 @@ public ValueTask WriteResponseTrailersAsync(long streamId, HttpResp var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); FinishWritingHeaders(payloadLength, done); } - catch (QPackEncodingException ex) + // Any exception from the QPack encoder can leave the dynamic table in a corrupt state. + // Since we allow custom header encoders we don't know what type of exceptions to expect. + catch (Exception ex) { _log.QPackEncodingError(_connectionId, streamId, ex); + _connectionContext.Abort(new ConnectionAbortedException(ex.Message, ex)); _http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError); } @@ -314,7 +318,7 @@ public ValueTask FlushAsync(IHttpOutputAborter? outputAborter, Canc } } - internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers) + internal void WriteResponseHeaders(int statusCode, HttpResponseHeaders headers) { lock (_writeLock) { @@ -325,15 +329,19 @@ internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers) try { - _headersEnumerator = EnumerateHeaders(headers).GetEnumerator(); + _headersEnumerator.Initialize(headers); _outgoingFrame.PrepareHeaders(); var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize); var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); FinishWritingHeaders(payloadLength, done); } - catch (QPackEncodingException ex) + // Any exception from the QPack encoder can leave the dynamic table in a corrupt state. + // Since we allow custom header encoders we don't know what type of exceptions to expect. + catch (Exception ex) { + _log.QPackEncodingError(_connectionId, _http3Stream.StreamId, ex); + _connectionContext.Abort(new ConnectionAbortedException(ex.Message, ex)); _http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError); throw new InvalidOperationException(ex.Message, ex); // Report the error to the user if this was the first write. } @@ -347,7 +355,6 @@ private void FinishWritingHeaders(int payloadLength, bool done) while (!done) { ValidateHeadersTotalSize(); - var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize); done = QPackHeaderWriter.Encode(_headersEnumerator!, buffer, ref _headersTotalSize, out payloadLength); _headerEncodingBuffer.Advance(payloadLength); @@ -404,16 +411,5 @@ public void Abort(ConnectionAbortedException error) _outputWriter.Complete(); } } - - private static IEnumerable> EnumerateHeaders(IHeaderDictionary headers) - { - foreach (var header in headers) - { - foreach (var value in header.Value) - { - yield return new KeyValuePair(header.Key, value); - } - } - } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs new file mode 100644 index 000000000000..67797a000f45 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs @@ -0,0 +1,152 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal sealed class Http3HeadersEnumerator : IEnumerator> + { + private enum HeadersType : byte + { + Headers, + Trailers, + Untyped + } + private HeadersType _headersType; + private HttpResponseHeaders.Enumerator _headersEnumerator; + private HttpResponseTrailers.Enumerator _trailersEnumerator; + private IEnumerator>? _genericEnumerator; + private StringValues.Enumerator _stringValuesEnumerator; + private bool _hasMultipleValues; + private KnownHeaderType _knownHeaderType; + + public Func EncodingSelector { get; set; } = KestrelServerOptions.DefaultHeaderEncodingSelector; + + public int QPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType); + public KeyValuePair Current { get; private set; } + object IEnumerator.Current => Current; + + public void Initialize(HttpResponseHeaders headers) + { + EncodingSelector = headers.EncodingSelector; + _headersEnumerator = headers.GetEnumerator(); + _headersType = HeadersType.Headers; + _hasMultipleValues = false; + } + + public void Initialize(HttpResponseTrailers headers) + { + EncodingSelector = headers.EncodingSelector; + _trailersEnumerator = headers.GetEnumerator(); + _headersType = HeadersType.Trailers; + _hasMultipleValues = false; + } + + public void Initialize(IDictionary headers) + { + switch (headers) + { + case HttpResponseHeaders responseHeaders: + _headersType = HeadersType.Headers; + _headersEnumerator = responseHeaders.GetEnumerator(); + break; + case HttpResponseTrailers responseTrailers: + _headersType = HeadersType.Trailers; + _trailersEnumerator = responseTrailers.GetEnumerator(); + break; + default: + _headersType = HeadersType.Untyped; + _genericEnumerator = headers.GetEnumerator(); + break; + } + + _hasMultipleValues = false; + } + + public bool MoveNext() + { + if (_hasMultipleValues && MoveNextOnStringEnumerator(Current.Key)) + { + return true; + } + + if (_headersType == HeadersType.Headers) + { + return _headersEnumerator.MoveNext() + ? SetCurrent(_headersEnumerator.Current.Key, _headersEnumerator.Current.Value, _headersEnumerator.CurrentKnownType) + : false; + } + else if (_headersType == HeadersType.Trailers) + { + return _trailersEnumerator.MoveNext() + ? SetCurrent(_trailersEnumerator.Current.Key, _trailersEnumerator.Current.Value, _trailersEnumerator.CurrentKnownType) + : false; + } + else + { + return _genericEnumerator!.MoveNext() + ? SetCurrent(_genericEnumerator.Current.Key, _genericEnumerator.Current.Value, default) + : false; + } + } + + private bool MoveNextOnStringEnumerator(string key) + { + var result = _stringValuesEnumerator.MoveNext(); + Current = result ? new KeyValuePair(key, _stringValuesEnumerator.Current) : default; + return result; + } + + private bool SetCurrent(string name, StringValues value, KnownHeaderType knownHeaderType) + { + _knownHeaderType = knownHeaderType; + + if (value.Count == 1) + { + Current = new KeyValuePair(name, value.ToString()); + _hasMultipleValues = false; + return true; + } + else + { + _stringValuesEnumerator = value.GetEnumerator(); + _hasMultipleValues = true; + return MoveNextOnStringEnumerator(name); + } + } + + public void Reset() + { + if (_headersType == HeadersType.Headers) + { + _headersEnumerator.Reset(); + } + else if (_headersType == HeadersType.Trailers) + { + _trailersEnumerator.Reset(); + } + else + { + _genericEnumerator!.Reset(); + } + _stringValuesEnumerator = default; + _knownHeaderType = default; + } + + public void Dispose() + { + } + + internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType) + { + // Not Implemented + return -1; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.FeatureCollection.cs index 90cc3b151e43..9aa6e85431f7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.FeatureCollection.cs @@ -23,7 +23,7 @@ IHeaderDictionary IHttpResponseTrailersFeature.Trailers { if (ResponseTrailers == null) { - ResponseTrailers = new HttpResponseTrailers(); + ResponseTrailers = new HttpResponseTrailers(ServerOptions.ResponseHeaderEncodingSelector); if (HasResponseCompleted) { ResponseTrailers.SetReadOnly(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs index 3bb0ea80ced4..710adc1bb0e5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Net.Http.QPack; @@ -10,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { internal static class QPackHeaderWriter { - public static bool BeginEncode(IEnumerator> enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool BeginEncode(Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) { bool hasValue = enumerator.MoveNext(); Debug.Assert(hasValue == true); @@ -25,7 +24,7 @@ public static bool BeginEncode(IEnumerator> enumera return doneEncode; } - public static bool BeginEncode(int statusCode, IEnumerator> enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool BeginEncode(int statusCode, Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) { bool hasValue = enumerator.MoveNext(); Debug.Assert(hasValue == true); @@ -43,20 +42,22 @@ public static bool BeginEncode(int statusCode, IEnumerator> enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool Encode(Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) { return Encode(enumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length); } - private static bool Encode(IEnumerator> enumerator, Span buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length) + private static bool Encode(Http3HeadersEnumerator enumerator, Span buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length) { length = 0; do { var current = enumerator.Current; + var valueEncoding = ReferenceEquals(enumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : enumerator.EncodingSelector(current.Key); - if (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(current.Key, current.Value, buffer.Slice(length), out int headerLength)) + if (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(current.Key, current.Value, valueEncoding, buffer.Slice(length), out int headerLength)) { if (length == 0 && throwIfNoneEncoded) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpCharacters.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpCharacters.cs index d59ab76a2a41..a2faf32bb346 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpCharacters.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpCharacters.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -182,6 +182,7 @@ public static int IndexOfInvalidTokenChar(ReadOnlySpan span) return -1; } + // Disallows control characters and anything more than 0x7F [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfInvalidFieldValueChar(string s) { @@ -198,5 +199,23 @@ public static int IndexOfInvalidFieldValueChar(string s) return -1; } + + // Disallows control characters but allows extended characters > 0x7F + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfInvalidFieldValueCharExtended(string s) + { + var fieldValue = _fieldValue; + + for (var i = 0; i < s.Length; i++) + { + var c = s[i]; + if (c < (uint)fieldValue.Length && !fieldValue[c]) + { + return i; + } + } + + return -1; + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index e97a6f6e9bba..f6324a7edec6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -110,7 +110,7 @@ public static string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan span, string name, Func encodingSelector) { - if (ReferenceEquals(KestrelServerOptions.DefaultRequestHeaderEncodingSelector, encodingSelector)) + if (ReferenceEquals(KestrelServerOptions.DefaultHeaderEncodingSelector, encodingSelector)) { return span.GetAsciiOrUTF8StringNonNullCharacters(DefaultRequestHeaderEncoding); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs index 8ec96189092d..c2248f14bee0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs @@ -70,9 +70,9 @@ internal interface IKestrelTrace : ILogger void Http2StreamResetAbort(string traceIdentifier, Http2ErrorCode error, ConnectionAbortedException abortReason); - void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex); + void HPackDecodingError(string connectionId, int streamId, Exception ex); - void HPackEncodingError(string connectionId, int streamId, HPackEncodingException ex); + void HPackEncodingError(string connectionId, int streamId, Exception ex); void Http2FrameReceived(string connectionId, Http2Frame frame); @@ -94,9 +94,9 @@ internal interface IKestrelTrace : ILogger void Http3FrameSending(string connectionId, long streamId, Http3RawFrame frame); - void QPackDecodingError(string connectionId, long streamId, QPackDecodingException ex); + void QPackDecodingError(string connectionId, long streamId, Exception ex); - void QPackEncodingError(string connectionId, long streamId, QPackEncodingException ex); + void QPackEncodingError(string connectionId, long streamId, Exception ex); void Http3OutboundControlStreamError(string connectionId, Exception ex); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs index 18e5b6392567..5f000f5b286a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs @@ -320,12 +320,12 @@ public void Http2StreamResetAbort(string traceIdentifier, Http2ErrorCode error, _http2StreamResetAbort(_http2Logger, traceIdentifier, error, abortReason); } - public virtual void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) + public virtual void HPackDecodingError(string connectionId, int streamId, Exception ex) { _hpackDecodingError(_http2Logger, connectionId, streamId, ex); } - public virtual void HPackEncodingError(string connectionId, int streamId, HPackEncodingException ex) + public virtual void HPackEncodingError(string connectionId, int streamId, Exception ex) { _hpackEncodingError(_http2Logger, connectionId, streamId, ex); } @@ -395,12 +395,12 @@ public void Http3FrameSending(string connectionId, long streamId, Http3RawFrame } } - public virtual void QPackDecodingError(string connectionId, long streamId, QPackDecodingException ex) + public virtual void QPackDecodingError(string connectionId, long streamId, Exception ex) { _qpackDecodingError(_http3Logger, connectionId, streamId, ex); } - public virtual void QPackEncodingError(string connectionId, long streamId, QPackEncodingException ex) + public virtual void QPackEncodingError(string connectionId, long streamId, Exception ex) { _qpackEncodingError(_http3Logger, connectionId, streamId, ex); } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index d48c13b02188..a70a63810e4d 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -28,9 +28,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public class KestrelServerOptions { // internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged. - internal static readonly Func DefaultRequestHeaderEncodingSelector = _ => null; + internal static readonly Func DefaultHeaderEncodingSelector = _ => null; - private Func _requestHeaderEncodingSelector = DefaultRequestHeaderEncodingSelector; + private Func _requestHeaderEncodingSelector = DefaultHeaderEncodingSelector; + + private Func _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector; // 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(); @@ -93,6 +95,16 @@ public class KestrelServerOptions set => _requestHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value)); } + /// + /// Gets or sets a callback that returns the to encode the value for the specified response header + /// or trailer name, or to use the default . + /// + public Func ResponseHeaderEncodingSelector + { + get => _responseHeaderEncodingSelector; + set => _responseHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value)); + } + /// /// Enables the Listen options callback to resolve and use services registered by the application during startup. /// Typically initialized by UseKestrel(). diff --git a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt index 8495f35e086c..a3e94fa64f7b 100644 --- a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt @@ -96,6 +96,8 @@ *REMOVED*~static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions, string fileName, string password, System.Action configureOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions *REMOVED*~static Microsoft.AspNetCore.Server.Kestrel.Https.CertificateLoader.LoadFromStoreCert(string subject, string storeName, System.Security.Cryptography.X509Certificates.StoreLocation storeLocation, bool allowInvalid) -> System.Security.Cryptography.X509Certificates.X509Certificate2 Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.DelayCertificate = 3 -> Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode +Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ResponseHeaderEncodingSelector.get -> System.Func! +Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ResponseHeaderEncodingSelector.set -> void static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! listenOptions, Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions! httpsOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! listenOptions, System.Action! configureOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! listenOptions, System.Net.Security.ServerOptionsSelectionCallback! serverOptionsSelectionCallback, object! state) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! diff --git a/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs index 50c2411818c6..bca46ecb5afc 100644 --- a/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs +++ b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs @@ -98,23 +98,29 @@ public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); + Assert.Equal(63u, e.Size); }, e => { Assert.Equal("Cache-Control", e.Name); Assert.Equal("private", e.Value); + Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); + Assert.Equal(65u, e.Size); }, e => { Assert.Equal(":status", e.Name); Assert.Equal("302", e.Value); + Assert.Equal(42u, e.Size); }); + Assert.Equal(222u, hpackEncoder.TableSize); + // Second response enumerator.Initialize(headers); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length)); @@ -129,23 +135,29 @@ public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() { Assert.Equal(":status", e.Name); Assert.Equal("307", e.Value); + Assert.Equal(42u, e.Size); }, e => { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); + Assert.Equal(63u, e.Size); }, e => { Assert.Equal("Cache-Control", e.Name); Assert.Equal("private", e.Value); + Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); + Assert.Equal(65u, e.Size); }); + Assert.Equal(222u, hpackEncoder.TableSize); + // Third response headers.Date = "Mon, 21 Oct 2013 20:13:22 GMT"; headers.ContentEncoding = "gzip"; @@ -171,22 +183,172 @@ public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() { Assert.Equal("Content-Encoding", e.Name); Assert.Equal("gzip", e.Value); + Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value); + Assert.Equal(65u, e.Size); }, e => { Assert.Equal(":status", e.Name); Assert.Equal("307", e.Value); + Assert.Equal(42u, e.Size); }, e => { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); + Assert.Equal(63u, e.Size); + }); + + Assert.Equal(222u, hpackEncoder.TableSize); + } + + [Fact] + public void BeginEncodeHeadersCustomEncoding_MaxHeaderTableSizeExceeded_EvictionsToFit() + { + // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 + + Span buffer = new byte[1024 * 16]; + + var headers = (IHeaderDictionary)new HttpResponseHeaders(_ => Encoding.UTF8); + headers.CacheControl = "你好e"; + headers.Date = "Mon, 21 Oct 2013 20:13:21 GMT"; + headers.Location = "你好你好你好你.c"; + + var enumerator = new Http2HeadersEnumerator(); + + var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: 256); + + // First response + enumerator.Initialize((HttpResponseHeaders)headers); + Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length)); + + var result = buffer.Slice(0, length).ToArray(); + var hex = BitConverter.ToString(result); + Assert.Equal( + "48-03-33-30-32-61-1D-4D-6F-6E-2C-20-32-31-20-4F-" + + "63-74-20-32-30-31-33-20-32-30-3A-31-33-3A-32-31-" + + "20-47-4D-54-58-07-E4-BD-A0-E5-A5-BD-65-6E-17-E4-" + + "BD-A0-E5-A5-BD-E4-BD-A0-E5-A5-BD-E4-BD-A0-E5-A5-" + + "BD-E4-BD-A0-2E-63", hex); + + var entries = GetHeaderEntries(hpackEncoder); + Assert.Collection(entries, + e => + { + Assert.Equal("Location", e.Name); + Assert.Equal("你好你好你好你.c", e.Value); + Assert.Equal(63u, e.Size); + }, + e => + { + Assert.Equal("Cache-Control", e.Name); + Assert.Equal("你好e", e.Value); + Assert.Equal(52u, e.Size); + }, + e => + { + Assert.Equal("Date", e.Name); + Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); + Assert.Equal(65u, e.Size); + }, + e => + { + Assert.Equal(":status", e.Name); + Assert.Equal("302", e.Value); + Assert.Equal(42u, e.Size); }); + + Assert.Equal(222u, hpackEncoder.TableSize); + + // Second response + enumerator.Initialize(headers); + Assert.True(HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length)); + + result = buffer.Slice(0, length).ToArray(); + hex = BitConverter.ToString(result); + Assert.Equal("48-03-33-30-37-C1-C0-BF", hex); + + entries = GetHeaderEntries(hpackEncoder); + Assert.Collection(entries, + e => + { + Assert.Equal(":status", e.Name); + Assert.Equal("307", e.Value); + Assert.Equal(42u, e.Size); + }, + e => + { + Assert.Equal("Location", e.Name); + Assert.Equal("你好你好你好你.c", e.Value); + Assert.Equal(63u, e.Size); + }, + e => + { + Assert.Equal("Cache-Control", e.Name); + Assert.Equal("你好e", e.Value); + Assert.Equal(52u, e.Size); + }, + e => + { + Assert.Equal("Date", e.Name); + Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); + Assert.Equal(65u, e.Size); + }); + + Assert.Equal(222u, hpackEncoder.TableSize); + + // Third response + headers.Date = "Mon, 21 Oct 2013 20:13:22 GMT"; + headers.ContentEncoding = "gzip"; + headers.SetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"; + + enumerator.Initialize(headers); + Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length)); + + result = buffer.Slice(0, length).ToArray(); + hex = BitConverter.ToString(result); + Assert.Equal( + "88-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-" + + "30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-54-" + + "C1-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" + + "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" + + "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" + + "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" + + "6E-3D-31", hex); + + entries = GetHeaderEntries(hpackEncoder); + Assert.Collection(entries, + e => + { + Assert.Equal("Content-Encoding", e.Name); + Assert.Equal("gzip", e.Value); + Assert.Equal(52u, e.Size); + }, + e => + { + Assert.Equal("Date", e.Name); + Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value); + Assert.Equal(65u, e.Size); + }, + e => + { + Assert.Equal(":status", e.Name); + Assert.Equal("307", e.Value); + Assert.Equal(42u, e.Size); + }, + e => + { + Assert.Equal("Location", e.Name); + Assert.Equal("你好你好你好你.c", e.Value); + Assert.Equal(63u, e.Size); + }); + + Assert.Equal(222u, hpackEncoder.TableSize); } [Theory] diff --git a/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs b/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs index 72c3310358ec..8e1047f7b411 100644 --- a/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs +++ b/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; using Xunit; diff --git a/src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs b/src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs new file mode 100644 index 000000000000..100aba3b6f3a --- /dev/null +++ b/src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs @@ -0,0 +1,156 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Net.Http.HPack; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; +using Microsoft.Extensions.Primitives; + +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http3HeadersEnumeratorTests + { + [Fact] + public void CanIterateOverResponseHeaders() + { + var responseHeaders = (IHeaderDictionary)new HttpResponseHeaders(); + + responseHeaders.ContentLength = 9; + responseHeaders.AcceptRanges = "AcceptRanges!"; + responseHeaders.Age = new StringValues(new[] { "1", "2" }); + responseHeaders.Date = "Date!"; + responseHeaders.GrpcEncoding = "Identity!"; + + responseHeaders.Append("Name1", "Value1"); + responseHeaders.Append("Name2", "Value2-1"); + responseHeaders.Append("Name2", "Value2-2"); + responseHeaders.Append("Name3", "Value3"); + + var e = new Http3HeadersEnumerator(); + e.Initialize(responseHeaders); + + var headers = GetNormalizedHeaders(e); + + Assert.Equal(new[] + { + CreateHeaderResult(-1, "Date", "Date!"), + CreateHeaderResult(-1, "Accept-Ranges", "AcceptRanges!"), + CreateHeaderResult(-1, "Age", "1"), + CreateHeaderResult(-1, "Age", "2"), + CreateHeaderResult(-1, "Grpc-Encoding", "Identity!"), + CreateHeaderResult(-1, "Content-Length", "9"), + CreateHeaderResult(-1, "Name1", "Value1"), + CreateHeaderResult(-1, "Name2", "Value2-1"), + CreateHeaderResult(-1, "Name2", "Value2-2"), + CreateHeaderResult(-1, "Name3", "Value3"), + }, headers); + } + + [Fact] + public void CanIterateOverResponseTrailers() + { + var responseTrailers = (IHeaderDictionary)new HttpResponseTrailers(); + + responseTrailers.ContentLength = 9; + responseTrailers.ETag = "ETag!"; + + responseTrailers.Append("Name1", "Value1"); + responseTrailers.Append("Name2", "Value2-1"); + responseTrailers.Append("Name2", "Value2-2"); + responseTrailers.Append("Name3", "Value3"); + + var e = new Http3HeadersEnumerator(); + e.Initialize(responseTrailers); + + var headers = GetNormalizedHeaders(e); + + Assert.Equal(new[] + { + CreateHeaderResult(-1, "ETag", "ETag!"), + CreateHeaderResult(-1, "Name1", "Value1"), + CreateHeaderResult(-1, "Name2", "Value2-1"), + CreateHeaderResult(-1, "Name2", "Value2-2"), + CreateHeaderResult(-1, "Name3", "Value3"), + }, headers); + } + + [Fact] + public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource() + { + var responseHeaders = new HttpResponseHeaders(); + responseHeaders.Append("Name1", "Value1"); + responseHeaders.Append("Name2", "Value2-1"); + responseHeaders.Append("Name2", "Value2-2"); + + var e = new Http3HeadersEnumerator(); + e.Initialize(responseHeaders); + + Assert.True(e.MoveNext()); + Assert.Equal("Name1", e.Current.Key); + Assert.Equal("Value1", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.True(e.MoveNext()); + Assert.Equal("Name2", e.Current.Key); + Assert.Equal("Value2-1", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.True(e.MoveNext()); + Assert.Equal("Name2", e.Current.Key); + Assert.Equal("Value2-2", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + var responseTrailers = (IHeaderDictionary)new HttpResponseTrailers(); + + responseTrailers.GrpcStatus = "1"; + + responseTrailers.Append("Name1", "Value1"); + responseTrailers.Append("Name2", "Value2-1"); + responseTrailers.Append("Name2", "Value2-2"); + + e.Initialize(responseTrailers); + + Assert.True(e.MoveNext()); + Assert.Equal("Grpc-Status", e.Current.Key); + Assert.Equal("1", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.True(e.MoveNext()); + Assert.Equal("Name1", e.Current.Key); + Assert.Equal("Value1", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.True(e.MoveNext()); + Assert.Equal("Name2", e.Current.Key); + Assert.Equal("Value2-1", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.True(e.MoveNext()); + Assert.Equal("Name2", e.Current.Key); + Assert.Equal("Value2-2", e.Current.Value); + Assert.Equal(-1, e.QPackStaticTableId); + + Assert.False(e.MoveNext()); + } + + private (int QPackStaticTableId, string Name, string Value)[] GetNormalizedHeaders(Http3HeadersEnumerator enumerator) + { + var headers = new List<(int HPackStaticTableId, string Name, string Value)>(); + while (enumerator.MoveNext()) + { + headers.Add(CreateHeaderResult(enumerator.QPackStaticTableId, enumerator.Current.Key, enumerator.Current.Value)); + } + return headers.ToArray(); + } + + private static (int QPackStaticTableId, string Key, string Value) CreateHeaderResult(int hPackStaticTableId, string key, string value) + { + return (hPackStaticTableId, key, value); + } + } +} diff --git a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs index a04a497271ec..de21074fbd81 100644 --- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO.Pipelines; +using System.Text; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -129,6 +130,117 @@ public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string }); } + [Theory] + [InlineData("\r\nData")] + [InlineData("\0Data")] + [InlineData("Data\r")] + [InlineData("Da\0ta")] + [InlineData("Da\u001Fta")] + [InlineData("Data\0")] + [InlineData("Da\nta")] + [InlineData("Da\u007Fta")] + [InlineData("Da\u0080ta")] + [InlineData("Da™ta")] + [InlineData("Dašta")] + public void AddingControlOrNonAsciiCharactersToHeaderPropertyThrows(string value) + { + var responseHeaders = (IHeaderDictionary)new HttpResponseHeaders(); + + // Known special header + Assert.Throws(() => + { + responseHeaders.Allow = value; + }); + + // Unknown header fallback + Assert.Throws(() => + { + responseHeaders.Accept = value; + }); + } + + [Theory] + [InlineData("\r\nData")] + [InlineData("\0Data")] + [InlineData("Data\r")] + [InlineData("Da\0ta")] + [InlineData("Da\u001Fta")] + [InlineData("Data\0")] + [InlineData("Da\nta")] + [InlineData("Da\u007Fta")] + public void AddingControlCharactersWithCustomEncoderThrows(string value) + { + var responseHeaders = new HttpResponseHeaders(_ => Encoding.UTF8); + + // Known special header + Assert.Throws(() => + { + ((IHeaderDictionary)responseHeaders).Allow = value; + }); + + // Unknown header fallback + Assert.Throws(() => + { + ((IHeaderDictionary)responseHeaders).Accept = value; + }); + + Assert.Throws(() => + { + ((IHeaderDictionary)responseHeaders)["Unknown"] = value; + }); + + Assert.Throws(() => + { + ((IHeaderDictionary)responseHeaders)["Unknown"] = new StringValues(new[] { "valid", value }); + }); + + Assert.Throws(() => + { + ((IDictionary)responseHeaders)["Unknown"] = value; + }); + + Assert.Throws(() => + { + var kvp = new KeyValuePair("Unknown", value); + ((ICollection>)responseHeaders).Add(kvp); + }); + + Assert.Throws(() => + { + var kvp = new KeyValuePair("Unknown", value); + ((IDictionary)responseHeaders).Add("Unknown", value); + }); + } + + [Theory] + [InlineData("Da\u0080ta")] + [InlineData("Da™ta")] + [InlineData("Dašta")] + public void AddingNonAsciiCharactersWithCustomEncoderWorks(string value) + { + var responseHeaders = new HttpResponseHeaders(_ => Encoding.UTF8); + + // Known special header + ((IHeaderDictionary)responseHeaders).Allow = value; + + // Unknown header fallback + ((IHeaderDictionary)responseHeaders).Accept = value; + + ((IHeaderDictionary)responseHeaders)["Unknown"] = value; + + ((IHeaderDictionary)responseHeaders)["Unknown"] = new StringValues(new[] { "valid", value }); + + ((IDictionary)responseHeaders)["Unknown"] = value; + + ((IHeaderDictionary)responseHeaders).Clear(); + var kvp = new KeyValuePair("Unknown", value); + ((ICollection>)responseHeaders).Add(kvp); + + ((IHeaderDictionary)responseHeaders).Clear(); + kvp = new KeyValuePair("Unknown", value); + ((IDictionary)responseHeaders).Add("Unknown", value); + } + [Fact] public void ThrowsWhenAddingHeaderAfterReadOnlyIsSet() { diff --git a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs index 7df5b8f4efdf..258b0123a919 100644 --- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs +++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs @@ -148,8 +148,7 @@ public void WriteAscii() } [Theory] - [InlineData(2, 1)] - [InlineData(3, 1)] + [InlineData(3, 2)] [InlineData(4, 2)] [InlineData(5, 3)] [InlineData(7, 4)] diff --git a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs index 5083ba7d16f5..cb4225bb50ca 100644 --- a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs +++ b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs @@ -18,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.GetRequestHeaderString(encodedBytes.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultRequestHeaderEncodingSelector); + var s = HttpUtilities.GetRequestHeaderString(encodedBytes.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultHeaderEncodingSelector); Assert.Equal(1, s.Length); } @@ -37,7 +37,7 @@ private void ExceptionThrownForZeroOrNonAscii(byte[] bytes) Array.Copy(bytes, 0, byteRange, position, bytes.Length); Assert.Throws(() => - HttpUtilities.GetRequestHeaderString(byteRange.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)); + HttpUtilities.GetRequestHeaderString(byteRange.AsSpan(), HeaderNames.Accept, KestrelServerOptions.DefaultHeaderEncodingSelector)); } } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/BytesToStringBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/BytesToStringBenchmark.cs index 0258e902ee79..a1e803d411ba 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/BytesToStringBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/BytesToStringBenchmark.cs @@ -74,7 +74,7 @@ public void Utf8BytesToString() { for (uint i = 0; i < Iterations; i++) { - HttpUtilities.GetRequestHeaderString(_utf8Bytes, _headerName, KestrelServerOptions.DefaultRequestHeaderEncodingSelector); + HttpUtilities.GetRequestHeaderString(_utf8Bytes, _headerName, KestrelServerOptions.DefaultHeaderEncodingSelector); } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HPackHeaderWriterBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HPackHeaderWriterBenchmark.cs index b78d3e511e60..751129e0ffa9 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/HPackHeaderWriterBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/HPackHeaderWriterBenchmark.cs @@ -18,8 +18,8 @@ public class HPackHeaderWriterBenchmark { private Http2HeadersEnumerator _http2HeadersEnumerator; private DynamicHPackEncoder _hpackEncoder; - private IHeaderDictionary _knownResponseHeaders; - private IHeaderDictionary _unknownResponseHeaders; + private HttpResponseHeaders _knownResponseHeaders; + private HttpResponseHeaders _unknownResponseHeaders; private byte[] _buffer; [GlobalSetup] @@ -31,18 +31,19 @@ public void GlobalSetup() _knownResponseHeaders = new HttpResponseHeaders(); - _knownResponseHeaders.Server = "Kestrel"; - _knownResponseHeaders.ContentType = "application/json"; - _knownResponseHeaders.Date = "Date!"; - _knownResponseHeaders.ContentLength = 0; - _knownResponseHeaders.AcceptRanges = "Ranges!"; - _knownResponseHeaders.TransferEncoding = "Encoding!"; - _knownResponseHeaders.Via = "Via!"; - _knownResponseHeaders.Vary = "Vary!"; - _knownResponseHeaders.WWWAuthenticate = "Authenticate!"; - _knownResponseHeaders.LastModified = "Modified!"; - _knownResponseHeaders.Expires = "Expires!"; - _knownResponseHeaders.Age = "Age!"; + var knownHeaders = (IHeaderDictionary)_knownResponseHeaders; + knownHeaders.Server = "Kestrel"; + knownHeaders.ContentType = "application/json"; + knownHeaders.Date = "Date!"; + knownHeaders.ContentLength = 0; + knownHeaders.AcceptRanges = "Ranges!"; + knownHeaders.TransferEncoding = "Encoding!"; + knownHeaders.Via = "Via!"; + knownHeaders.Vary = "Vary!"; + knownHeaders.WWWAuthenticate = "Authenticate!"; + knownHeaders.LastModified = "Modified!"; + knownHeaders.Expires = "Expires!"; + knownHeaders.Age = "Age!"; _unknownResponseHeaders = new HttpResponseHeaders(); for (var i = 0; i < 10; i++) @@ -58,11 +59,27 @@ public void BeginEncodeHeaders_KnownHeaders() HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _http2HeadersEnumerator, _buffer, out _); } + [Benchmark] + public void BeginEncodeHeaders_KnownHeaders_CustomEncoding() + { + _knownResponseHeaders.EncodingSelector = _ => Encoding.UTF8; + _http2HeadersEnumerator.Initialize(_knownResponseHeaders); + HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _http2HeadersEnumerator, _buffer, out _); + } + [Benchmark] public void BeginEncodeHeaders_UnknownHeaders() { _http2HeadersEnumerator.Initialize(_unknownResponseHeaders); HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _http2HeadersEnumerator, _buffer, out _); } + + [Benchmark] + public void BeginEncodeHeaders_UnknownHeaders_CustomEncoding() + { + _knownResponseHeaders.EncodingSelector = _ => Encoding.UTF8; + _http2HeadersEnumerator.Initialize(_unknownResponseHeaders); + HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _http2HeadersEnumerator, _buffer, out _); + } } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs index 11eac3dd5859..9bcb669c373f 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs @@ -52,8 +52,8 @@ public void ResponseMinimumDataRateNotSatisfied(string connectionId, string trac public void ApplicationAbortedConnection(string connectionId, string traceIdentifier) { } public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex) { } public void Http2StreamError(string connectionId, Http2StreamErrorException ex) { } - public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) { } - public void HPackEncodingError(string connectionId, int streamId, HPackEncodingException ex) { } + public void HPackDecodingError(string connectionId, int streamId, Exception ex) { } + public void HPackEncodingError(string connectionId, int streamId, Exception ex) { } public void Http2StreamResetAbort(string traceIdentifier, Http2ErrorCode error, ConnectionAbortedException abortReason) { } public void Http2ConnectionClosing(string connectionId) { } public void Http2ConnectionClosed(string connectionId, int highestOpenedStreamId) { } @@ -67,8 +67,8 @@ public void Http3ConnectionClosed(string connectionId, long highestOpenedStreamI public void Http3StreamAbort(string traceIdentifier, Http3ErrorCode error, ConnectionAbortedException abortReason) { } public void Http3FrameReceived(string connectionId, long streamId, Http3RawFrame frame) { } public void Http3FrameSending(string connectionId, long streamId, Http3RawFrame frame) { } - public void QPackDecodingError(string connectionId, long streamId, QPackDecodingException ex) { } - public void QPackEncodingError(string connectionId, long streamId, QPackEncodingException ex) { } + public void QPackDecodingError(string connectionId, long streamId, Exception ex) { } + public void QPackEncodingError(string connectionId, long streamId, Exception ex) { } public void Http3OutboundControlStreamError(string connectionId, Exception ex) { } } } diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index 9ccbc432fb12..6c0d60d6b092 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -329,13 +329,15 @@ static string AppendHPackSwitchSection(HPackGroup group) var header = group.Header; if (header.Name == HeaderNames.ContentLength) { - return $@"if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)) + return $@"var customEncoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : EncodingSelector(HeaderNames.ContentLength); + if (customEncoding == null) {{ AppendContentLength(value); }} else {{ - AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength)); + AppendContentLengthCustomEncoding(value, customEncoding); }} return true;"; } @@ -370,13 +372,15 @@ string GenerateIfBody(KnownHeader header, string extraIndent = "") if (header.Name == HeaderNames.ContentLength) { return $@" - {extraIndent}if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector)) + {extraIndent}var customEncoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + {extraIndent} ? null : EncodingSelector(HeaderNames.ContentLength); + {extraIndent}if (customEncoding == null) {extraIndent}{{ {extraIndent} AppendContentLength(value); {extraIndent}}} {extraIndent}else {extraIndent}{{ - {extraIndent} AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength)); + {extraIndent} AppendContentLengthCustomEncoding(value, customEncoding); {extraIndent}}} {extraIndent}return;"; } @@ -775,6 +779,7 @@ public static string GeneratedFile() using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Microsoft.AspNetCore.Http; @@ -858,7 +863,8 @@ StringValues IHeaderDictionary.{header.Identifier} var flag = {header.FlagBit()}; if (value.Count > 0) - {{ + {{{(loop.ClassName != "HttpRequestHeaders" ? $@" + ValidateHeaderValueCharacters(HeaderNames.{header.Identifier}, value, EncodingSelector);" : "")} _bits |= flag; _headers._{header.Identifier} = value; }} @@ -884,8 +890,8 @@ StringValues IHeaderDictionary.{header} }} set {{ - if (_isReadOnly) {{ ThrowHeadersReadOnlyException(); }} - + if (_isReadOnly) {{ ThrowHeadersReadOnlyException(); }}{(loop.ClassName != "HttpRequestHeaders" ? $@" + ValidateHeaderValueCharacters(HeaderNames.{header}, value, EncodingSelector);" : "")} SetValueUnknown(HeaderNames.{header}, value); }} }}")} @@ -948,7 +954,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) protected override void SetValueFast(string key, StringValues value) {{{(loop.ClassName != "HttpRequestHeaders" ? @" - ValidateHeaderValueCharacters(value);" : "")} + ValidateHeaderValueCharacters(key, value, EncodingSelector);" : "")} switch (key.Length) {{{Each(loop.HeadersByLength, byLength => $@" case {byLength.Key}: @@ -979,7 +985,7 @@ protected override void SetValueFast(string key, StringValues value) protected override bool AddValueFast(string key, StringValues value) {{{(loop.ClassName != "HttpRequestHeaders" ? @" - ValidateHeaderValueCharacters(value);" : "")} + ValidateHeaderValueCharacters(key, value, EncodingSelector);" : "")} switch (key.Length) {{{Each(loop.HeadersByLength, byLength => $@" case {byLength.Key}: @@ -1171,6 +1177,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) {{ int keyStart; int keyLength; + var headerName = string.Empty; switch (next) {{{Each(loop.Headers.OrderBy(h => h.Index).Where(h => h.Identifier != "ContentLength"), header => $@" case {header.Index}: // Header: ""{header.Name}"" @@ -1191,6 +1198,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) values = ref _headers._{header.Identifier}; keyStart = {header.BytesOffset}; keyLength = {header.BytesCount}; + headerName = HeaderNames.{header.Identifier}; }}")} break; // OutputHeader ")} @@ -1203,6 +1211,8 @@ internal unsafe void CopyToFast(ref BufferWriter output) {{ // Clear bit tempBits ^= (1UL << next); + var encoding = ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : EncodingSelector(headerName); var valueCount = values.Count; Debug.Assert(valueCount > 0); @@ -1213,7 +1223,14 @@ internal unsafe void CopyToFast(ref BufferWriter output) if (value != null) {{ output.Write(headerKey); - output.WriteAscii(value); + if (encoding is null) + {{ + output.WriteAscii(value); + }} + else + {{ + output.WriteEncoded(value, encoding); + }} }} }} // Set exact next diff --git a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs index f106506b4e46..a8f7ca03a99d 100644 --- a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs +++ b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs @@ -192,13 +192,13 @@ public void Http2StreamError(string connectionId, Http2StreamErrorException ex) _trace2.Http2StreamError(connectionId, ex); } - public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) + public void HPackDecodingError(string connectionId, int streamId, Exception ex) { _trace1.HPackDecodingError(connectionId, streamId, ex); _trace2.HPackDecodingError(connectionId, streamId, ex); } - public void HPackEncodingError(string connectionId, int streamId, HPackEncodingException ex) + public void HPackEncodingError(string connectionId, int streamId, Exception ex) { _trace1.HPackEncodingError(connectionId, streamId, ex); _trace2.HPackEncodingError(connectionId, streamId, ex); @@ -282,13 +282,13 @@ public void Http3FrameSending(string connectionId, long streamId, Http3RawFrame _trace2.Http3FrameSending(connectionId, streamId, frame); } - public void QPackDecodingError(string connectionId, long streamId, QPackDecodingException ex) + public void QPackDecodingError(string connectionId, long streamId, Exception ex) { _trace1.QPackDecodingError(connectionId, streamId, ex); _trace2.QPackDecodingError(connectionId, streamId, ex); } - public void QPackEncodingError(string connectionId, long streamId, QPackEncodingException ex) + public void QPackEncodingError(string connectionId, long streamId, Exception ex) { _trace1.QPackEncodingError(connectionId, streamId, ex); _trace2.QPackEncodingError(connectionId, streamId, ex); diff --git a/src/Servers/Kestrel/shared/test/StreamBackedTestConnection.cs b/src/Servers/Kestrel/shared/test/StreamBackedTestConnection.cs index 11e7925bf52d..f5b41b10185a 100644 --- a/src/Servers/Kestrel/shared/test/StreamBackedTestConnection.cs +++ b/src/Servers/Kestrel/shared/test/StreamBackedTestConnection.cs @@ -20,10 +20,10 @@ public abstract class StreamBackedTestConnection : IDisposable private readonly Stream _stream; private readonly StreamReader _reader; - protected StreamBackedTestConnection(Stream stream) + protected StreamBackedTestConnection(Stream stream, Encoding encoding = null) { _stream = stream; - _reader = new StreamReader(_stream, Encoding.ASCII); + _reader = new StreamReader(_stream, encoding ?? Encoding.ASCII); } public Stream Stream => _stream; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 3e0850b4c75b..5e4e6e2f5ea6 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -1953,6 +1953,105 @@ await InitializeConnectionAsync(async context => Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); } + [Fact] + public async Task ResponseHeaders_WithNonAscii_Throws() + { + await InitializeConnectionAsync(async context => + { + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.ContentType = "Custom 你好 Type"); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + } + + [Fact] + public async Task ResponseHeaders_WithNonAsciiAndCustomEncoder_Works() + { + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8; + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response. + + await InitializeConnectionAsync(async context => + { + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + context.Response.ContentType = "Custom 你好 Type"; + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 84, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(4, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + Assert.Equal("Custom 你好 Type", _decodedHeaders[HeaderNames.ContentType]); + Assert.Equal("Custom 你好 Value", _decodedHeaders["CustomName"]); + } + + [Fact] + public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection() + { + var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding; + + await InitializeConnectionAsync(async context => + { + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + } + [Fact] public async Task ResponseTrailers_WithoutData_Sent() { @@ -2185,6 +2284,10 @@ await InitializeConnectionAsync(async context => await context.Response.WriteAsync("Hello World"); Assert.Throws(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value")); Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom 你好 Value")); + Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom \r Value")); + // ETag is one of the few special cased trailers. Accept is not. + Assert.Throws(() => context.Features.Get().Trailers.ETag = "Custom 你好 Tag"); + Assert.Throws(() => context.Features.Get().Trailers.Accept = "Custom 你好 Tag"); }); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); @@ -2213,6 +2316,92 @@ await ExpectAsync(Http2FrameType.DATA, Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); } + [Fact] + public async Task ResponseTrailers_WithNonAsciiAndCustomEncoder_Works() + { + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8; + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response. + + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + Assert.Throws(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom \r Value")); + context.Response.AppendTrailer("CustomName", "Custom 你好 Value"); + // ETag is one of the few special cased trailers. Accept is not. + context.Features.Get().Trailers.ETag = "Custom 你好 Tag"; + context.Features.Get().Trailers.Accept = "Custom 你好 Accept"; + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 80, + withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS), + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + _decodedHeaders.Clear(); + + _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Equal("Custom 你好 Value", _decodedHeaders["CustomName"]); + Assert.Equal("Custom 你好 Tag", _decodedHeaders[HeaderNames.ETag]); + Assert.Equal("Custom 你好 Accept", _decodedHeaders[HeaderNames.Accept]); + } + + [Fact] + public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConnection() + { + var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding; + + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("CustomName", "Custom 你好 Value"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + } + [Fact] public async Task ResponseTrailers_TooLong_Throws() { @@ -4787,6 +4976,25 @@ await WaitForConnectionErrorAsync( expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders); } + [Fact] + public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnection() + { + var encoding = Encoding.GetEncoding(Encoding.ASCII.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => encoding; + + await InitializeConnectionAsync(context => + { + Assert.Equal("£", context.Request.Headers["X-Test"]); + return Task.CompletedTask; + }); + + await StartStreamAsync(1, LatinHeaderData, endStream: true); + + await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, + Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); + } + [Fact] public async Task RemoveConnectionSpecificHeaders() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index af7ec5dc7fb0..838c21f5f401 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -736,6 +736,103 @@ public async Task ResponseTrailers_WithoutData_Sent() Assert.Equal("Value2", responseTrailers["Trailer2"]); } + [Fact] + public async Task ResponseHeaders_WithNonAscii_Throws() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var trailersFeature = context.Features.Get(); + + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.ContentType = "Custom 你好 Type"); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + await context.Response.WriteAsync("Hello World"); + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray())); + + Assert.Equal(2, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + } + + [Fact] + public async Task ResponseHeaders_WithNonAsciiAndCustomEncoder_Works() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8; + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response. + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var trailersFeature = context.Features.Get(); + + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + context.Response.ContentType = "Custom 你好 Type"; + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + await context.Response.WriteAsync("Hello World"); + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray())); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("Custom 你好 Type", responseHeaders[HeaderNames.ContentType]); + Assert.Equal("Custom 你好 Value", responseHeaders["CustomName"]); + } + + [Fact] + public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + await context.Response.WriteAsync("Hello World"); + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError, ""); + } + [Fact] public async Task ResponseTrailers_WithData_Sent() { @@ -798,6 +895,106 @@ public async Task ResponseTrailers_WithExeption500_Cleared() await requestStream.ExpectReceiveEndOfStream(); } + [Fact] + public async Task ResponseTrailers_WithNonAscii_Throws() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + Assert.Throws(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom 你好 Value")); + Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom \r Value")); + // ETag is one of the few special cased trailers. Accept is not. + Assert.Throws(() => context.Features.Get().Trailers.ETag = "Custom 你好 Tag"); + Assert.Throws(() => context.Features.Get().Trailers.Accept = "Custom 你好 Tag"); + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray())); + await requestStream.ExpectReceiveEndOfStream(); + } + + [Fact] + public async Task ResponseTrailers_WithNonAsciiAndCustomEncoder_Works() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8; + _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response. + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + Assert.Throws(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.AppendTrailer("CustomName", "Custom \r Value")); + context.Response.AppendTrailer("CustomName", "Custom 你好 Value"); + // ETag is one of the few special cased trailers. Accept is not. + context.Features.Get().Trailers.ETag = "Custom 你好 Tag"; + context.Features.Get().Trailers.Accept = "Custom 你好 Accept"; + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray())); + + var responseTrailers = await requestStream.ExpectHeadersAsync(); + Assert.Equal(3, responseTrailers.Count); + Assert.Equal("Custom 你好 Value", responseTrailers["CustomName"]); + Assert.Equal("Custom 你好 Tag", responseTrailers[HeaderNames.ETag]); + Assert.Equal("Custom 你好 Accept", responseTrailers[HeaderNames.Accept]); + + await requestStream.ExpectReceiveEndOfStream(); + } + + [Fact] + public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConnection() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("CustomName", "Custom 你好 Value"); + }); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + var responseData = await requestStream.ExpectDataAsync(); + Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray())); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError, ""); + } + [Fact] public async Task ResetStream_ReturnStreamError() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index ba9eab872d1f..50f7802eb884 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -27,6 +27,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -611,12 +612,24 @@ public async Task SendHeadersAsync(IEnumerable> hea var frame = new Http3RawFrame(); frame.PrepareHeaders(); var buffer = _headerEncodingBuffer.AsMemory(); - var done = QPackHeaderWriter.BeginEncode(headers.GetEnumerator(), buffer.Span, ref headersTotalSize, out var length); + var done = QPackHeaderWriter.BeginEncode(GetHeadersEnumerator(headers), + buffer.Span, ref headersTotalSize, out var length); Assert.True(done); await SendFrameAsync(frame, buffer.Slice(0, length), endStream); } + internal Http3HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers) + { + var dictionary = headers + .GroupBy(g => g.Key) + .ToDictionary(g => g.Key, g => new StringValues(g.Select(values => values.Value).ToArray())); + + var headersEnumerator = new Http3HeadersEnumerator(); + headersEnumerator.Initialize(dictionary); + return headersEnumerator; + } + internal async Task SendHeadersPartialAsync() { // Send HEADERS frame header without content. diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseHeaderTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseHeaderTests.cs new file mode 100644 index 000000000000..86fa7a1fa2ac --- /dev/null +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseHeaderTests.cs @@ -0,0 +1,104 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests +{ + public class ResponseHeaderTests : TestApplicationErrorLoggerLoggedTest + { + [Fact] + public async Task ResponseHeaders_WithNonAscii_Throws() + { + await using var server = new TestServer(context => + { + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.ContentType = "Custom 你好 Type"); // Special cased + Assert.Throws(() => context.Response.Headers.Accept = "Custom 你好 Accept"); // Not special cased + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + context.Response.ContentLength = 11; + return context.Response.WriteAsync("Hello World"); + }, new TestServiceContext(LoggerFactory)); + using var connection = server.CreateConnection(); + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + $"HTTP/1.1 200 OK", + "Content-Length: 11", + $"Date: {server.Context.DateHeaderValue}", + "", + "Hello World"); + } + + [Fact] + public async Task ResponseHeaders_WithNonAsciiWithCustomEncoding_Works() + { + var testContext = new TestServiceContext(LoggerFactory); + testContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8; + + await using var server = new TestServer(context => + { + Assert.Throws(() => context.Response.Headers.Append("Custom你好Name", "Custom Value")); + Assert.Throws(() => context.Response.Headers.Append("CustomName", "Custom \r Value")); + context.Response.ContentType = "Custom 你好 Type"; + context.Response.Headers.Accept = "Custom 你好 Accept"; + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + context.Response.ContentLength = 11; + return context.Response.WriteAsync("Hello World"); + }, testContext); + + using var connection = server.CreateConnection(Encoding.UTF8); + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + $"HTTP/1.1 200 OK", + "Content-Length: 11", + "Content-Type: Custom 你好 Type", + $"Date: {server.Context.DateHeaderValue}", + "Accept: Custom 你好 Accept", + "CustomName: Custom 你好 Value", + "", + "Hello World"); + } + + [Fact] + public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection() + { + var testContext = new TestServiceContext(LoggerFactory); + var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + testContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding; + + await using var server = new TestServer(context => + { + context.Response.Headers.Append("CustomName", "Custom 你好 Value"); + context.Response.ContentLength = 11; + return context.Response.WriteAsync("Hello World"); + }, testContext); + using var connection = server.CreateConnection(); + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.ReceiveEnd(); + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs index b8e3209f8887..4eccebe2e0df 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.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.Text; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Testing; @@ -9,8 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans { internal class InMemoryConnection : StreamBackedTestConnection { - public InMemoryConnection(InMemoryTransportConnection transportConnection) - : base(new DuplexPipeStream(transportConnection.Output, transportConnection.Input)) + public InMemoryConnection(InMemoryTransportConnection transportConnection, Encoding encoding) + : base(new DuplexPipeStream(transportConnection.Output, transportConnection.Input), encoding) { TransportConnection = transportConnection; } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index 21ca585fed2c..8707ec747fe2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -115,11 +116,11 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action _headerTableSize; + public DynamicHPackEncoder(bool allowDynamicCompression = true, uint maxHeaderTableSize = DefaultHeaderTableSize) { _allowDynamicCompression = allowDynamicCompression; _maxHeaderTableSize = maxHeaderTableSize; Head = new EncoderHeaderEntry(); - Head.Initialize(-1, string.Empty, string.Empty, int.MaxValue, null); + Head.Initialize(-1, string.Empty, string.Empty, 0, int.MaxValue, null); // Bucket count balances memory usage and the expected low number of headers (constrained by the header table size). // Performance with different bucket counts hasn't been measured in detail. _headerBuckets = new EncoderHeaderEntry[16]; @@ -63,7 +66,8 @@ public bool EnsureDynamicTableSizeUpdate(Span buffer, out int length) return true; } - public bool EncodeHeader(Span buffer, int staticTableIndex, HeaderEncodingHint encodingHint, string name, string value, out int bytesWritten) + public bool EncodeHeader(Span buffer, int staticTableIndex, HeaderEncodingHint encodingHint, string name, string value, + Encoding? valueEncoding, out int bytesWritten) { Debug.Assert(!_pendingTableSizeUpdate, "Dynamic table size update should be encoded before headers."); @@ -73,30 +77,31 @@ public bool EncodeHeader(Span buffer, int staticTableIndex, HeaderEncoding int index = ResolveDynamicTableIndex(staticTableIndex, name); return index == -1 - ? HPackEncoder.EncodeLiteralHeaderFieldNeverIndexingNewName(name, value, buffer, out bytesWritten) - : HPackEncoder.EncodeLiteralHeaderFieldNeverIndexing(index, value, buffer, out bytesWritten); + ? HPackEncoder.EncodeLiteralHeaderFieldNeverIndexingNewName(name, value, valueEncoding, buffer, out bytesWritten) + : HPackEncoder.EncodeLiteralHeaderFieldNeverIndexing(index, value, valueEncoding, buffer, out bytesWritten); } // No dynamic table. Only use the static table. if (!_allowDynamicCompression || _maxHeaderTableSize == 0 || encodingHint == HeaderEncodingHint.IgnoreIndex) { return staticTableIndex == -1 - ? HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out bytesWritten) - : HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(staticTableIndex, value, buffer, out bytesWritten); + ? HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, valueEncoding, buffer, out bytesWritten) + : HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(staticTableIndex, value, valueEncoding, buffer, out bytesWritten); } // Header is greater than the maximum table size. // Don't attempt to add dynamic header as all existing dynamic headers will be removed. - if (HeaderField.GetLength(name.Length, value.Length) > _maxHeaderTableSize) + var headerLength = HeaderField.GetLength(name.Length, valueEncoding?.GetByteCount(value) ?? value.Length); + if (headerLength > _maxHeaderTableSize) { int index = ResolveDynamicTableIndex(staticTableIndex, name); return index == -1 - ? HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out bytesWritten) - : HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(index, value, buffer, out bytesWritten); + ? HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, valueEncoding, buffer, out bytesWritten) + : HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(index, value, valueEncoding, buffer, out bytesWritten); } - return EncodeDynamicHeader(buffer, staticTableIndex, name, value, out bytesWritten); + return EncodeDynamicHeader(buffer, staticTableIndex, name, value, headerLength, valueEncoding, out bytesWritten); } private int ResolveDynamicTableIndex(int staticTableIndex, string name) @@ -110,7 +115,8 @@ private int ResolveDynamicTableIndex(int staticTableIndex, string name) return CalculateDynamicTableIndex(name); } - private bool EncodeDynamicHeader(Span buffer, int staticTableIndex, string name, string value, out int bytesWritten) + private bool EncodeDynamicHeader(Span buffer, int staticTableIndex, string name, string value, + int headerLength, Encoding? valueEncoding, out int bytesWritten) { EncoderHeaderEntry? headerField = GetEntry(name, value); if (headerField != null) @@ -122,15 +128,15 @@ private bool EncodeDynamicHeader(Span buffer, int staticTableIndex, string else { // Doesn't exist in dynamic table. Add new entry to dynamic table. - uint headerSize = (uint)HeaderField.GetLength(name.Length, value.Length); int index = ResolveDynamicTableIndex(staticTableIndex, name); bool success = index == -1 - ? HPackEncoder.EncodeLiteralHeaderFieldIndexingNewName(name, value, buffer, out bytesWritten) - : HPackEncoder.EncodeLiteralHeaderFieldIndexing(index, value, buffer, out bytesWritten); + ? HPackEncoder.EncodeLiteralHeaderFieldIndexingNewName(name, value, valueEncoding, buffer, out bytesWritten) + : HPackEncoder.EncodeLiteralHeaderFieldIndexing(index, value, valueEncoding, buffer, out bytesWritten); if (success) { + uint headerSize = (uint)headerLength; EnsureCapacity(headerSize); AddHeaderEntry(name, value, headerSize); } @@ -212,7 +218,7 @@ private void AddHeaderEntry(string name, string value, uint headerSize) EncoderHeaderEntry? oldEntry = _headerBuckets[bucketIndex]; // Attempt to reuse removed entry EncoderHeaderEntry? newEntry = PopRemovedEntry() ?? new EncoderHeaderEntry(); - newEntry.Initialize(hash, name, value, Head.Before!.Index - 1, oldEntry); + newEntry.Initialize(hash, name, value, headerSize, Head.Before!.Index - 1, oldEntry); _headerBuckets[bucketIndex] = newEntry; newEntry.AddBefore(Head); _headerTableSize += headerSize; @@ -266,7 +272,7 @@ private void PushRemovedEntry(EncoderHeaderEntry removed) { prev.Next = next; } - _headerTableSize -= eldest.CalculateSize(); + _headerTableSize -= eldest.Size; eldest.Remove(); return eldest; } diff --git a/src/Shared/Hpack/EncoderHeaderEntry.cs b/src/Shared/Hpack/EncoderHeaderEntry.cs index aa87ab7dcd91..646fa19f6ca3 100644 --- a/src/Shared/Hpack/EncoderHeaderEntry.cs +++ b/src/Shared/Hpack/EncoderHeaderEntry.cs @@ -12,6 +12,7 @@ internal class EncoderHeaderEntry // Header name and value public string? Name; public string? Value; + public uint Size; // Chained list of headers in the same bucket public EncoderHeaderEntry? Next; @@ -27,23 +28,19 @@ internal class EncoderHeaderEntry /// /// Initialize header values. An entry will be reinitialized when reused. /// - public void Initialize(int hash, string name, string value, int index, EncoderHeaderEntry? next) + public void Initialize(int hash, string name, string value, uint size, int index, EncoderHeaderEntry? next) { Debug.Assert(name != null); Debug.Assert(value != null); Name = name; Value = value; + Size = size; Index = index; Hash = hash; Next = next; } - public uint CalculateSize() - { - return (uint)HeaderField.GetLength(Name!.Length, Value!.Length); - } - /// /// Remove entry from the linked list and reset header values. /// @@ -57,6 +54,7 @@ public void Remove() Hash = 0; Name = null; Value = null; + Size = 0; } /// diff --git a/src/Shared/Http2cat/HPackHeaderWriter.cs b/src/Shared/Http2cat/HPackHeaderWriter.cs index 27772caa7231..33e66b4befcf 100644 --- a/src/Shared/Http2cat/HPackHeaderWriter.cs +++ b/src/Shared/Http2cat/HPackHeaderWriter.cs @@ -85,7 +85,7 @@ private static bool EncodeHeaders(IEnumerator> head private static bool EncodeHeader(string name, string value, Span buffer, out int length) { - return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out length); + return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, valueEncoding: null, buffer, out length); } } } diff --git a/src/Shared/ServerInfrastructure/BufferExtensions.cs b/src/Shared/ServerInfrastructure/BufferExtensions.cs index 6f3ef2461270..0d6b61779c0e 100644 --- a/src/Shared/ServerInfrastructure/BufferExtensions.cs +++ b/src/Shared/ServerInfrastructure/BufferExtensions.cs @@ -124,7 +124,7 @@ internal static void WriteAscii(ref this BufferWriter buffer, string } else { - WriteAsciiMultiWrite(ref buffer, data); + WriteEncodedMultiWrite(ref buffer, data, sourceLength, Encoding.ASCII); } } @@ -199,32 +199,61 @@ private static void WriteNumericMultiWrite(ref this BufferWriter buf buffer.Write(new ReadOnlySpan(byteBuffer, position, length)); } + internal static void WriteEncoded(ref this BufferWriter buffer, string data, Encoding encoding) + { + if (string.IsNullOrEmpty(data)) + { + return; + } + + var dest = buffer.Span; + var sourceLength = encoding.GetByteCount(data); + // Fast path, try encoding to the available memory directly + if (sourceLength <= dest.Length) + { + encoding.GetBytes(data, dest); + buffer.Advance(sourceLength); + } + else + { + WriteEncodedMultiWrite(ref buffer, data, sourceLength, encoding); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) + private static void WriteEncodedMultiWrite(ref this BufferWriter buffer, string data, int encodedLength, Encoding encoding) { - var dataLength = data.Length; - var offset = 0; + var source = data.AsSpan(); + var totalBytesUsed = 0; + var encoder = encoding.GetEncoder(); + var minBufferSize = encoding.GetMaxByteCount(1); + buffer.Ensure(minBufferSize); var bytes = buffer.Span; - do + var completed = false; + + // This may be a bug, but encoder.Convert returns completed = true for UTF7 too early. + // Therefore, we check encodedLength - totalBytesUsed too. + while (!completed || encodedLength - totalBytesUsed != 0) { - var writable = Math.Min(dataLength - offset, bytes.Length); // Zero length spans are possible, though unlikely. - // ASCII.GetBytes and .Advance will both handle them so we won't special case for them. - Encoding.ASCII.GetBytes(data.AsSpan(offset, writable), bytes); - buffer.Advance(writable); + // encoding.Convert and .Advance will both handle them so we won't special case for them. + encoder.Convert(source, bytes, flush: true, out var charsUsed, out var bytesUsed, out completed); + buffer.Advance(bytesUsed); - offset += writable; - if (offset >= dataLength) + totalBytesUsed += bytesUsed; + if (totalBytesUsed >= encodedLength) { - Debug.Assert(offset == dataLength); + Debug.Assert(totalBytesUsed == encodedLength); // Encoded everything break; } + source = source.Slice(charsUsed); + // Get new span, more to encode. - buffer.Ensure(); + buffer.Ensure(minBufferSize); bytes = buffer.Span; - } while (true); + } } private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); diff --git a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs index 67a61c3c69f3..a7f33d2c9298 100644 --- a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs +++ b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs @@ -78,7 +78,7 @@ public static bool EncodeStatusHeader(int statusCode, Span destination, ou } /// Encodes a "Literal Header Field without Indexing". - public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ @@ -97,7 +97,7 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string val if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); - if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength)) + if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; @@ -110,7 +110,7 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string val } /// Encodes a "Literal Header Field never Indexing". - public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.3 // ------------------------------------------------------ @@ -129,7 +129,7 @@ public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value if (IntegerEncoder.Encode(index, 4, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); - if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength)) + if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; @@ -142,7 +142,7 @@ public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value } /// Encodes a "Literal Header Field with Indexing". - public static bool EncodeLiteralHeaderFieldIndexing(int index, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldIndexing(int index, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ @@ -161,7 +161,7 @@ public static bool EncodeLiteralHeaderFieldIndexing(int index, string value, Spa if (IntegerEncoder.Encode(index, 6, destination, out int indexLength)) { Debug.Assert(indexLength >= 1); - if (EncodeStringLiteral(value, valueEncoding: null, destination.Slice(indexLength), out int nameLength)) + if (EncodeStringLiteral(value, valueEncoding, destination.Slice(indexLength), out int nameLength)) { bytesWritten = indexLength + nameLength; return true; @@ -209,7 +209,7 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, Span } /// Encodes a "Literal Header Field with Indexing - New Name". - public static bool EncodeLiteralHeaderFieldIndexingNewName(string name, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldIndexingNewName(string name, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ @@ -226,11 +226,11 @@ public static bool EncodeLiteralHeaderFieldIndexingNewName(string name, string v // | Value String (Length octets) | // +-------------------------------+ - return EncodeLiteralHeaderNewNameCore(0x40, name, value, destination, out bytesWritten); + return EncodeLiteralHeaderNewNameCore(0x40, name, value, valueEncoding, destination, out bytesWritten); } /// Encodes a "Literal Header Field without Indexing - New Name". - public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.2 // ------------------------------------------------------ @@ -247,11 +247,11 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, s // | Value String (Length octets) | // +-------------------------------+ - return EncodeLiteralHeaderNewNameCore(0, name, value, destination, out bytesWritten); + return EncodeLiteralHeaderNewNameCore(0, name, value, valueEncoding, destination, out bytesWritten); } /// Encodes a "Literal Header Field never Indexing - New Name". - public static bool EncodeLiteralHeaderFieldNeverIndexingNewName(string name, string value, Span destination, out int bytesWritten) + public static bool EncodeLiteralHeaderFieldNeverIndexingNewName(string name, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { // From https://tools.ietf.org/html/rfc7541#section-6.2.3 // ------------------------------------------------------ @@ -268,16 +268,16 @@ public static bool EncodeLiteralHeaderFieldNeverIndexingNewName(string name, str // | Value String (Length octets) | // +-------------------------------+ - return EncodeLiteralHeaderNewNameCore(0x10, name, value, destination, out bytesWritten); + return EncodeLiteralHeaderNewNameCore(0x10, name, value, valueEncoding, destination, out bytesWritten); } - private static bool EncodeLiteralHeaderNewNameCore(byte mask, string name, string value, Span destination, out int bytesWritten) + private static bool EncodeLiteralHeaderNewNameCore(byte mask, string name, string value, Encoding? valueEncoding, Span destination, out int bytesWritten) { if ((uint)destination.Length >= 3) { destination[0] = mask; if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) && - EncodeStringLiteral(value, valueEncoding: null, destination.Slice(1 + nameLength), out int valueLength)) + EncodeStringLiteral(value, valueEncoding, destination.Slice(1 + nameLength), out int valueLength)) { bytesWritten = 1 + nameLength + valueLength; return true; @@ -643,7 +643,7 @@ public static byte[] EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(int #endif while (true) { - if (EncodeLiteralHeaderFieldWithoutIndexing(index, value, span, out int length)) + if (EncodeLiteralHeaderFieldWithoutIndexing(index, value, valueEncoding: null, span, out int length)) { return span.Slice(0, length).ToArray(); } diff --git a/src/Shared/runtime/Http2/Hpack/HeaderField.cs b/src/Shared/runtime/Http2/Hpack/HeaderField.cs index 5127e6fb9538..aff43658c3ab 100644 --- a/src/Shared/runtime/Http2/Hpack/HeaderField.cs +++ b/src/Shared/runtime/Http2/Hpack/HeaderField.cs @@ -36,7 +36,7 @@ public override string ToString() { if (Name != null) { - return Encoding.ASCII.GetString(Name) + ": " + Encoding.ASCII.GetString(Value); + return Encoding.Latin1.GetString(Name) + ": " + Encoding.Latin1.GetString(Value); } else {