< Summary

Information
Class: System.Net.Http.Headers.HeaderEntry
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HttpHeaders.cs
Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 1664
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Headers\HttpHeaders.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.Collections;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Runtime.CompilerServices;
 9using System.Runtime.InteropServices;
 10using System.Text;
 11using System.Threading;
 12
 13namespace System.Net.Http.Headers
 14{
 15    /// <summary>
 16    /// Key/value pairs of headers. The value is either a raw <see cref="string"/> or a <see cref="HttpHeaders.HeaderSto
 17    /// We're using a custom type instead of <see cref="KeyValuePair{TKey, TValue}"/> because we need ref access to fiel
 18    /// </summary>
 19    internal struct HeaderEntry
 20    {
 21        public HeaderDescriptor Key;
 22        public object Value;
 23
 24        public HeaderEntry(HeaderDescriptor key, object value)
 854125        {
 854126            Key = key;
 854127            Value = value;
 854128        }
 29    }
 30
 31    public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
 32    {
 33        // This type is used to store a collection of headers in 'headerStore':
 34        // - A header can have multiple values.
 35        // - A header can have an associated parser which is able to parse the raw string value into a strongly typed ob
 36        // - If a header has an associated parser and the provided raw value can't be parsed, the value is considered
 37        //   invalid. Invalid values are stored if added using TryAddWithoutValidation(). If the value was added using A
 38        //   Add() will throw FormatException.
 39        // - Since parsing header values is expensive and users usually only care about a few headers, header values are
 40        //   lazily initialized.
 41        //
 42        // Given the properties above, a header value can have three states:
 43        // - 'raw': The header value was added using TryAddWithoutValidation() and it wasn't parsed yet.
 44        // - 'parsed': The header value was successfully parsed. It was either added using Add() where the value was par
 45        //   immediately, or if added using TryAddWithoutValidation() a user already accessed a property/method triggeri
 46        //   value to be parsed.
 47        // - 'invalid': The header value was parsed, but parsing failed because the value is invalid. Storing invalid va
 48        //   allows users to still retrieve the value (by calling GetValues()), but it will not be exposed as strongly t
 49        //   object. E.g. the client receives a response with the following header: 'Via: 1.1 proxy, invalid'
 50        //   - HttpHeaders.GetValues() will return "1.1 proxy", "invalid"
 51        //   - HttpResponseHeaders.Via collection will only contain one ViaHeaderValue object with value "1.1 proxy"
 52
 53        /// <summary>Either a <see cref="HeaderEntry"/> array or a Dictionary&lt;<see cref="HeaderDescriptor"/>, <see cr
 54        private object? _headerStore;
 55        private int _count;
 56
 57        private readonly HttpHeaderType _allowedHeaderTypes;
 58        private readonly HttpHeaderType _treatAsCustomHeaderTypes;
 59
 60        protected HttpHeaders()
 61            : this(HttpHeaderType.All, HttpHeaderType.None)
 62        {
 63        }
 64
 65        internal HttpHeaders(HttpHeaderType allowedHeaderTypes, HttpHeaderType treatAsCustomHeaderTypes)
 66        {
 67            // Should be no overlap
 68            Debug.Assert((allowedHeaderTypes & treatAsCustomHeaderTypes) == 0);
 69
 70            _allowedHeaderTypes = allowedHeaderTypes & ~HttpHeaderType.NonTrailing;
 71            _treatAsCustomHeaderTypes = treatAsCustomHeaderTypes & ~HttpHeaderType.NonTrailing;
 72        }
 73
 74        /// <summary>Gets a view of the contents of this headers collection that does not parse nor validate the data up
 75        public HttpHeadersNonValidated NonValidated => new HttpHeadersNonValidated(this);
 76
 77        public void Add(string name, string? value) => Add(GetHeaderDescriptor(name), value);
 78
 79        internal void Add(HeaderDescriptor descriptor, string? value)
 80        {
 81            if (descriptor.Parser is null)
 82            {
 83                // If the header has no parser, we only have to check for new lines or null.
 84                CheckIsAllowedHeaderName(descriptor);
 85                CheckContainsNewLineOrNull(value);
 86                TryAddWithoutValidation(descriptor, value);
 87                return;
 88            }
 89
 90            // We don't use GetOrCreateHeaderInfo() here, since this would create a new header in the store. If parsing
 91            // the value then throws, we would have to remove the header from the store again. So just get a
 92            // HeaderStoreItemInfo object and try to parse the value. If it works, we'll add the header.
 93            PrepareHeaderInfoForAdd(descriptor, out HeaderStoreItemInfo info, out bool addToStore);
 94            ParseAndAddValue(descriptor, info, value);
 95
 96            // If we get here, then the value could be parsed correctly. If we created a new HeaderStoreItemInfo, add
 97            // it to the store if we added at least one value.
 98            if (addToStore && (info.ParsedAndInvalidValues != null))
 99            {
 100                info.AssertContainsNoInvalidValues();
 101                Debug.Assert(!Contains(descriptor));
 102                AddEntryToStore(new HeaderEntry(descriptor, info));
 103            }
 104        }
 105
 106        public void Add(string name, IEnumerable<string?> values) => Add(GetHeaderDescriptor(name), values);
 107
 108        internal void Add(HeaderDescriptor descriptor, IEnumerable<string?> values)
 109        {
 110            ArgumentNullException.ThrowIfNull(values);
 111
 112            // It's relatively common to only add a single value with this overload, especially when copying
 113            // between HttpHeaders collections. Avoid boxing the enumerator and possibly a HeaderStoreItemInfo
 114            // allocation by deferring to the overload for a single value instead.
 115            if (values is IList<string?> { Count: 1 } valuesList)
 116            {
 117                Add(descriptor, valuesList[0]);
 118                return;
 119            }
 120
 121            PrepareHeaderInfoForAdd(descriptor, out HeaderStoreItemInfo info, out bool addToStore);
 122
 123            try
 124            {
 125                // Note that if the first couple of values are valid followed by an invalid value, the valid values
 126                // will be added to the store before the exception for the invalid value is thrown.
 127                if (descriptor.Parser is null)
 128                {
 129                    foreach (string? value in values)
 130                    {
 131                        // If the header has no parser, we only have to check for new lines or null.
 132                        CheckContainsNewLineOrNull(value);
 133                        AddParsedValue(info, value ?? string.Empty);
 134                    }
 135                }
 136                else
 137                {
 138                    foreach (string? value in values)
 139                    {
 140                        ParseAndAddValue(descriptor, info, value);
 141                    }
 142                }
 143            }
 144            finally
 145            {
 146                // Even if one of the values was invalid, make sure we add the header for the valid ones. We need to be
 147                // consistent here: If values get added to an _existing_ header, then all values until the invalid one
 148                // get added. Same here: If multiple values get added to a _new_ header, make sure the header gets added
 149                // with the valid values.
 150                // However, if all values for a _new_ header were invalid, then don't add the header.
 151                if (addToStore && (info.ParsedAndInvalidValues != null))
 152                {
 153                    info.AssertContainsNoInvalidValues();
 154                    Debug.Assert(!Contains(descriptor));
 155                    AddEntryToStore(new HeaderEntry(descriptor, info));
 156                }
 157            }
 158        }
 159
 160        public bool TryAddWithoutValidation(string name, string? value) =>
 161            TryGetHeaderDescriptor(name, out HeaderDescriptor descriptor) &&
 162            TryAddWithoutValidation(descriptor, value);
 163
 164        internal bool TryAddWithoutValidation(HeaderDescriptor descriptor, string? value)
 165        {
 166            // Normalize null values to be empty values, which are allowed. If the user adds multiple
 167            // null/empty values, all of them are added to the collection. This will result in delimiter-only
 168            // values, e.g. adding two null-strings (or empty, or whitespace-only) results in "My-Header: ,".
 169            value ??= string.Empty;
 170
 171            ref object? storeValueRef = ref GetValueRefOrAddDefault(descriptor);
 172            object? currentValue = storeValueRef;
 173
 174            if (currentValue is null)
 175            {
 176                storeValueRef = value;
 177            }
 178            else
 179            {
 180                if (currentValue is not HeaderStoreItemInfo info)
 181                {
 182                    // The header store contained a single raw string value, so promote it
 183                    // to being a HeaderStoreItemInfo and add to it.
 184                    Debug.Assert(currentValue is string);
 185                    storeValueRef = info = new HeaderStoreItemInfo() { RawValue = currentValue };
 186                }
 187
 188                AddRawValue(info, value);
 189            }
 190
 191            return true;
 192        }
 193
 194        public bool TryAddWithoutValidation(string name, IEnumerable<string?> values) =>
 195            TryGetHeaderDescriptor(name, out HeaderDescriptor descriptor) &&
 196            TryAddWithoutValidation(descriptor, values);
 197
 198        internal bool TryAddWithoutValidation(HeaderDescriptor descriptor, IEnumerable<string?> values)
 199        {
 200            ArgumentNullException.ThrowIfNull(values);
 201
 202            if (values is IList<string?> valuesList)
 203            {
 204                int count = valuesList.Count;
 205
 206                if (count > 0)
 207                {
 208                    // The store value is either a string (a single unparsed value) or a HeaderStoreItemInfo.
 209                    // The RawValue on HeaderStoreItemInfo can likewise be either a single string or a List<string>.
 210
 211                    ref object? storeValueRef = ref GetValueRefOrAddDefault(descriptor);
 212                    object? storeValue = storeValueRef;
 213
 214                    // If the storeValue was already set or we're adding more than 1 value,
 215                    // we'll have to store the values in a List<string> on HeaderStoreItemInfo.
 216                    if (storeValue is not null || count > 1)
 217                    {
 218                        if (storeValue is not HeaderStoreItemInfo info)
 219                        {
 220                            storeValueRef = info = new HeaderStoreItemInfo { RawValue = storeValue };
 221                        }
 222
 223                        object? rawValue = info.RawValue;
 224                        if (rawValue is not List<string> rawValues)
 225                        {
 226                            info.RawValue = rawValues = new List<string>();
 227
 228                            if (rawValue != null)
 229                            {
 230                                rawValues.EnsureCapacity(count + 1);
 231                                rawValues.Add((string)rawValue);
 232                            }
 233                        }
 234
 235                        rawValues.EnsureCapacity(rawValues.Count + count);
 236
 237                        for (int i = 0; i < count; i++)
 238                        {
 239                            rawValues.Add(valuesList[i] ?? string.Empty);
 240                        }
 241                    }
 242                    else
 243                    {
 244                        // We're adding a single value to a new header entry. We can store the unparsed value as-is.
 245                        storeValueRef = valuesList[0] ?? string.Empty;
 246                    }
 247                }
 248            }
 249            else
 250            {
 251                foreach (string? value in values)
 252                {
 253                    TryAddWithoutValidation(descriptor, value ?? string.Empty);
 254                }
 255            }
 256
 257            return true;
 258        }
 259
 260        public IEnumerable<string> GetValues(string name) => GetValues(GetHeaderDescriptor(name));
 261
 262        internal IEnumerable<string> GetValues(HeaderDescriptor descriptor)
 263        {
 264            if (TryGetValues(descriptor, out IEnumerable<string>? values))
 265            {
 266                return values;
 267            }
 268
 269            throw new InvalidOperationException(SR.net_http_headers_not_found);
 270        }
 271
 272        public bool TryGetValues(string name, [NotNullWhen(true)] out IEnumerable<string>? values)
 273        {
 274            if (TryGetHeaderDescriptor(name, out HeaderDescriptor descriptor))
 275            {
 276                return TryGetValues(descriptor, out values);
 277            }
 278
 279            values = null;
 280            return false;
 281        }
 282
 283        internal bool TryGetValues(HeaderDescriptor descriptor, [NotNullWhen(true)] out IEnumerable<string>? values)
 284        {
 285            ref object storeValueRef = ref GetValueRefOrNullRef(descriptor);
 286            if (!Unsafe.IsNullRef(ref storeValueRef))
 287            {
 288                object value = storeValueRef;
 289
 290                if (value is not HeaderStoreItemInfo info)
 291                {
 292                    if (descriptor.Parser is null)
 293                    {
 294                        // This is a custom header without a known parser, so unparsed values won't change.
 295                        // Avoid allocating the HeaderStoreItemInfo and just return the raw value as-is.
 296                        values = new string[] { (string)value };
 297                        return true;
 298                    }
 299
 300                    info = ReplaceWithHeaderStoreItemInfo(ref storeValueRef, value);
 301                }
 302
 303                ParseRawHeaderValues(descriptor, info);
 304                values = GetStoreValuesAsStringArray(descriptor, info);
 305                return true;
 306            }
 307
 308            values = null;
 309            return false;
 310        }
 311
 312        public bool Contains(string name) => Contains(GetHeaderDescriptor(name));
 313
 314        public override string ToString()
 315        {
 316            var vsb = new ValueStringBuilder(stackalloc char[512]);
 317            Dump(ref vsb, indentLines: false);
 318            return vsb.ToString();
 319        }
 320
 321        internal void Dump(ref ValueStringBuilder builder, bool indentLines)
 322        {
 323            // Return all headers as string similar to:
 324            // HeaderName1: Value1, Value2
 325            // HeaderName2: Value1
 326            // ...
 327
 328            foreach (HeaderEntry entry in GetEntries())
 329            {
 330                if (indentLines)
 331                {
 332                    builder.Append("  ");
 333                }
 334
 335                builder.Append(entry.Key.Name);
 336                builder.Append(": ");
 337
 338                GetStoreValuesAsStringOrStringArray(entry.Key, entry.Value, out string? singleValue, out string[]? multi
 339                Debug.Assert(singleValue is not null ^ multiValue is not null);
 340
 341                if (singleValue is not null)
 342                {
 343                    builder.Append(singleValue);
 344                }
 345                else
 346                {
 347                    // Note that if we get multiple values for a header that doesn't support multiple values, we'll
 348                    // just separate the values using a comma (default separator).
 349                    string separator = entry.Key.Separator;
 350
 351                    Debug.Assert(multiValue is not null && multiValue.Length > 0);
 352                    builder.Append(multiValue[0]);
 353                    for (int i = 1; i < multiValue.Length; i++)
 354                    {
 355                        builder.Append(separator);
 356                        builder.Append(multiValue[i]);
 357                    }
 358                }
 359
 360                builder.Append(Environment.NewLine);
 361            }
 362        }
 363
 364        internal string GetHeaderString(HeaderDescriptor descriptor)
 365        {
 366            if (TryGetHeaderValue(descriptor, out object? info))
 367            {
 368                GetStoreValuesAsStringOrStringArray(descriptor, info, out string? singleValue, out string[]? multiValue)
 369                Debug.Assert(singleValue is not null ^ multiValue is not null);
 370
 371                if (singleValue is not null)
 372                {
 373                    return singleValue;
 374                }
 375
 376                // Note that if we get multiple values for a header that doesn't support multiple values, we'll
 377                // just separate the values using a comma (default separator).
 378                return string.Join(descriptor.Separator, multiValue!);
 379            }
 380
 381            return string.Empty;
 382        }
 383
 384        #region IEnumerable<KeyValuePair<string, IEnumerable<string>>> Members
 385
 386        public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator() => _count == 0 ?
 387                ((IEnumerable<KeyValuePair<string, IEnumerable<string>>>)Array.Empty<KeyValuePair<string, IEnumerable<st
 388                GetEnumeratorCore();
 389
 390        private IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumeratorCore()
 391        {
 392            Debug.Assert(_headerStore is not null);
 393
 394            HeaderEntry[]? entries = GetEntriesArray();
 395            Debug.Assert(_count != 0 && entries is not null, "Caller should have validated the collection is not empty")
 396
 397            for (int i = 0; i < _count; i++)
 398            {
 399                HeaderEntry entry = entries[i];
 400
 401                if (entry.Value is not HeaderStoreItemInfo info)
 402                {
 403                    if (entry.Key.Parser is null)
 404                    {
 405                        // This is a custom header without a known parser, so unparsed values won't change.
 406                        // Avoid allocating the HeaderStoreItemInfo and just return the raw value as-is.
 407                        yield return new KeyValuePair<string, IEnumerable<string>>(entry.Key.Name, new string[] { (strin
 408                        continue;
 409                    }
 410
 411                    // To retain consistent semantics, we need to upgrade a raw string to a HeaderStoreItemInfo
 412                    // during enumeration so that we can parse the raw value in order to a) return
 413                    // the correct set of parsed values, and b) update the instance for subsequent enumerations
 414                    // to reflect that parsing.
 415                    ref object storeValueRef = ref EntriesAreLiveView
 416                        ? ref entries[i].Value
 417                        : ref CollectionsMarshal.GetValueRefOrNullRef((Dictionary<HeaderDescriptor, object>)_headerStore
 418
 419                    info = ReplaceWithHeaderStoreItemInfo(ref storeValueRef, entry.Value);
 420                }
 421
 422                // Make sure we parse all raw values before returning the result. Note that this has to be
 423                // done before we calculate the array length (next line): A raw value may contain a list of
 424                // values.
 425                ParseRawHeaderValues(entry.Key, info);
 426
 427                string[] values = GetStoreValuesAsStringArray(entry.Key, info);
 428                yield return new KeyValuePair<string, IEnumerable<string>>(entry.Key.Name, values);
 429            }
 430        }
 431
 432        #endregion
 433
 434        #region IEnumerable Members
 435
 436        Collections.IEnumerator Collections.IEnumerable.GetEnumerator() => GetEnumerator();
 437
 438        #endregion
 439
 440        internal void AddParsedValue(HeaderDescriptor descriptor, object value)
 441        {
 442            Debug.Assert(value != null);
 443            Debug.Assert(descriptor.Parser != null, "Can't add parsed value if there is no parser available.");
 444
 445            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor);
 446
 447            // If the current header has only one value, we can't add another value. The strongly typed property
 448            // must not call AddParsedValue(), but SetParsedValue(). E.g. for headers like 'Date', 'Host'.
 449            Debug.Assert(descriptor.Parser.SupportsMultipleValues, $"Header '{descriptor.Name}' doesn't support multiple
 450
 451            AddParsedValue(info, value);
 452        }
 453
 454        internal void SetParsedValue(HeaderDescriptor descriptor, object value)
 455        {
 456            Debug.Assert(value != null);
 457            Debug.Assert(descriptor.Parser != null, "Can't add parsed value if there is no parser available.");
 458
 459            // This method will first clear all values. This is used e.g. when setting the 'Date' or 'Host' header.
 460            // i.e. headers not supporting collections.
 461            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor);
 462
 463            info.ParsedAndInvalidValues = null;
 464            info.RawValue = null;
 465
 466            AddParsedValue(info, value);
 467        }
 468
 469        internal void SetOrRemoveParsedValue(HeaderDescriptor descriptor, object? value)
 470        {
 471            if (value == null)
 472            {
 473                Remove(descriptor);
 474            }
 475            else
 476            {
 477                SetParsedValue(descriptor, value);
 478            }
 479        }
 480
 481        public bool Remove(string name) => Remove(GetHeaderDescriptor(name));
 482
 483        internal bool RemoveParsedValue(HeaderDescriptor descriptor, object value, bool removeAll = false)
 484        {
 485            Debug.Assert(value != null);
 486
 487            // If we have a value for this header, then verify if we have a single value. If so, compare that
 488            // value with 'item'. If we have a list of values, then remove 'item' from the list.
 489            if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
 490            {
 491                Debug.Assert(descriptor.Parser != null, "Can't add parsed value if there is no parser available.");
 492                Debug.Assert(descriptor.Parser.SupportsMultipleValues,
 493                    "This method should not be used for single-value headers. Use Remove(string) instead.");
 494
 495                // If there is no entry, just return.
 496                var parsedValue = info.ParsedAndInvalidValues;
 497                if (parsedValue == null)
 498                {
 499                    return false;
 500                }
 501
 502                bool result = false;
 503                IEqualityComparer? comparer = descriptor.Parser.Comparer;
 504
 505                List<object>? parsedValues = parsedValue as List<object>;
 506                if (parsedValues == null)
 507                {
 508                    if (parsedValue is not InvalidValue)
 509                    {
 510                        Debug.Assert(parsedValue.GetType() == value.GetType(),
 511                            "Stored value does not have the same type as 'value'.");
 512
 513                        if (AreEqual(value, parsedValue, comparer))
 514                        {
 515                            info.ParsedAndInvalidValues = null;
 516                            result = true;
 517                        }
 518                    }
 519                }
 520                else
 521                {
 522                    for (int i = 0; i < parsedValues.Count; i++)
 523                    {
 524                        object item = parsedValues[i];
 525                        if (item is not InvalidValue)
 526                        {
 527                            Debug.Assert(item.GetType() == value.GetType(),
 528                                "One of the stored values does not have the same type as 'value'.");
 529
 530                            if (AreEqual(value, item, comparer))
 531                            {
 532                                parsedValues.RemoveAt(i);
 533                                i--;
 534
 535                                if (!result)
 536                                {
 537                                    result = true;
 538
 539                                    if (!removeAll)
 540                                    {
 541                                        break;
 542                                    }
 543                                }
 544                                else
 545                                {
 546                                    // We've removed a second item. Fallback to RemoveAll in case there are more to main
 547                                    // Create a copy of the locals to avoid the capture allocation in the common case.
 548                                    object valueLocal = value;
 549                                    IEqualityComparer? comparerLocal = comparer;
 550                                    parsedValues.RemoveAll(item => item is not InvalidValue && AreEqual(valueLocal, item
 551                                    break;
 552                                }
 553                            }
 554                        }
 555                    }
 556
 557                    // If we removed the last item in a list, remove the list.
 558                    if (parsedValues.Count == 0)
 559                    {
 560                        info.AssertContainsNoInvalidValues();
 561                        info.ParsedAndInvalidValues = null;
 562                    }
 563                }
 564
 565                // If there is no value for the header left, remove the header.
 566                if (info.IsEmpty)
 567                {
 568                    bool headerRemoved = Remove(descriptor);
 569                    Debug.Assert(headerRemoved, $"Existing header '{descriptor.Name}' couldn't be removed.");
 570                }
 571
 572                return result;
 573            }
 574
 575            return false;
 576        }
 577
 578        internal bool ContainsParsedValue(HeaderDescriptor descriptor, object value)
 579        {
 580            Debug.Assert(value != null);
 581
 582            // If we have a value for this header, then verify if we have a single value. If so, compare that
 583            // value with 'item'. If we have a list of values, then compare each item in the list with 'item'.
 584            if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
 585            {
 586                Debug.Assert(descriptor.Parser != null, "Can't add parsed value if there is no parser available.");
 587                Debug.Assert(descriptor.Parser.SupportsMultipleValues,
 588                    "This method should not be used for single-value headers. Use equality comparer instead.");
 589
 590                // If there is no entry, just return.
 591                var parsedValue = info.ParsedAndInvalidValues;
 592                if (parsedValue == null)
 593                {
 594                    return false;
 595                }
 596
 597                List<object>? parsedValues = parsedValue as List<object>;
 598
 599                IEqualityComparer? comparer = descriptor.Parser.Comparer;
 600
 601                if (parsedValues == null)
 602                {
 603                    if (parsedValue is not InvalidValue)
 604                    {
 605                        Debug.Assert(parsedValue.GetType() == value.GetType(),
 606                            "Stored value does not have the same type as 'value'.");
 607
 608                        return AreEqual(value, parsedValue, comparer);
 609                    }
 610                }
 611                else
 612                {
 613                    foreach (object item in parsedValues)
 614                    {
 615                        if (item is not InvalidValue)
 616                        {
 617                            Debug.Assert(item.GetType() == value.GetType(),
 618                                "One of the stored values does not have the same type as 'value'.");
 619
 620                            if (AreEqual(value, item, comparer))
 621                            {
 622                                return true;
 623                            }
 624                        }
 625                    }
 626
 627                    return false;
 628                }
 629            }
 630
 631            return false;
 632        }
 633
 634        internal virtual void AddHeaders(HttpHeaders sourceHeaders)
 635        {
 636            Debug.Assert(sourceHeaders != null);
 637            Debug.Assert(GetType() == sourceHeaders.GetType(), "Can only copy headers from an instance of the same type.
 638
 639            // Only add header values if they're not already set on the message. Note that we don't merge
 640            // collections: If both the default headers and the message have set some values for a certain
 641            // header, then we don't try to merge the values.
 642            if (_count == 0 && sourceHeaders._headerStore is HeaderEntry[] sourceEntries)
 643            {
 644                // If the target collection is empty, we don't have to search for existing values
 645                _count = sourceHeaders._count;
 646                if (_headerStore is not HeaderEntry[] entries || entries.Length < _count)
 647                {
 648                    entries = new HeaderEntry[sourceEntries.Length];
 649                    _headerStore = entries;
 650                }
 651
 652                for (int i = 0; i < _count && i < sourceEntries.Length; i++)
 653                {
 654                    HeaderEntry entry = sourceEntries[i];
 655                    if (entry.Value is HeaderStoreItemInfo info)
 656                    {
 657                        entry.Value = CloneHeaderInfo(entry.Key, info);
 658                    }
 659                    entries[i] = entry;
 660                }
 661            }
 662            else
 663            {
 664                foreach (HeaderEntry entry in sourceHeaders.GetEntries())
 665                {
 666                    ref object? storeValueRef = ref GetValueRefOrAddDefault(entry.Key);
 667                    if (storeValueRef is null)
 668                    {
 669                        object sourceValue = entry.Value;
 670                        if (sourceValue is HeaderStoreItemInfo info)
 671                        {
 672                            storeValueRef = CloneHeaderInfo(entry.Key, info);
 673                        }
 674                        else
 675                        {
 676                            Debug.Assert(sourceValue is string);
 677                            storeValueRef = sourceValue;
 678                        }
 679                    }
 680                }
 681            }
 682        }
 683
 684        private static HeaderStoreItemInfo CloneHeaderInfo(HeaderDescriptor descriptor, HeaderStoreItemInfo sourceInfo)
 685        {
 686            lock (sourceInfo)
 687            {
 688                var destinationInfo = new HeaderStoreItemInfo
 689                {
 690                    // Always copy raw values
 691                    RawValue = CloneStringHeaderInfoValues(sourceInfo.RawValue)
 692                };
 693
 694                if (descriptor.Parser == null)
 695                {
 696                    sourceInfo.AssertContainsNoInvalidValues();
 697                    destinationInfo.ParsedAndInvalidValues = CloneStringHeaderInfoValues(sourceInfo.ParsedAndInvalidValu
 698                }
 699                else
 700                {
 701                    // We have a parser, so we also have to clone invalid values and parsed values.
 702                    if (sourceInfo.ParsedAndInvalidValues != null)
 703                    {
 704                        List<object>? sourceValues = sourceInfo.ParsedAndInvalidValues as List<object>;
 705                        if (sourceValues == null)
 706                        {
 707                            CloneAndAddValue(destinationInfo, sourceInfo.ParsedAndInvalidValues);
 708                        }
 709                        else
 710                        {
 711                            foreach (object item in sourceValues)
 712                            {
 713                                CloneAndAddValue(destinationInfo, item);
 714                            }
 715                        }
 716                    }
 717                }
 718
 719                return destinationInfo;
 720            }
 721        }
 722
 723        private static void CloneAndAddValue(HeaderStoreItemInfo destinationInfo, object source)
 724        {
 725            // We only have one value. Clone it and assign it to the store.
 726            if (source is ICloneable cloneableValue)
 727            {
 728                Debug.Assert(source is not InvalidValue);
 729                AddParsedValue(destinationInfo, cloneableValue.Clone());
 730            }
 731            else
 732            {
 733                // If it doesn't implement ICloneable, it's a value type or an immutable type like String/Uri.
 734                AddParsedValue(destinationInfo, source);
 735            }
 736        }
 737
 738        [return: NotNullIfNotNull(nameof(source))]
 739        private static object? CloneStringHeaderInfoValues(object? source)
 740        {
 741            if (source == null)
 742            {
 743                return null;
 744            }
 745
 746            List<object>? sourceValues = source as List<object>;
 747            if (sourceValues == null)
 748            {
 749                // If we just have one value, return the reference to the string (strings are immutable so it's OK
 750                // to use the reference).
 751                return source;
 752            }
 753            else
 754            {
 755                // If we have a list of strings, create a new list and copy all strings to the new list.
 756                return new List<object>(sourceValues);
 757            }
 758        }
 759
 760        private HeaderStoreItemInfo GetOrCreateHeaderInfo(HeaderDescriptor descriptor)
 761        {
 762            if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
 763            {
 764                return info;
 765            }
 766            else
 767            {
 768                return CreateAndAddHeaderToStore(descriptor);
 769            }
 770        }
 771
 772        private HeaderStoreItemInfo CreateAndAddHeaderToStore(HeaderDescriptor descriptor)
 773        {
 774            Debug.Assert(!Contains(descriptor));
 775
 776            // If we don't have the header in the store yet, add it now.
 777            HeaderStoreItemInfo result = new HeaderStoreItemInfo();
 778
 779            // If the descriptor header type is in _treatAsCustomHeaderTypes, it must be converted to a custom header be
 780            Debug.Assert((descriptor.HeaderType & _treatAsCustomHeaderTypes) == 0);
 781
 782            AddEntryToStore(new HeaderEntry(descriptor, result));
 783
 784            return result;
 785        }
 786
 787        internal bool TryGetHeaderValue(HeaderDescriptor descriptor, [NotNullWhen(true)] out object? value)
 788        {
 789            ref object storeValueRef = ref GetValueRefOrNullRef(descriptor);
 790            if (Unsafe.IsNullRef(ref storeValueRef))
 791            {
 792                value = null;
 793                return false;
 794            }
 795            else
 796            {
 797                value = storeValueRef;
 798                return true;
 799            }
 800        }
 801
 802        private bool TryGetAndParseHeaderInfo(HeaderDescriptor key, [NotNullWhen(true)] out HeaderStoreItemInfo? info)
 803        {
 804            ref object storeValueRef = ref GetValueRefOrNullRef(key);
 805            if (!Unsafe.IsNullRef(ref storeValueRef))
 806            {
 807                object value = storeValueRef;
 808
 809                info = value is HeaderStoreItemInfo hsi
 810                    ? hsi
 811                    : ReplaceWithHeaderStoreItemInfo(ref storeValueRef, value);
 812
 813                ParseRawHeaderValues(key, info);
 814                return true;
 815            }
 816
 817            info = null;
 818            return false;
 819        }
 820
 821        /// <summary>
 822        /// Replaces <paramref name="storeValueRef"/> with a new <see cref="HeaderStoreItemInfo"/>,
 823        /// or returns the existing <see cref="HeaderStoreItemInfo"/> if a different thread beat us to it.
 824        /// </summary>
 825        /// <remarks>
 826        /// This helper should be used any time we're upgrading a storage slot from an unparsed string to a HeaderStoreI
 827        /// Concurrent writes to the header collection are UB, so we don't need to worry about race conditions when doin
 828        /// </remarks>
 829        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 830        private static HeaderStoreItemInfo ReplaceWithHeaderStoreItemInfo(ref object storeValueRef, object value)
 831        {
 832            Debug.Assert(value is string);
 833
 834            var info = new HeaderStoreItemInfo() { RawValue = value };
 835            object previousValue = Interlocked.CompareExchange(ref storeValueRef, info, value);
 836
 837            if (ReferenceEquals(previousValue, value))
 838            {
 839                return info;
 840            }
 841
 842            // Rare race condition: Another thread replaced the value with a HeaderStoreItemInfo.
 843            return (HeaderStoreItemInfo)previousValue;
 844        }
 845
 846        private static void ParseRawHeaderValues(HeaderDescriptor descriptor, HeaderStoreItemInfo info)
 847        {
 848            // Unlike TryGetHeaderInfo() this method tries to parse all non-validated header values (if any)
 849            // before returning to the caller.
 850            lock (info)
 851            {
 852                Debug.Assert(!info.IsEmpty);
 853                if (info.RawValue != null)
 854                {
 855                    if (info.RawValue is List<string> rawValues)
 856                    {
 857                        foreach (string rawValue in rawValues)
 858                        {
 859                            ParseSingleRawHeaderValue(info, descriptor, rawValue);
 860                        }
 861                    }
 862                    else
 863                    {
 864                        string? rawValue = info.RawValue as string;
 865                        Debug.Assert(rawValue is not null);
 866                        ParseSingleRawHeaderValue(info, descriptor, rawValue);
 867                    }
 868
 869                    // At this point all values are either in info.ParsedValue, info.InvalidValue. Reset RawValue.
 870                    Debug.Assert(info.ParsedAndInvalidValues is not null);
 871                    info.RawValue = null;
 872                }
 873            }
 874        }
 875
 876        private static void ParseSingleRawHeaderValue(HeaderStoreItemInfo info, HeaderDescriptor descriptor, string rawV
 877        {
 878            Debug.Assert(Monitor.IsEntered(info));
 879            if (descriptor.Parser == null)
 880            {
 881                if (HttpRuleParser.ContainsNewLineOrNull(rawValue))
 882                {
 883                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, SR.Format(SR.net_http_log_headers_no_
 884                    AddInvalidValue(info, rawValue);
 885                }
 886                else
 887                {
 888                    AddParsedValue(info, rawValue);
 889                }
 890            }
 891            else
 892            {
 893                if (!TryParseAndAddRawHeaderValue(descriptor, info, rawValue, true))
 894                {
 895                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.HeadersInvalidValue(descriptor.Name, rawValue
 896                }
 897            }
 898        }
 899
 900        // See Add(name, string)
 901        internal bool TryParseAndAddValue(HeaderDescriptor descriptor, string? value)
 902        {
 903            // We don't use GetOrCreateHeaderInfo() here, since this would create a new header in the store. If parsing
 904            // the value then throws, we would have to remove the header from the store again. So just get a
 905            // HeaderStoreItemInfo object and try to parse the value. If it works, we'll add the header.
 906            HeaderStoreItemInfo info;
 907            bool addToStore;
 908            PrepareHeaderInfoForAdd(descriptor, out info, out addToStore);
 909
 910            bool result = TryParseAndAddRawHeaderValue(descriptor, info, value, false);
 911
 912            if (result && addToStore && (info.ParsedAndInvalidValues != null))
 913            {
 914                info.AssertContainsNoInvalidValues();
 915                // If we get here, then the value could be parsed correctly. If we created a new HeaderStoreItemInfo, ad
 916                // it to the store if we added at least one value.
 917                Debug.Assert(!Contains(descriptor));
 918                AddEntryToStore(new HeaderEntry(descriptor, info));
 919            }
 920
 921            return result;
 922        }
 923
 924        // See ParseAndAddValue
 925        private static bool TryParseAndAddRawHeaderValue(HeaderDescriptor descriptor, HeaderStoreItemInfo info, string? 
 926        {
 927            Debug.Assert(info != null);
 928            Debug.Assert(descriptor.Parser != null);
 929
 930            // Values are added as 'invalid' if we either can't parse the value OR if we already have a value
 931            // and the current header doesn't support multiple values: e.g. trying to add a date/time value
 932            // to the 'Date' header if we already have a date/time value will result in the second value being
 933            // added to the 'invalid' header values.
 934            if (!info.CanAddParsedValue(descriptor.Parser))
 935            {
 936                if (addWhenInvalid)
 937                {
 938                    AddInvalidValue(info, value ?? string.Empty);
 939                }
 940                return false;
 941            }
 942
 943            int index = 0;
 944
 945            if (descriptor.Parser.TryParseValue(value, info.ParsedAndInvalidValues, ref index, out object? parsedValue))
 946            {
 947                // The raw string only represented one value (which was successfully parsed). Add the value and return.
 948                if ((value == null) || (index == value.Length))
 949                {
 950                    if (parsedValue != null)
 951                    {
 952                        AddParsedValue(info, parsedValue);
 953                    }
 954                    else if (addWhenInvalid && info.ParsedAndInvalidValues is null)
 955                    {
 956                        AddInvalidValue(info, value ?? string.Empty);
 957                    }
 958                    return true;
 959                }
 960                Debug.Assert(index < value.Length, "Parser must return an index value within the string length.");
 961
 962                // If we successfully parsed a value, but there are more left to read, store the results in a temp
 963                // list. Only when all values are parsed successfully write the list to the store.
 964                List<object> parsedValues = new List<object>();
 965                if (parsedValue != null)
 966                {
 967                    parsedValues.Add(parsedValue);
 968                }
 969
 970                while (index < value.Length)
 971                {
 972                    if (descriptor.Parser.TryParseValue(value, info.ParsedAndInvalidValues, ref index, out parsedValue))
 973                    {
 974                        if (parsedValue != null)
 975                        {
 976                            parsedValues.Add(parsedValue);
 977                        }
 978                    }
 979                    else
 980                    {
 981                        if (addWhenInvalid)
 982                        {
 983                            AddInvalidValue(info, value);
 984                        }
 985                        return false;
 986                    }
 987                }
 988
 989                // All values were parsed correctly. Copy results to the store.
 990                foreach (object item in parsedValues)
 991                {
 992                    AddParsedValue(info, item);
 993                }
 994
 995                if (parsedValues.Count == 0 && addWhenInvalid && info.ParsedAndInvalidValues is null)
 996                {
 997                    AddInvalidValue(info, value);
 998                }
 999
 1000                return true;
 1001            }
 1002
 1003            Debug.Assert(value != null);
 1004            if (addWhenInvalid)
 1005            {
 1006                AddInvalidValue(info, value ?? string.Empty);
 1007            }
 1008            return false;
 1009        }
 1010
 1011        private static void AddParsedValue(HeaderStoreItemInfo info, object value)
 1012        {
 1013            Debug.Assert(!(value is List<object>),
 1014                "Header value types must not derive from List<object> since this type is used internally to store " +
 1015                "lists of values. So we would not be able to distinguish between a single value and a list of values.");
 1016
 1017            AddValueToStoreValue<object>(value, ref info.ParsedAndInvalidValues);
 1018        }
 1019
 1020        private static void AddInvalidValue(HeaderStoreItemInfo info, string value)
 1021        {
 1022            AddValueToStoreValue<object>(new InvalidValue(value), ref info.ParsedAndInvalidValues);
 1023        }
 1024
 1025        private static void AddRawValue(HeaderStoreItemInfo info, string value)
 1026        {
 1027            AddValueToStoreValue<string>(value, ref info.RawValue);
 1028        }
 1029
 1030        private static void AddValueToStoreValue<T>(T value, ref object? currentStoreValue) where T : class
 1031        {
 1032            // If there is no value set yet, then add current item as value (we don't create a list
 1033            // if not required). If 'info.Value' is already assigned then make sure 'info.Value' is a
 1034            // List<T> and append 'item' to the list.
 1035            if (currentStoreValue == null)
 1036            {
 1037                currentStoreValue = value;
 1038            }
 1039            else
 1040            {
 1041                List<T>? storeValues = currentStoreValue as List<T>;
 1042
 1043                if (storeValues == null)
 1044                {
 1045                    storeValues = new List<T>(2);
 1046                    Debug.Assert(currentStoreValue is T);
 1047                    storeValues.Add((T)currentStoreValue);
 1048                    currentStoreValue = storeValues;
 1049                }
 1050                Debug.Assert(value is T);
 1051                storeValues.Add((T)value);
 1052            }
 1053        }
 1054
 1055        internal object? GetSingleParsedValue(HeaderDescriptor descriptor)
 1056        {
 1057            if (!TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
 1058            {
 1059                return null;
 1060            }
 1061
 1062            return info.GetSingleParsedValue();
 1063        }
 1064
 1065        internal object? GetParsedAndInvalidValues(HeaderDescriptor descriptor)
 1066        {
 1067            if (!TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
 1068            {
 1069                return null;
 1070            }
 1071
 1072            return info.ParsedAndInvalidValues;
 1073        }
 1074
 1075        internal virtual bool IsAllowedHeaderName(HeaderDescriptor descriptor) => true;
 1076
 1077        private void CheckIsAllowedHeaderName(HeaderDescriptor descriptor)
 1078        {
 1079            if (!IsAllowedHeaderName(descriptor))
 1080            {
 1081                throw new InvalidOperationException(SR.Format(SR.net_http_headers_not_allowed_header_name, descriptor.Na
 1082            }
 1083        }
 1084
 1085        private void PrepareHeaderInfoForAdd(HeaderDescriptor descriptor, out HeaderStoreItemInfo info, out bool addToSt
 1086        {
 1087            CheckIsAllowedHeaderName(descriptor);
 1088
 1089            addToStore = false;
 1090            if (!TryGetAndParseHeaderInfo(descriptor, out info!))
 1091            {
 1092                info = new HeaderStoreItemInfo();
 1093                addToStore = true;
 1094            }
 1095        }
 1096
 1097        private static void ParseAndAddValue(HeaderDescriptor descriptor, HeaderStoreItemInfo info, string? value)
 1098        {
 1099            Debug.Assert(info != null);
 1100            Debug.Assert(descriptor.Parser != null);
 1101
 1102            // If the header only supports 1 value, we can add the current value only if there is no
 1103            // value already set.
 1104            if (!info.CanAddParsedValue(descriptor.Parser))
 1105            {
 1106                throw new FormatException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_heade
 1107            }
 1108
 1109            int index = 0;
 1110            object parsedValue = descriptor.Parser.ParseValue(value, info.ParsedAndInvalidValues, ref index);
 1111
 1112            // The raw string only represented one value (which was successfully parsed). Add the value and return.
 1113            // If value is null we still have to first call ParseValue() to allow the parser to decide whether null is
 1114            // a valid value. If it is (i.e. no exception thrown), we set the parsed value (if any) and return.
 1115            if ((value == null) || (index == value.Length))
 1116            {
 1117                // If the returned value is null, then it means the header accepts empty values. i.e. we don't throw
 1118                // but we don't add 'null' to the store either.
 1119                if (parsedValue != null)
 1120                {
 1121                    AddParsedValue(info, parsedValue);
 1122                }
 1123                return;
 1124            }
 1125            Debug.Assert(index < value.Length, "Parser must return an index value within the string length.");
 1126
 1127            // If we successfully parsed a value, but there are more left to read, store the results in a temp
 1128            // list. Only when all values are parsed successfully write the list to the store.
 1129            List<object> parsedValues = new List<object>();
 1130            if (parsedValue != null)
 1131            {
 1132                parsedValues.Add(parsedValue);
 1133            }
 1134
 1135            while (index < value.Length)
 1136            {
 1137                parsedValue = descriptor.Parser.ParseValue(value, info.ParsedAndInvalidValues, ref index);
 1138                if (parsedValue != null)
 1139                {
 1140                    parsedValues.Add(parsedValue);
 1141                }
 1142            }
 1143
 1144            // All values were parsed correctly. Copy results to the store.
 1145            foreach (object item in parsedValues)
 1146            {
 1147                AddParsedValue(info, item);
 1148            }
 1149        }
 1150
 1151        internal HeaderDescriptor GetHeaderDescriptor(string name)
 1152        {
 1153            ArgumentException.ThrowIfNullOrEmpty(name);
 1154
 1155            if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
 1156            {
 1157                throw new FormatException(SR.Format(SR.net_http_headers_invalid_header_name, name));
 1158            }
 1159
 1160            if ((descriptor.HeaderType & _allowedHeaderTypes) != 0)
 1161            {
 1162                return descriptor;
 1163            }
 1164            else if ((descriptor.HeaderType & _treatAsCustomHeaderTypes) != 0)
 1165            {
 1166                return descriptor.AsCustomHeader();
 1167            }
 1168
 1169            throw new InvalidOperationException(SR.Format(SR.net_http_headers_not_allowed_header_name, name));
 1170        }
 1171
 1172        internal bool TryGetHeaderDescriptor(string name, out HeaderDescriptor descriptor)
 1173        {
 1174            if (string.IsNullOrEmpty(name))
 1175            {
 1176                descriptor = default;
 1177                return false;
 1178            }
 1179
 1180            if (HeaderDescriptor.TryGet(name, out descriptor))
 1181            {
 1182                HttpHeaderType headerType = descriptor.HeaderType;
 1183
 1184                if ((headerType & _allowedHeaderTypes) != 0)
 1185                {
 1186                    return true;
 1187                }
 1188
 1189                if ((headerType & _treatAsCustomHeaderTypes) != 0)
 1190                {
 1191                    descriptor = descriptor.AsCustomHeader();
 1192                    return true;
 1193                }
 1194            }
 1195
 1196            return false;
 1197        }
 1198
 1199        internal static void CheckContainsNewLineOrNull(string? value)
 1200        {
 1201            if (value == null)
 1202            {
 1203                return;
 1204            }
 1205
 1206            if (HttpRuleParser.ContainsNewLineOrNull(value))
 1207            {
 1208                throw new FormatException(SR.net_http_headers_no_newlines_no_nul);
 1209            }
 1210        }
 1211
 1212        internal static string[] GetStoreValuesAsStringArray(HeaderDescriptor descriptor, HeaderStoreItemInfo info)
 1213        {
 1214            GetStoreValuesAsStringOrStringArray(descriptor, info, out string? singleValue, out string[]? multiValue);
 1215            Debug.Assert(singleValue is not null ^ multiValue is not null);
 1216            return multiValue ?? new[] { singleValue! };
 1217        }
 1218
 1219        internal static void GetStoreValuesAsStringOrStringArray(HeaderDescriptor descriptor, object sourceValues, out s
 1220        {
 1221            HeaderStoreItemInfo? info = sourceValues as HeaderStoreItemInfo;
 1222            if (info is null)
 1223            {
 1224                Debug.Assert(sourceValues is string);
 1225                singleValue = (string)sourceValues;
 1226                multiValue = null;
 1227                return;
 1228            }
 1229
 1230            lock (info)
 1231            {
 1232                int length = GetValueCount(info);
 1233
 1234                scoped Span<string?> values;
 1235                singleValue = null;
 1236                if (length == 1)
 1237                {
 1238                    multiValue = null;
 1239                    values = new Span<string?>(ref singleValue);
 1240                }
 1241                else
 1242                {
 1243                    Debug.Assert(length > 1, "The header should have been removed when it became empty");
 1244                    values = (multiValue = new string[length])!;
 1245                }
 1246
 1247                int currentIndex = 0;
 1248                ReadStoreValues<object?>(values, info.ParsedAndInvalidValues, descriptor.Parser, ref currentIndex);
 1249                ReadStoreValues<string?>(values, info.RawValue, null, ref currentIndex);
 1250
 1251                Debug.Assert(currentIndex == length);
 1252            }
 1253        }
 1254
 1255        internal static int GetStoreValuesIntoStringArray(HeaderDescriptor descriptor, object sourceValues, [NotNull] re
 1256        {
 1257            values ??= Array.Empty<string>();
 1258
 1259            HeaderStoreItemInfo? info = sourceValues as HeaderStoreItemInfo;
 1260            if (info is null)
 1261            {
 1262                Debug.Assert(sourceValues is string);
 1263
 1264                if (values.Length == 0)
 1265                {
 1266                    values = new string[1];
 1267                }
 1268
 1269                values[0] = (string)sourceValues;
 1270                return 1;
 1271            }
 1272
 1273            lock (info)
 1274            {
 1275                int length = GetValueCount(info);
 1276                Debug.Assert(length > 0);
 1277
 1278                if (values.Length < length)
 1279                {
 1280                    values = new string[length];
 1281                }
 1282
 1283                int currentIndex = 0;
 1284                ReadStoreValues<object?>(values!, info.ParsedAndInvalidValues, descriptor.Parser, ref currentIndex);
 1285                ReadStoreValues<string?>(values!, info.RawValue, null, ref currentIndex);
 1286                Debug.Assert(currentIndex == length);
 1287
 1288                return length;
 1289            }
 1290        }
 1291
 1292        private static int GetValueCount(HeaderStoreItemInfo info)
 1293        {
 1294            Debug.Assert(info != null);
 1295            Debug.Assert(Monitor.IsEntered(info));
 1296
 1297            return Count<object>(info.ParsedAndInvalidValues) + Count<string>(info.RawValue);
 1298
 1299            static int Count<T>(object? valueStore) =>
 1300                valueStore is null ? 0 :
 1301                valueStore is List<T> list ? list.Count :
 1302                1;
 1303        }
 1304
 1305        private static void ReadStoreValues<T>(Span<string?> values, object? storeValue, HttpHeaderParser? parser, ref i
 1306        {
 1307            if (storeValue != null)
 1308            {
 1309                List<T>? storeValues = storeValue as List<T>;
 1310
 1311                if (storeValues == null)
 1312                {
 1313                    values[currentIndex] = parser == null || storeValue is InvalidValue ? storeValue.ToString() : parser
 1314                    currentIndex++;
 1315                }
 1316                else
 1317                {
 1318                    foreach (object? item in storeValues)
 1319                    {
 1320                        Debug.Assert(item != null);
 1321                        values[currentIndex] = parser == null || item is InvalidValue ? item.ToString() : parser.ToStrin
 1322                        currentIndex++;
 1323                    }
 1324                }
 1325            }
 1326        }
 1327
 1328        private static bool AreEqual(object value, object? storeValue, IEqualityComparer? comparer)
 1329        {
 1330            Debug.Assert(value != null);
 1331
 1332            if (comparer != null)
 1333            {
 1334                return comparer.Equals(value, storeValue);
 1335            }
 1336
 1337            // We don't have a comparer, so use the Equals() method.
 1338            return value.Equals(storeValue);
 1339        }
 1340
 1341        internal sealed class InvalidValue
 1342        {
 1343            private readonly string _value;
 1344
 1345            public InvalidValue(string value)
 1346            {
 1347                Debug.Assert(value is not null);
 1348                _value = value;
 1349            }
 1350
 1351            public override string ToString() => _value;
 1352        }
 1353
 1354        internal sealed class HeaderStoreItemInfo
 1355        {
 1356            internal HeaderStoreItemInfo() { }
 1357
 1358            internal object? RawValue;
 1359            internal object? ParsedAndInvalidValues;
 1360
 1361            public bool CanAddParsedValue(HttpHeaderParser parser)
 1362            {
 1363                Debug.Assert(parser != null, "There should be no reason to call CanAddValue if there is no parser for th
 1364
 1365                // If the header only supports one value, and we have already a value set, then we can't add
 1366                // another value. E.g. the 'Date' header only supports one value. We can't add multiple timestamps
 1367                // to 'Date'.
 1368                // So if this is a known header, ask the parser if it supports multiple values and check whether
 1369                // we already have a (valid or invalid) value.
 1370                // Note that we ignore the rawValue by purpose: E.g. we are parsing 2 raw values for a header only
 1371                // supporting 1 value. When the first value gets parsed, CanAddValue returns true and we add the
 1372                // parsed value to ParsedValue. When the second value is parsed, CanAddValue returns false, because
 1373                // we have already a parsed value.
 1374                return parser.SupportsMultipleValues || ParsedAndInvalidValues is null;
 1375            }
 1376
 1377            [Conditional("DEBUG")]
 1378            public void AssertContainsNoInvalidValues()
 1379            {
 1380                if (ParsedAndInvalidValues is not null)
 1381                {
 1382                    if (ParsedAndInvalidValues is List<object> list)
 1383                    {
 1384                        foreach (object item in list)
 1385                        {
 1386                            Debug.Assert(item is not InvalidValue);
 1387                        }
 1388                    }
 1389                    else
 1390                    {
 1391                        Debug.Assert(ParsedAndInvalidValues is not InvalidValue);
 1392                    }
 1393                }
 1394            }
 1395
 1396            public object? GetSingleParsedValue()
 1397            {
 1398                if (ParsedAndInvalidValues is not null)
 1399                {
 1400                    if (ParsedAndInvalidValues is List<object> list)
 1401                    {
 1402                        AssertContainsSingleParsedValue(list);
 1403                        foreach (object item in list)
 1404                        {
 1405                            if (item is not InvalidValue)
 1406                            {
 1407                                return item;
 1408                            }
 1409                        }
 1410                    }
 1411                    else
 1412                    {
 1413                        if (ParsedAndInvalidValues is not InvalidValue)
 1414                        {
 1415                            return ParsedAndInvalidValues;
 1416                        }
 1417                    }
 1418                }
 1419
 1420                return null;
 1421            }
 1422
 1423            [Conditional("DEBUG")]
 1424            private static void AssertContainsSingleParsedValue(List<object> list)
 1425            {
 1426                int count = 0;
 1427                foreach (object item in list)
 1428                {
 1429                    if (item is not InvalidValue)
 1430                    {
 1431                        count++;
 1432                    }
 1433                }
 1434
 1435                Debug.Assert(count == 1, "Only a single parsed value should be stored for this parser");
 1436            }
 1437
 1438            public bool IsEmpty => RawValue == null && ParsedAndInvalidValues == null;
 1439        }
 1440
 1441
 1442        #region Low-level implementation details that work with _headerStore directly
 1443
 1444        private const int InitialCapacity = 4;
 1445        internal const int ArrayThreshold = 64; // Above this threshold, header ordering will not be preserved
 1446
 1447        internal HeaderEntry[]? GetEntriesArray()
 1448        {
 1449            object? store = _headerStore;
 1450            if (store is null)
 1451            {
 1452                return null;
 1453            }
 1454            else if (store is HeaderEntry[] entries)
 1455            {
 1456                return entries;
 1457            }
 1458            else
 1459            {
 1460                return GetEntriesFromDictionary();
 1461            }
 1462
 1463            HeaderEntry[] GetEntriesFromDictionary()
 1464            {
 1465                var dictionary = (Dictionary<HeaderDescriptor, object>)_headerStore!;
 1466                var entries = new HeaderEntry[dictionary.Count];
 1467                int i = 0;
 1468                foreach (KeyValuePair<HeaderDescriptor, object> entry in dictionary)
 1469                {
 1470                    entries[i++] = new HeaderEntry
 1471                    {
 1472                        Key = entry.Key,
 1473                        Value = entry.Value
 1474                    };
 1475                }
 1476                return entries;
 1477            }
 1478        }
 1479
 1480        internal ReadOnlySpan<HeaderEntry> GetEntries()
 1481        {
 1482            return new ReadOnlySpan<HeaderEntry>(GetEntriesArray(), 0, _count);
 1483        }
 1484
 1485        internal int Count => _count;
 1486
 1487        private bool EntriesAreLiveView => _headerStore is HeaderEntry[];
 1488
 1489        private ref object GetValueRefOrNullRef(HeaderDescriptor key)
 1490        {
 1491            ref object valueRef = ref Unsafe.NullRef<object>();
 1492
 1493            object? store = _headerStore;
 1494            if (store is HeaderEntry[] entries)
 1495            {
 1496                for (int i = 0; i < _count && i < entries.Length; i++)
 1497                {
 1498                    if (key.Equals(entries[i].Key))
 1499                    {
 1500                        valueRef = ref entries[i].Value;
 1501                        break;
 1502                    }
 1503                }
 1504            }
 1505            else if (store is not null)
 1506            {
 1507                valueRef = ref CollectionsMarshal.GetValueRefOrNullRef((Dictionary<HeaderDescriptor, object>)store, key)
 1508            }
 1509
 1510            return ref valueRef;
 1511        }
 1512
 1513        private ref object? GetValueRefOrAddDefault(HeaderDescriptor key)
 1514        {
 1515            object? store = _headerStore;
 1516            if (store is HeaderEntry[] entries)
 1517            {
 1518                for (int i = 0; i < _count && i < entries.Length; i++)
 1519                {
 1520                    if (key.Equals(entries[i].Key))
 1521                    {
 1522                        return ref entries[i].Value!;
 1523                    }
 1524                }
 1525
 1526                int count = _count;
 1527                _count++;
 1528                if ((uint)count < (uint)entries.Length)
 1529                {
 1530                    entries[count].Key = key;
 1531                    return ref entries[count].Value!;
 1532                }
 1533
 1534                return ref GrowEntriesAndAddDefault(key);
 1535            }
 1536            else if (store is null)
 1537            {
 1538                _count++;
 1539                entries = new HeaderEntry[InitialCapacity];
 1540                _headerStore = entries;
 1541                entries[0].Key = key;
 1542                return ref entries[0].Value!;
 1543            }
 1544            else
 1545            {
 1546                return ref DictionaryGetValueRefOrAddDefault(key);
 1547            }
 1548
 1549            ref object? GrowEntriesAndAddDefault(HeaderDescriptor key)
 1550            {
 1551                var entries = (HeaderEntry[])_headerStore!;
 1552                if (entries.Length == ArrayThreshold)
 1553                {
 1554                    return ref ConvertToDictionaryAndAddDefault(key);
 1555                }
 1556                else
 1557                {
 1558                    Array.Resize(ref entries, entries.Length << 1);
 1559                    _headerStore = entries;
 1560                    ref HeaderEntry firstNewEntry = ref entries[entries.Length >> 1];
 1561                    firstNewEntry.Key = key;
 1562                    return ref firstNewEntry.Value!;
 1563                }
 1564            }
 1565
 1566            ref object? ConvertToDictionaryAndAddDefault(HeaderDescriptor key)
 1567            {
 1568                var entries = (HeaderEntry[])_headerStore!;
 1569                var dictionary = new Dictionary<HeaderDescriptor, object>(ArrayThreshold);
 1570                _headerStore = dictionary;
 1571                foreach (HeaderEntry entry in entries)
 1572                {
 1573                    dictionary.Add(entry.Key, entry.Value);
 1574                }
 1575                Debug.Assert(dictionary.Count == _count - 1);
 1576                return ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out _);
 1577            }
 1578
 1579            ref object? DictionaryGetValueRefOrAddDefault(HeaderDescriptor key)
 1580            {
 1581                var dictionary = (Dictionary<HeaderDescriptor, object>)_headerStore!;
 1582                ref object? value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out _);
 1583                if (value is null)
 1584                {
 1585                    _count++;
 1586                }
 1587                return ref value;
 1588            }
 1589        }
 1590
 1591        private void AddEntryToStore(HeaderEntry entry)
 1592        {
 1593            Debug.Assert(!Contains(entry.Key));
 1594
 1595            if (_headerStore is HeaderEntry[] entries)
 1596            {
 1597                int count = _count;
 1598                if ((uint)count < (uint)entries.Length)
 1599                {
 1600                    entries[count] = entry;
 1601                    _count++;
 1602                    return;
 1603                }
 1604            }
 1605
 1606            GetValueRefOrAddDefault(entry.Key) = entry.Value;
 1607        }
 1608
 1609        internal bool Contains(HeaderDescriptor key)
 1610        {
 1611            return !Unsafe.IsNullRef(ref GetValueRefOrNullRef(key));
 1612        }
 1613
 1614        public void Clear()
 1615        {
 1616            if (_headerStore is HeaderEntry[] entries)
 1617            {
 1618                Array.Clear(entries, 0, _count);
 1619            }
 1620            else
 1621            {
 1622                _headerStore = null;
 1623            }
 1624            _count = 0;
 1625        }
 1626
 1627        internal bool Remove(HeaderDescriptor key)
 1628        {
 1629            bool removed = false;
 1630
 1631            object? store = _headerStore;
 1632            if (store is HeaderEntry[] entries)
 1633            {
 1634                for (int i = 0; i < _count && i < entries.Length; i++)
 1635                {
 1636                    if (key.Equals(entries[i].Key))
 1637                    {
 1638                        while (i + 1 < _count && (uint)(i + 1) < (uint)entries.Length)
 1639                        {
 1640                            entries[i] = entries[i + 1];
 1641                            i++;
 1642                        }
 1643                        entries[i] = default;
 1644                        removed = true;
 1645                        break;
 1646                    }
 1647                }
 1648            }
 1649            else if (store is not null)
 1650            {
 1651                removed = ((Dictionary<HeaderDescriptor, object>)store).Remove(key);
 1652            }
 1653
 1654            if (removed)
 1655            {
 1656                _count--;
 1657            }
 1658
 1659            return removed;
 1660        }
 1661
 1662        #endregion // _headerStore implementation
 1663    }
 1664}