< Summary

Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 1669
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)

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