Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

using System.Net.Http.HPack;
using System.Net.Http.QPack;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace System.Net.Http.Headers
Expand Down Expand Up @@ -118,29 +117,63 @@ 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];
}

/// <summary>
/// Find possible known header match via lookup on length and a distinguishing char for that length.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
private static KnownHeader? GetCandidate<T>(ReadOnlySpan<T> key)
where T : struct, INumberBase<T>
private static KnownHeader? GetCandidate<T>(T key)
where T : struct, IHeaderNameAccessor // Enforce struct for performance
{
// 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.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int GetLower(T value) => int.CreateTruncating(value) | 0x20;

switch (key.Length)
int length = key.Length;
switch (length)
{
case 2:
return TE; // TE

case 3:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'a': return Age; // [A]ge
case 'p': return P3P; // [P]3P
Expand All @@ -150,7 +183,7 @@ internal static class KnownHeaders
break;

case 4:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'd': return Date; // [D]ate
case 'e': return ETag; // [E]Tag
Expand All @@ -162,15 +195,15 @@ internal static class KnownHeaders
break;

case 5:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'a': return Allow; // [A]llow
case 'r': return Range; // [R]ange
}
break;

case 6:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'a': return Accept; // [A]ccept
case 'c': return Cookie; // [C]ookie
Expand All @@ -182,14 +215,14 @@ internal static class KnownHeaders
break;

case 7:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
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 (GetLower(key[3]))
switch (key[3] | 0x20)
{
case 'e': return Referer; // [R]ef[e]rer
case 'r': return Refresh; // [R]ef[r]esh
Expand All @@ -203,7 +236,7 @@ internal static class KnownHeaders
break;

case 8:
switch (GetLower(key[3]))
switch (key[3] | 0x20)
{
case '-': return AltUsed; // Alt[-]Used
case 'a': return Location; // Loc[a]tion
Expand All @@ -216,7 +249,7 @@ internal static class KnownHeaders
return ExpectCT; // Expect-CT

case 10:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'c': return Connection; // [C]onnection
case 'k': return KeepAlive; // [K]eep-Alive
Expand All @@ -226,7 +259,7 @@ internal static class KnownHeaders
break;

case 11:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'c': return ContentMD5; // [C]ontent-MD5
case 'g': return GrpcStatus; // [g]rpc-status
Expand All @@ -237,7 +270,7 @@ internal static class KnownHeaders
break;

case 12:
switch (GetLower(key[5]))
switch (key[5] | 0x20)
{
case 'd': return XMSEdgeRef; // X-MSE[d]ge-Ref
case 'e': return XPoweredBy; // X-Pow[e]red-By
Expand All @@ -250,12 +283,12 @@ internal static class KnownHeaders
break;

case 13:
switch (GetLower(key[12]))
switch (key[12] | 0x20)
{
case 'd': return LastModified; // Last-Modifie[d]
case 'e': return ContentRange; // Content-Rang[e]
case 'g':
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 's': return ServerTiming; // [S]erver-Timin[g]
case 'g': return GrpcEncoding; // [g]rpc-encodin[g]
Expand All @@ -270,15 +303,15 @@ internal static class KnownHeaders
break;

case 14:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'a': return AcceptCharset; // [A]ccept-Charset
case 'c': return ContentLength; // [C]ontent-Length
}
break;

case 15:
switch (GetLower(key[7]))
switch (key[7] | 0x20)
{
case '-': return XFrameOptions; // X-Frame[-]Options
case 'e': return AcceptEncoding; // Accept-[E]ncoding
Expand All @@ -290,11 +323,11 @@ internal static class KnownHeaders
break;

case 16:
switch (GetLower(key[11]))
switch (key[11] | 0x20)
{
case 'a': return ContentLocation; // Content-Loc[a]tion
case 'c':
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'p': return ProxyConnection; // [P]roxy-Conne[c]tion
case 'x': return XXssProtection; // [X]-XSS-Prote[c]tion
Expand All @@ -308,7 +341,7 @@ internal static class KnownHeaders
break;

case 17:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'i': return IfModifiedSince; // [I]f-Modified-Since
case 's': return SecWebSocketKey; // [S]ec-WebSocket-Key
Expand All @@ -317,15 +350,15 @@ internal static class KnownHeaders
break;

case 18:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'p': return ProxyAuthenticate; // [P]roxy-Authenticate
case 'x': return XContentDuration; // [X]-Content-Duration
}
break;

case 19:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'c': return ContentDisposition; // [C]ontent-Disposition
case 'i': return IfUnmodifiedSince; // [I]f-Unmodified-Since
Expand All @@ -341,7 +374,7 @@ internal static class KnownHeaders
return SecWebSocketVersion; // Sec-WebSocket-Version

case 22:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 'a': return AccessControlMaxAge; // [A]ccess-Control-Max-Age
case 's': return SecWebSocketProtocol; // [S]ec-WebSocket-Protocol
Expand All @@ -356,7 +389,7 @@ internal static class KnownHeaders
return SecWebSocketExtensions; // Sec-WebSocket-Extensions

case 25:
switch (GetLower(key[0]))
switch (key[0] | 0x20)
{
case 's': return StrictTransportSecurity; // [S]trict-Transport-Security
case 'u': return UpgradeInsecureRequests; // [U]pgrade-Insecure-Requests
Expand All @@ -367,7 +400,7 @@ internal static class KnownHeaders
return AccessControlAllowOrigin; // Access-Control-Allow-Origin

case 28:
switch (GetLower(key[21]))
switch (key[21] | 0x20)
{
case 'h': return AccessControlAllowHeaders; // Access-Control-Allow-[H]eaders
case 'm': return AccessControlAllowMethods; // Access-Control-Allow-[M]ethods
Expand All @@ -385,9 +418,9 @@ internal static class KnownHeaders
return null;
}

public static KnownHeader? TryGetKnownHeader(string name)
internal static KnownHeader? TryGetKnownHeader(string name)
{
KnownHeader? candidate = GetCandidate<char>(name);
KnownHeader? candidate = GetCandidate(new StringAccessor(name));
if (candidate != null && StringComparer.OrdinalIgnoreCase.Equals(name, candidate.Name))
{
return candidate;
Expand All @@ -396,12 +429,15 @@ internal static class KnownHeaders
return null;
}

public static KnownHeader? TryGetKnownHeader(ReadOnlySpan<byte> name)
internal static unsafe KnownHeader? TryGetKnownHeader(ReadOnlySpan<byte> name)
{
KnownHeader? candidate = GetCandidate(name);
if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name))
fixed (byte* p = &MemoryMarshal.GetReference(name))
{
return candidate;
KnownHeader? candidate = GetCandidate(new BytePtrAccessor(p, name.Length));
if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name))
{
return candidate;
}
}

return null;
Expand Down
Loading