diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs index 1b412481a3badc..395a9c766306ac 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs @@ -3,7 +3,8 @@ using System.Net.Http.HPack; using System.Net.Http.QPack; -using System.Runtime.InteropServices; +using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; namespace System.Net.Http.Headers @@ -114,42 +115,6 @@ internal static class KnownHeaders private static AltSvcHeaderParser? GetAltSvcHeaderParser() => AltSvcHeaderParser.Parser; #endif - // Helper interface for making GetCandidate generic over strings, utf8, etc - private interface IHeaderNameAccessor - { - int Length { get; } - char this[int index] { get; } - } - - private readonly struct StringAccessor : IHeaderNameAccessor - { - private readonly string _string; - - public StringAccessor(string s) - { - _string = s; - } - - public int Length => _string.Length; - public char this[int index] => _string[index]; - } - - // Can't use Span here as it's unsupported. - private readonly unsafe struct BytePtrAccessor : IHeaderNameAccessor - { - private readonly byte* _p; - private readonly int _length; - - public BytePtrAccessor(byte* p, int length) - { - _p = p; - _length = length; - } - - public int Length => _length; - public char this[int index] => (char)_p[index]; - } - /// /// Find possible known header match via lookup on length and a distinguishing char for that length. /// @@ -157,20 +122,22 @@ public BytePtrAccessor(byte* p, int length) /// Matching is case-insensitive. Because of this, we do not preserve the case of the original header, /// whether from the wire or from the user explicitly setting a known header using a header name string. /// - private static KnownHeader? GetCandidate(T key) - where T : struct, IHeaderNameAccessor // Enforce struct for performance + private static KnownHeader? GetCandidate(ReadOnlySpan key) + where T : struct, INumberBase { // Lookup is performed by first switching on the header name's length, and then switching // on the most unique position in that length's string. - int length = key.Length; - switch (length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetLower(T value) => int.CreateTruncating(value) | 0x20; + + switch (key.Length) { case 2: return TE; // TE case 3: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'a': return Age; // [A]ge case 'p': return P3P; // [P]3P @@ -180,7 +147,7 @@ public BytePtrAccessor(byte* p, int length) break; case 4: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'd': return Date; // [D]ate case 'e': return ETag; // [E]Tag @@ -192,7 +159,7 @@ public BytePtrAccessor(byte* p, int length) break; case 5: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'a': return Allow; // [A]llow case 'r': return Range; // [R]ange @@ -200,7 +167,7 @@ public BytePtrAccessor(byte* p, int length) break; case 6: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'a': return Accept; // [A]ccept case 'c': return Cookie; // [C]ookie @@ -212,14 +179,14 @@ public BytePtrAccessor(byte* p, int length) break; case 7: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case ':': return PseudoStatus; // [:]status case 'a': return AltSvc; // [A]lt-Svc case 'c': return Cookie2; // [C]ookie2 case 'e': return Expires; // [E]xpires case 'r': - switch (key[3] | 0x20) + switch (GetLower(key[3])) { case 'e': return Referer; // [R]ef[e]rer case 'r': return Refresh; // [R]ef[r]esh @@ -233,7 +200,7 @@ public BytePtrAccessor(byte* p, int length) break; case 8: - switch (key[3] | 0x20) + switch (GetLower(key[3])) { case '-': return AltUsed; // Alt[-]Used case 'a': return Location; // Loc[a]tion @@ -246,7 +213,7 @@ public BytePtrAccessor(byte* p, int length) return ExpectCT; // Expect-CT case 10: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'c': return Connection; // [C]onnection case 'k': return KeepAlive; // [K]eep-Alive @@ -256,7 +223,7 @@ public BytePtrAccessor(byte* p, int length) break; case 11: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'c': return ContentMD5; // [C]ontent-MD5 case 'g': return GrpcStatus; // [g]rpc-status @@ -266,7 +233,7 @@ public BytePtrAccessor(byte* p, int length) break; case 12: - switch (key[5] | 0x20) + switch (GetLower(key[5])) { case 'd': return XMSEdgeRef; // X-MSE[d]ge-Ref case 'e': return XPoweredBy; // X-Pow[e]red-By @@ -279,12 +246,12 @@ public BytePtrAccessor(byte* p, int length) break; case 13: - switch (key[12] | 0x20) + switch (GetLower(key[12])) { case 'd': return LastModified; // Last-Modifie[d] case 'e': return ContentRange; // Content-Rang[e] case 'g': - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 's': return ServerTiming; // [S]erver-Timin[g] case 'g': return GrpcEncoding; // [g]rpc-encodin[g] @@ -299,7 +266,7 @@ public BytePtrAccessor(byte* p, int length) break; case 14: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'a': return AcceptCharset; // [A]ccept-Charset case 'c': return ContentLength; // [C]ontent-Length @@ -307,7 +274,7 @@ public BytePtrAccessor(byte* p, int length) break; case 15: - switch (key[7] | 0x20) + switch (GetLower(key[7])) { case '-': return XFrameOptions; // X-Frame[-]Options case 'e': return AcceptEncoding; // Accept-[E]ncoding @@ -319,11 +286,11 @@ public BytePtrAccessor(byte* p, int length) break; case 16: - switch (key[11] | 0x20) + switch (GetLower(key[11])) { case 'a': return ContentLocation; // Content-Loc[a]tion case 'c': - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'p': return ProxyConnection; // [P]roxy-Conne[c]tion case 'x': return XXssProtection; // [X]-XSS-Prote[c]tion @@ -337,7 +304,7 @@ public BytePtrAccessor(byte* p, int length) break; case 17: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'i': return IfModifiedSince; // [I]f-Modified-Since case 's': return SecWebSocketKey; // [S]ec-WebSocket-Key @@ -346,7 +313,7 @@ public BytePtrAccessor(byte* p, int length) break; case 18: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'p': return ProxyAuthenticate; // [P]roxy-Authenticate case 'x': return XContentDuration; // [X]-Content-Duration @@ -354,7 +321,7 @@ public BytePtrAccessor(byte* p, int length) break; case 19: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'c': return ContentDisposition; // [C]ontent-Disposition case 'i': return IfUnmodifiedSince; // [I]f-Unmodified-Since @@ -369,7 +336,7 @@ public BytePtrAccessor(byte* p, int length) return SecWebSocketVersion; // Sec-WebSocket-Version case 22: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 'a': return AccessControlMaxAge; // [A]ccess-Control-Max-Age case 's': return SecWebSocketProtocol; // [S]ec-WebSocket-Protocol @@ -384,7 +351,7 @@ public BytePtrAccessor(byte* p, int length) return SecWebSocketExtensions; // Sec-WebSocket-Extensions case 25: - switch (key[0] | 0x20) + switch (GetLower(key[0])) { case 's': return StrictTransportSecurity; // [S]trict-Transport-Security case 'u': return UpgradeInsecureRequests; // [U]pgrade-Insecure-Requests @@ -395,7 +362,7 @@ public BytePtrAccessor(byte* p, int length) return AccessControlAllowOrigin; // Access-Control-Allow-Origin case 28: - switch (key[21] | 0x20) + switch (GetLower(key[21])) { case 'h': return AccessControlAllowHeaders; // Access-Control-Allow-[H]eaders case 'm': return AccessControlAllowMethods; // Access-Control-Allow-[M]ethods @@ -412,9 +379,9 @@ public BytePtrAccessor(byte* p, int length) return null; } - internal static KnownHeader? TryGetKnownHeader(string name) + public static KnownHeader? TryGetKnownHeader(string name) { - KnownHeader? candidate = GetCandidate(new StringAccessor(name)); + KnownHeader? candidate = GetCandidate(name); if (candidate != null && StringComparer.OrdinalIgnoreCase.Equals(name, candidate.Name)) { return candidate; @@ -423,15 +390,12 @@ public BytePtrAccessor(byte* p, int length) return null; } - internal static unsafe KnownHeader? TryGetKnownHeader(ReadOnlySpan name) + public static KnownHeader? TryGetKnownHeader(ReadOnlySpan name) { - fixed (byte* p = &MemoryMarshal.GetReference(name)) + KnownHeader? candidate = GetCandidate(name); + if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name)) { - KnownHeader? candidate = GetCandidate(new BytePtrAccessor(p, name.Length)); - if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name)) - { - return candidate; - } + return candidate; } return null;