< Summary

Information
Class: System.Net.Http.Headers.HeaderDescriptor
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HeaderDescriptor.cs
Line coverage
15%
Covered lines: 30
Uncovered lines: 159
Coverable lines: 189
Total lines: 336
Line coverage: 15.8%
Branch coverage
6%
Covered branches: 10
Total branches: 147
Branch coverage: 6.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor(...)100%11100%
.ctor(...)50%44100%
Equals(...)100%110%
Equals(...)100%22100%
GetHashCode()0%220%
Equals(...)100%110%
TryGet(...)25%4457.14%
TryGet(...)0%440%
TryGetStaticQPackHeader(...)0%220%
AsCustomHeader()100%11100%
GetHeaderValue(...)0%24240%
GetKnownContentType(...)0%93930%
TryDecodeUtf8(...)0%220%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HeaderDescriptor.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Text;
 8using System.Text.Unicode;
 9
 10namespace System.Net.Http.Headers
 11{
 12    // This struct represents a particular named header --
 13    // if the header is one of our known headers, then it contains a reference to the KnownHeader object;
 14    // otherwise, for custom headers, it just contains a string for the header name.
 15    // Use HeaderDescriptor.TryGet to resolve an arbitrary header name to a HeaderDescriptor.
 16    internal readonly struct HeaderDescriptor : IEquatable<HeaderDescriptor>
 17    {
 018        private static readonly SearchValues<byte> s_dangerousCharacterBytes = SearchValues.Create((byte)'\0', (byte)'\r
 19
 20        /// <summary>
 21        /// Either a <see cref="KnownHeader"/> or <see cref="string"/>.
 22        /// </summary>
 23        private readonly object _descriptor;
 24
 25        public HeaderDescriptor(KnownHeader knownHeader)
 7663126        {
 7663127            _descriptor = knownHeader;
 7663128        }
 29
 30        // This should not be used directly; use static TryGet below
 31        internal HeaderDescriptor(string headerName, bool customHeader = false)
 2088132        {
 2088133            Debug.Assert(customHeader || KnownHeaders.TryGetKnownHeader(headerName) is null, $"The {nameof(KnownHeader)}
 2088134            _descriptor = headerName;
 2088135        }
 36
 7389537        public string Name => _descriptor is KnownHeader header ? header.Name : (_descriptor as string)!;
 100492238        public HttpHeaderParser? Parser => (_descriptor as KnownHeader)?.Parser;
 11036239        public HttpHeaderType HeaderType => _descriptor is KnownHeader knownHeader ? knownHeader.HeaderType : HttpHeader
 040        public KnownHeader? KnownHeader => _descriptor as KnownHeader;
 41
 042        public bool Equals(KnownHeader other) => ReferenceEquals(_descriptor, other);
 43
 44        public bool Equals(HeaderDescriptor other)
 1374445        {
 1374446            if (_descriptor is string headerName)
 481947            {
 481948                return string.Equals(headerName, other._descriptor as string, StringComparison.OrdinalIgnoreCase);
 49            }
 50            else
 892551            {
 892552                return ReferenceEquals(_descriptor, other._descriptor);
 53            }
 1374454        }
 55
 056        public override int GetHashCode() => _descriptor is KnownHeader knownHeader ? knownHeader.GetHashCode() : String
 57
 058        public override bool Equals(object? obj) => throw new InvalidOperationException();   // Ensure this is never cal
 59
 60        // Returns false for invalid header name.
 61        public static bool TryGet(string headerName, out HeaderDescriptor descriptor)
 7663162        {
 7663163            Debug.Assert(!string.IsNullOrEmpty(headerName));
 64
 7663165            KnownHeader? knownHeader = KnownHeaders.TryGetKnownHeader(headerName);
 7663166            if (knownHeader != null)
 7663167            {
 7663168                descriptor = new HeaderDescriptor(knownHeader);
 7663169                return true;
 70            }
 71
 072            if (!HttpRuleParser.IsToken(headerName))
 073            {
 074                descriptor = default(HeaderDescriptor);
 075                return false;
 76            }
 77
 078            descriptor = new HeaderDescriptor(headerName);
 079            return true;
 7663180        }
 81
 82        // Returns false for invalid header name.
 83        public static bool TryGet(ReadOnlySpan<byte> headerName, out HeaderDescriptor descriptor)
 084        {
 085            Debug.Assert(headerName.Length > 0);
 86
 087            KnownHeader? knownHeader = KnownHeaders.TryGetKnownHeader(headerName);
 088            if (knownHeader != null)
 089            {
 090                descriptor = new HeaderDescriptor(knownHeader);
 091                return true;
 92            }
 93
 094            if (!HttpRuleParser.IsToken(headerName))
 095            {
 096                descriptor = default(HeaderDescriptor);
 097                return false;
 98            }
 99
 0100            descriptor = new HeaderDescriptor(HttpRuleParser.GetTokenString(headerName));
 0101            return true;
 0102        }
 103
 104        internal static bool TryGetStaticQPackHeader(int index, out HeaderDescriptor descriptor, [NotNullWhen(true)] out
 0105        {
 0106            Debug.Assert(index >= 0);
 107
 108            // Micro-opt: store field to variable to prevent Length re-read and use unsigned to avoid bounds check.
 0109            (HeaderDescriptor descriptor, string value)[] qpackStaticTable = QPackStaticTable.HeaderLookup;
 0110            Debug.Assert(qpackStaticTable.Length == 99);
 111
 0112            uint uindex = (uint)index;
 113
 0114            if (uindex < (uint)qpackStaticTable.Length)
 0115            {
 0116                (descriptor, knownValue) = qpackStaticTable[uindex];
 0117                return true;
 118            }
 119            else
 0120            {
 0121                descriptor = default;
 0122                knownValue = null;
 0123                return false;
 124            }
 0125        }
 126
 127        public HeaderDescriptor AsCustomHeader()
 20881128        {
 20881129            Debug.Assert(_descriptor is KnownHeader);
 20881130            Debug.Assert(HeaderType != HttpHeaderType.Custom);
 20881131            return new HeaderDescriptor(Name, customHeader: true);
 20881132        }
 133
 134        public string GetHeaderValue(ReadOnlySpan<byte> headerValue, Encoding? valueEncoding)
 0135        {
 0136            if (headerValue.Length == 0)
 0137            {
 0138                return string.Empty;
 139            }
 140
 141            // If it's a known header value, use the known value instead of allocating a new string.
 0142            if (_descriptor is KnownHeader knownHeader)
 0143            {
 0144                if (knownHeader.KnownValues is string[] knownValues)
 0145                {
 0146                    for (int i = 0; i < knownValues.Length; i++)
 0147                    {
 0148                        if (Ascii.Equals(headerValue, knownValues[i]))
 0149                        {
 0150                            return knownValues[i];
 151                        }
 0152                    }
 0153                }
 154
 0155                if (knownHeader == KnownHeaders.ContentType)
 0156                {
 0157                    string? contentType = GetKnownContentType(headerValue);
 0158                    if (contentType != null)
 0159                    {
 0160                        return contentType;
 161                    }
 0162                }
 0163                else if (knownHeader == KnownHeaders.Location)
 0164                {
 165                    // Normally Location should be in ISO-8859-1 but occasionally some servers respond with UTF-8.
 166                    // If the user set the ResponseHeaderEncodingSelector, we give that priority instead.
 0167                    if (valueEncoding is null && TryDecodeUtf8(headerValue, out string? decoded))
 0168                    {
 0169                        return decoded;
 170                    }
 0171                }
 0172            }
 173
 0174            string value = (valueEncoding ?? HttpRuleParser.DefaultHttpEncoding).GetString(headerValue);
 0175            if (headerValue.ContainsAny(s_dangerousCharacterBytes))
 0176            {
 177                // Depending on the encoding, 'value' may contain a dangerous character.
 178                // We are replacing them with SP to conform with https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5
 179                // This is a low-occurrence corner case, so we don't care about the cost of Replace() and the extra allo
 0180                value = value.Replace('\0', ' ').Replace('\r', ' ').Replace('\n', ' ');
 0181            }
 182
 0183            return value;
 0184        }
 185
 186        internal static string? GetKnownContentType(ReadOnlySpan<byte> contentTypeValue)
 0187        {
 0188            string? candidate = null;
 0189            switch (contentTypeValue.Length)
 190            {
 191                case 8:
 0192                    switch (contentTypeValue[7])
 193                    {
 0194                        case (byte)'l': candidate = "text/xml"; break; // text/xm[l]
 0195                        case (byte)'s': candidate = "text/css"; break; // text/cs[s]
 0196                        case (byte)'v': candidate = "text/csv"; break; // text/cs[v]
 197                    }
 0198                    break;
 199
 200                case 9:
 0201                    switch (contentTypeValue[6])
 202                    {
 0203                        case (byte)'g': candidate = "image/gif"; break; // image/[g]if
 0204                        case (byte)'p': candidate = "image/png"; break; // image/[p]ng
 0205                        case (byte)'t': candidate = "text/html"; break; // text/h[t]ml
 206                    }
 0207                    break;
 208
 209                case 10:
 0210                    switch (contentTypeValue[6])
 211                    {
 0212                        case (byte)'l': candidate = "text/plain"; break; // text/p[l]ain
 0213                        case (byte)'j': candidate = "image/jpeg"; break; // image/[j]peg
 0214                        case (byte)'w': candidate = "image/webp"; break; // image/[w]ebp
 215                    }
 0216                    break;
 217
 218                case 13:
 0219                    candidate = "image/svg+xml"; // image/svg+xml
 0220                    break;
 221
 222                case 15:
 0223                    switch (contentTypeValue[12])
 224                    {
 0225                        case (byte)'p': candidate = "application/pdf"; break; // application/[p]df
 0226                        case (byte)'x': candidate = "application/xml"; break; // application/[x]ml
 0227                        case (byte)'z': candidate = "application/zip"; break; // application/[z]ip
 0228                        case (byte)'i': candidate = "text/javascript"; break; // text/javascr[i]pt
 229                    }
 0230                    break;
 231
 232                case 16:
 0233                    switch (contentTypeValue[12])
 234                    {
 0235                        case (byte)'g': candidate = "application/grpc"; break; // application/[g]rpc
 0236                        case (byte)'j': candidate = "application/json"; break; // application/[j]son
 237                    }
 0238                    break;
 239
 240                case 17:
 0241                    candidate = "text/event-stream"; // text/event-stream
 0242                    break;
 243
 244                case 19:
 0245                    candidate = "multipart/form-data"; // multipart/form-data
 0246                    break;
 247
 248                case 22:
 0249                    candidate = "application/javascript"; // application/javascript
 0250                    break;
 251
 252                case 23:
 0253                    switch (contentTypeValue[18])
 254                    {
 0255                        case (byte)'u': candidate = "text/html;charset=utf-8"; break; // text/html;charset=[u]tf-8
 0256                        case (byte)'U': candidate = "text/html;charset=UTF-8"; break; // text/html;charset=[U]TF-8
 257                    }
 0258                    break;
 259
 260                case 24:
 0261                    switch (contentTypeValue[10] ^ contentTypeValue[19])
 262                    {
 0263                        case 'n' ^ 't': candidate = "application/octet-stream"; break; // applicatio[n]/octet-s[t]ream
 0264                        case ' ' ^ 'u': candidate = "text/html; charset=utf-8"; break; // text/html;[ ]charset=[u]tf-8
 0265                        case ' ' ^ 'U': candidate = "text/html; charset=UTF-8"; break; // text/html;[ ]charset=[U]TF-8
 0266                        case ';' ^ 'u': candidate = "text/plain;charset=utf-8"; break; // text/plain[;]charset=[u]tf-8
 0267                        case ';' ^ 'U': candidate = "text/plain;charset=UTF-8"; break; // text/plain[;]charset=[U]TF-8
 268                    }
 0269                    break;
 270
 271                case 25:
 0272                    switch (contentTypeValue[20])
 273                    {
 0274                        case (byte)'u': candidate = "text/plain; charset=utf-8"; break; // text/plain; charset=[u]tf-8
 0275                        case (byte)'U': candidate = "text/plain; charset=UTF-8"; break; // text/plain; charset=[U]TF-8
 276                    }
 0277                    break;
 278
 279                case 29:
 0280                    switch (contentTypeValue[19])
 281                    {
 0282                        case (byte)'I': candidate = "text/html; charset=ISO-8859-1"; break; // text/html; charset=[I]SO-
 0283                        case (byte)'i': candidate = "text/html; charset=iso-8859-1"; break; // text/html; charset=[i]so-
 284                    }
 0285                    break;
 286
 287                case 30:
 0288                    switch (contentTypeValue[25])
 289                    {
 0290                        case (byte)'u': candidate = "text/javascript; charset=utf-8"; break; // text/javascript; charset
 0291                        case (byte)'U': candidate = "text/javascript; charset=UTF-8"; break; // text/javascript; charset
 292                    }
 0293                    break;
 294
 295                case 31:
 0296                    candidate = "application/json; charset=utf-8"; // application/json; charset=utf-8
 0297                    break;
 298
 299                case 33:
 0300                    candidate = "application/x-www-form-urlencoded"; // application/x-www-form-urlencoded
 0301                    break;
 302            }
 303
 0304            Debug.Assert(candidate is null || candidate.Length == contentTypeValue.Length);
 305
 0306            return candidate != null && Ascii.Equals(contentTypeValue, candidate) ?
 0307                candidate :
 0308                null;
 0309        }
 310
 311        private static bool TryDecodeUtf8(ReadOnlySpan<byte> input, [NotNullWhen(true)] out string? decoded)
 0312        {
 0313            char[] rented = ArrayPool<char>.Shared.Rent(input.Length);
 314
 315            try
 0316            {
 0317                if (Utf8.ToUtf16(input, rented, out _, out int charsWritten, replaceInvalidSequences: false) == Operatio
 0318                {
 0319                    decoded = new string(rented, 0, charsWritten);
 0320                    return true;
 321                }
 0322            }
 323            finally
 0324            {
 0325                ArrayPool<char>.Shared.Return(rented);
 0326            }
 327
 0328            decoded = null;
 0329            return false;
 0330        }
 331
 0332        public string Separator => Parser is { } parser ? parser.Separator : HttpHeaderParser.DefaultSeparator;
 333
 0334        public byte[] SeparatorBytes => Parser is { } parser ? parser.SeparatorBytes : HttpHeaderParser.DefaultSeparator
 335    }
 336}