< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 58
Coverable lines: 58
Total lines: 970
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 6
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%110%
GetCookiesCount()0%220%
GetEnumerator()100%110%
Remove(...)100%110%
.cctor()100%110%
System.Collections.IComparer.Compare(...)0%440%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.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.Text;
 8
 9// Relevant cookie specs:
 10//
 11// PERSISTENT CLIENT STATE HTTP COOKIES (1996)
 12// From <http:// web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html>
 13//
 14// RFC2109 HTTP State Management Mechanism (February 1997)
 15// From <http:// tools.ietf.org/html/rfc2109>
 16//
 17// RFC2965 HTTP State Management Mechanism (October 2000)
 18// From <http:// tools.ietf.org/html/rfc2965>
 19//
 20// RFC6265 HTTP State Management Mechanism (April 2011)
 21// From <http:// tools.ietf.org/html/rfc6265>
 22//
 23// The Version attribute of the cookie header is defined and used only in RFC2109 and RFC2965 cookie
 24// specs and specifies Version=1. The Version attribute is not used in the  Netscape cookie spec
 25// (considered as Version=0). Nor is it used in the most recent cookie spec, RFC6265, introduced in 2011.
 26// RFC6265 deprecates all previous cookie specs including the Version attribute.
 27//
 28// Cookies without an explicit Domain attribute will only match a potential uri that matches the original
 29// uri from where the cookie came from.
 30// For explicit Domain attribute in the cookie, see the rules defined in Cookie.HostMatchesDomain().
 31
 32namespace System.Net
 33{
 34    internal readonly struct HeaderVariantInfo
 35    {
 36        private readonly string _name;
 37        private readonly CookieVariant _variant;
 38
 39        internal HeaderVariantInfo(string name, CookieVariant variant)
 40        {
 41            _name = name;
 42            _variant = variant;
 43        }
 44
 45        internal string Name
 46        {
 47            get
 48            {
 49                return _name;
 50            }
 51        }
 52
 53        internal CookieVariant Variant
 54        {
 55            get
 56            {
 57                return _variant;
 58            }
 59        }
 60    }
 61
 62    // CookieContainer
 63    //
 64    // Manage cookies for a user (implicit). Based on RFC 2965.
 65    [Serializable]
 66    [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c5
 67    public class CookieContainer
 68    {
 69        public const int DefaultCookieLimit = 300;
 70        public const int DefaultPerDomainCookieLimit = 20;
 71        public const int DefaultCookieLengthLimit = 4096;
 72
 73        private static readonly HeaderVariantInfo[] s_headerInfo = {
 74            new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie,  CookieVariant.Rfc2109),
 75            new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie2, CookieVariant.Rfc2965)
 76        };
 77
 78        private readonly Hashtable m_domainTable = new Hashtable(); // Do not rename (binary serialization)
 79        private int m_maxCookieSize = DefaultCookieLengthLimit; // Do not rename (binary serialization)
 80        private int m_maxCookies = DefaultCookieLimit; // Do not rename (binary serialization)
 81        private int m_maxCookiesPerDomain = DefaultPerDomainCookieLimit; // Do not rename (binary serialization)
 82        private int m_count; // Do not rename (binary serialization)
 83#pragma warning disable CA1823 // Avoid unused private fields
 84        private readonly string m_fqdnMyDomain = string.Empty;
 85#pragma warning restore CA1823 // Avoid unused private fields
 86
 87        public CookieContainer()
 88        {
 89        }
 90
 91        public CookieContainer(int capacity)
 92        {
 93            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
 94            m_maxCookies = capacity;
 95        }
 96
 97        public CookieContainer(int capacity, int perDomainCapacity, int maxCookieSize) : this(capacity)
 98        {
 99            if (perDomainCapacity != int.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity))
 100            {
 101                throw new ArgumentOutOfRangeException(nameof(perDomainCapacity), SR.Format(SR.net_cookie_capacity_range,
 102            }
 103            m_maxCookiesPerDomain = perDomainCapacity;
 104            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxCookieSize);
 105            m_maxCookieSize = maxCookieSize;
 106        }
 107
 108        // NOTE: after shrinking the capacity, Count can become greater than Capacity.
 109        public int Capacity
 110        {
 111            get
 112            {
 113                return m_maxCookies;
 114            }
 115            set
 116            {
 117                if (value <= 0 || (value < m_maxCookiesPerDomain && m_maxCookiesPerDomain != int.MaxValue))
 118                {
 119                    throw new ArgumentOutOfRangeException(nameof(value), SR.Format(SR.net_cookie_capacity_range, "Capaci
 120                }
 121                if (value < m_maxCookies)
 122                {
 123                    m_maxCookies = value;
 124                    AgeCookies(null);
 125                }
 126                m_maxCookies = value;
 127            }
 128        }
 129
 130        /// <devdoc>
 131        ///   <para>Returns the total number of cookies in the container.</para>
 132        /// </devdoc>
 133        public int Count
 134        {
 135            get
 136            {
 137                return m_count;
 138            }
 139        }
 140
 141        public int MaxCookieSize
 142        {
 143            get
 144            {
 145                return m_maxCookieSize;
 146            }
 147            set
 148            {
 149                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 150                m_maxCookieSize = value;
 151            }
 152        }
 153
 154        /// <devdoc>
 155        ///   <para>After shrinking domain capacity, each domain will less hold than new domain capacity.</para>
 156        /// </devdoc>
 157        public int PerDomainCapacity
 158        {
 159            get
 160            {
 161                return m_maxCookiesPerDomain;
 162            }
 163            set
 164            {
 165                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 166                if (value != int.MaxValue)
 167                {
 168                    ArgumentOutOfRangeException.ThrowIfGreaterThan(value, m_maxCookies);
 169                }
 170
 171                if (value < m_maxCookiesPerDomain)
 172                {
 173                    m_maxCookiesPerDomain = value;
 174                    AgeCookies(null);
 175                }
 176                m_maxCookiesPerDomain = value;
 177            }
 178        }
 179
 180        // This method will construct a faked URI: the Domain property is required for param.
 181        public void Add(Cookie cookie)
 182        {
 183            ArgumentNullException.ThrowIfNull(cookie);
 184
 185            if (cookie.Domain.Length == 0)
 186            {
 187                throw new ArgumentException(
 188                    SR.Format(SR.net_emptystringcall, nameof(cookie) + "." + nameof(cookie.Domain)),
 189                    nameof(cookie));
 190            }
 191
 192            Uri? uri;
 193            var uriSb = new StringBuilder();
 194
 195            // We cannot add an invalid cookie into the container.
 196            // Trying to prepare Uri for the cookie verification.
 197            uriSb.Append(cookie.Secure ? UriScheme.Https : UriScheme.Http).Append(UriScheme.SchemeDelimiter);
 198
 199            // If the original cookie has an explicitly set domain, copy it over to the new cookie.
 200            if (!cookie.DomainImplicit)
 201            {
 202                if (cookie.Domain[0] == '.')
 203                {
 204                    uriSb.Append('0'); // URI cctor should consume this faked host.
 205                }
 206            }
 207            uriSb.Append(cookie.Domain);
 208
 209
 210            // Either keep Port as implicit or set it according to original cookie.
 211            if (cookie.PortList != null)
 212            {
 213                uriSb.Append(':').Append(cookie.PortList[0]);
 214            }
 215
 216            // Path must be present, set to root by default.
 217            uriSb.Append(cookie.Path);
 218
 219            if (!Uri.TryCreate(uriSb.ToString(), UriKind.Absolute, out uri))
 220                throw new CookieException(SR.Format(SR.net_cookie_attribute, "Domain", cookie.Domain));
 221
 222            // We don't know cookie verification status, so re-create the cookie and verify it.
 223            Cookie new_cookie = cookie.Clone();
 224            new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 225
 226            InternalAdd(new_cookie);
 227        }
 228
 229        // This method is called *only* when cookie verification is done, so unlike with public
 230        // Add(Cookie cookie) the cookie is in a reasonable condition.
 231        internal void InternalAdd(Cookie cookie)
 232        {
 233            PathList? pathList;
 234
 235            if (cookie.Value.Length > m_maxCookieSize)
 236            {
 237                throw new CookieException(SR.Format(SR.net_cookie_size, cookie, m_maxCookieSize));
 238            }
 239
 240            try
 241            {
 242                lock (m_domainTable.SyncRoot)
 243                {
 244                    pathList = (PathList?)m_domainTable[cookie.DomainKey];
 245                    if (pathList == null)
 246                    {
 247                        m_domainTable[cookie.DomainKey] = (pathList = new PathList());
 248                    }
 249                }
 250                int domain_count = pathList.GetCookiesCount();
 251
 252                CookieCollection? cookies;
 253                lock (pathList.SyncRoot)
 254                {
 255                    cookies = (CookieCollection?)pathList[cookie.Path]!;
 256
 257                    if (cookies == null)
 258                    {
 259                        cookies = new CookieCollection();
 260                        pathList[cookie.Path] = cookies;
 261                    }
 262                }
 263
 264                if (cookie.Expired)
 265                {
 266                    // Explicit removal command (Max-Age == 0)
 267                    lock (cookies)
 268                    {
 269                        int idx = cookies.IndexOf(cookie);
 270                        if (idx != -1)
 271                        {
 272                            cookies.RemoveAt(idx);
 273                            --m_count;
 274                        }
 275                    }
 276                }
 277                else
 278                {
 279                    // This is about real cookie adding, check Capacity first
 280                    if (domain_count >= m_maxCookiesPerDomain && !AgeCookies(cookie.DomainKey))
 281                    {
 282                        return; // Cannot age: reject new cookie
 283                    }
 284                    else if (m_count >= m_maxCookies && !AgeCookies(null))
 285                    {
 286                        return; // Cannot age: reject new cookie
 287                    }
 288
 289                    // About to change the collection.
 290                    lock (cookies)
 291                    {
 292                        m_count += cookies.InternalAdd(cookie, true);
 293                    }
 294                }
 295
 296                // We don't want to cleanup m_domaintable/m_list too often. Add check to avoid overhead.
 297                if (m_domainTable.Count > m_count || pathList.Count > m_maxCookiesPerDomain)
 298                {
 299                    DomainTableCleanup();
 300                }
 301            }
 302            catch (OutOfMemoryException)
 303            {
 304                throw;
 305            }
 306            catch (Exception e)
 307            {
 308                throw new CookieException(SR.net_container_add_cookie, e);
 309            }
 310        }
 311
 312        // This function, when called, must delete at least one cookie.
 313        // If there are expired cookies in given scope they are cleaned up.
 314        // If nothing is found the least used Collection will be found and removed
 315        // from the container.
 316        //
 317        // Also note that expired cookies are also removed during request preparation
 318        // (this.GetCookies method).
 319        //
 320        // Param. 'domain' == null means to age in the whole container.
 321        private bool AgeCookies(string? domain)
 322        {
 323            Debug.Assert(m_maxCookies != 0);
 324            Debug.Assert(m_maxCookiesPerDomain != 0);
 325
 326            int removed = 0;
 327            DateTime oldUsed = DateTime.MaxValue;
 328            DateTime tempUsed;
 329
 330            CookieCollection? lruCc = null;
 331            string? lruDomain;
 332            string tempDomain;
 333
 334            PathList pathList;
 335            int domain_count;
 336            int itemp = 0;
 337            float remainingFraction = 1.0F;
 338
 339            // The container was shrunk, might need additional cleanup for each domain
 340            if (m_count > m_maxCookies)
 341            {
 342                // Means the fraction of the container to be left.
 343                // Each domain will be cut accordingly.
 344                remainingFraction = (float)m_maxCookies / (float)m_count;
 345            }
 346            lock (m_domainTable.SyncRoot)
 347            {
 348                foreach (object item in m_domainTable)
 349                {
 350                    DictionaryEntry entry = (DictionaryEntry)item;
 351                    if (domain == null)
 352                    {
 353                        tempDomain = (string)entry.Key;
 354                        pathList = (PathList)entry.Value!; // Aliasing to trick foreach
 355                    }
 356                    else
 357                    {
 358                        tempDomain = domain;
 359                        pathList = (PathList)m_domainTable[domain]!;
 360                    }
 361
 362                    domain_count = 0; // Cookies in the domain
 363                    lock (pathList.SyncRoot)
 364                    {
 365                        foreach (CookieCollection? cc in pathList.Values)
 366                        {
 367                            Debug.Assert(cc != null);
 368                            itemp = ExpireCollection(cc);
 369                            removed += itemp;
 370                            m_count -= itemp; // Update this container's count
 371                            domain_count += cc.Count;
 372
 373                            // We also find the least used cookie collection in ENTIRE container.
 374                            // We count the collection as LRU only if it holds 1+ elements.
 375                            if (cc.Count > 0 && (tempUsed = cc.TimeStamp(CookieCollection.Stamp.Check)) < oldUsed)
 376                            {
 377                                lruDomain = tempDomain;
 378                                lruCc = cc;
 379                                oldUsed = tempUsed;
 380                            }
 381                        }
 382                    }
 383
 384                    // Check if we have reduced to the limit of the domain by expiration only.
 385                    int min_count = Math.Min((int)(domain_count * remainingFraction), Math.Min(m_maxCookiesPerDomain, m_
 386                    if (domain_count > min_count)
 387                    {
 388                        // This case requires sorting all domain collections by timestamp.
 389                        CookieCollection[] cookies;
 390                        DateTime[] stamps;
 391                        lock (pathList.SyncRoot)
 392                        {
 393                            cookies = new CookieCollection[pathList.Count];
 394                            stamps = new DateTime[pathList.Count];
 395                            foreach (CookieCollection? cc in pathList.Values)
 396                            {
 397                                stamps[itemp] = cc!.TimeStamp(CookieCollection.Stamp.Check);
 398                                cookies[itemp] = cc;
 399                                ++itemp;
 400                            }
 401                        }
 402                        Array.Sort(stamps, cookies);
 403
 404                        itemp = 0;
 405                        for (int i = 0; i < cookies.Length; ++i)
 406                        {
 407                            CookieCollection cc = cookies[i];
 408
 409                            lock (cc)
 410                            {
 411                                while (domain_count > min_count && cc.Count > 0)
 412                                {
 413                                    cc.RemoveAt(0);
 414                                    --domain_count;
 415                                    --m_count;
 416                                    ++removed;
 417                                }
 418                            }
 419                            if (domain_count <= min_count)
 420                            {
 421                                break;
 422                            }
 423                        }
 424
 425                        if (domain_count > min_count && domain != null)
 426                        {
 427                            // Cannot complete aging of explicit domain (no cookie adding allowed).
 428                            return false;
 429                        }
 430                    }
 431                }
 432            }
 433
 434            // We have completed aging of the specified domain.
 435            if (domain != null)
 436            {
 437                return true;
 438            }
 439
 440            // The rest is for entire container aging.
 441            // We must get at least one free slot.
 442
 443            // Don't need to apply LRU if we already cleaned something.
 444            if (removed != 0)
 445            {
 446                return true;
 447            }
 448
 449            if (oldUsed == DateTime.MaxValue)
 450            {
 451                // Something strange. Either capacity is 0 or all collections are locked with cc.Used.
 452                return false;
 453            }
 454
 455            // Remove oldest cookies from the least used collection.
 456            lock (lruCc!)
 457            {
 458                while (m_count >= m_maxCookies && lruCc.Count > 0)
 459                {
 460                    lruCc.RemoveAt(0);
 461                    --m_count;
 462                }
 463            }
 464            return true;
 465        }
 466
 467        private void DomainTableCleanup()
 468        {
 469            var removePathList = new List<object>();
 470            var removeDomainList = new List<string>();
 471
 472            string currentDomain;
 473            PathList pathList;
 474
 475            lock (m_domainTable.SyncRoot)
 476            {
 477                // Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations.
 478                IDictionaryEnumerator enumerator = m_domainTable.GetEnumerator();
 479                while (enumerator.MoveNext())
 480                {
 481                    currentDomain = (string)enumerator.Key;
 482                    pathList = (PathList)enumerator.Value!;
 483
 484                    lock (pathList.SyncRoot)
 485                    {
 486                        IDictionaryEnumerator e = pathList.GetEnumerator();
 487                        while (e.MoveNext())
 488                        {
 489                            CookieCollection cc = (CookieCollection)e.Value!;
 490                            if (cc.Count == 0)
 491                            {
 492                                removePathList.Add(e.Key);
 493                            }
 494                        }
 495
 496                        foreach (var key in removePathList)
 497                        {
 498                            pathList.Remove(key);
 499                        }
 500
 501                        removePathList.Clear();
 502                        if (pathList.Count == 0) removeDomainList.Add(currentDomain);
 503                    }
 504                }
 505
 506                foreach (var key in removeDomainList)
 507                {
 508                    m_domainTable.Remove(key);
 509                }
 510            }
 511        }
 512
 513        // Return number of cookies removed from the collection.
 514        private static int ExpireCollection(CookieCollection cc)
 515        {
 516            lock (cc)
 517            {
 518                int oldCount = cc.Count;
 519                int idx = oldCount - 1;
 520
 521                // Cannot use enumerator as we are going to alter collection.
 522                while (idx >= 0)
 523                {
 524                    Cookie cookie = cc[idx];
 525                    if (cookie.Expired)
 526                    {
 527                        cc.RemoveAt(idx);
 528                    }
 529                    --idx;
 530                }
 531                return oldCount - cc.Count;
 532            }
 533        }
 534
 535        public void Add(CookieCollection cookies)
 536        {
 537            ArgumentNullException.ThrowIfNull(cookies);
 538
 539            foreach (Cookie c in (ICollection<Cookie>)cookies)
 540            {
 541                Add(c);
 542            }
 543        }
 544
 545        public void Add(Uri uri, Cookie cookie)
 546        {
 547            ArgumentNullException.ThrowIfNull(uri);
 548            ArgumentNullException.ThrowIfNull(cookie);
 549
 550            Cookie new_cookie = cookie.Clone();
 551            new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 552
 553            InternalAdd(new_cookie);
 554        }
 555
 556        public void Add(Uri uri, CookieCollection cookies)
 557        {
 558            ArgumentNullException.ThrowIfNull(uri);
 559            ArgumentNullException.ThrowIfNull(cookies);
 560
 561            foreach (Cookie c in cookies)
 562            {
 563                Cookie new_cookie = c.Clone();
 564                new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 565                InternalAdd(new_cookie);
 566            }
 567        }
 568
 569        internal CookieCollection CookieCutter(Uri uri, string? headerName, string setCookieHeader)
 570        {
 571            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"uri:{uri} headerName:{headerName} setCookieH
 572
 573            CookieCollection cookies = new CookieCollection();
 574            CookieVariant variant = CookieVariant.Unknown;
 575            if (headerName == null)
 576            {
 577                variant = CookieVariant.Default;
 578            }
 579            else
 580            {
 581                for (int i = 0; i < s_headerInfo.Length; ++i)
 582                {
 583                    if ((string.Equals(headerName, s_headerInfo[i].Name, StringComparison.OrdinalIgnoreCase)))
 584                    {
 585                        variant = s_headerInfo[i].Variant;
 586                    }
 587                }
 588            }
 589
 590            try
 591            {
 592                CookieParser parser = new CookieParser(setCookieHeader);
 593                do
 594                {
 595                    Cookie? cookie = parser.Get();
 596                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"CookieParser returned cookie:{cookie
 597
 598                    if (cookie == null)
 599                    {
 600                        if (parser.EndofHeader())
 601                        {
 602                            break;
 603                        }
 604                        continue;
 605                    }
 606
 607                    // Parser marks invalid cookies this way
 608                    if (string.IsNullOrEmpty(cookie.Name))
 609                    {
 610                        throw new CookieException(SR.net_cookie_format);
 611                    }
 612
 613                    // This will set the default values from the response URI
 614                    // AND will check for cookie validity
 615                    cookie.VerifyAndSetDefaults(variant, uri);
 616                    // If many same cookies arrive we collapse them into just one, hence setting
 617                    // parameter isStrict = true below
 618                    cookies.InternalAdd(cookie, true);
 619                } while (true);
 620            }
 621            catch (OutOfMemoryException)
 622            {
 623                throw;
 624            }
 625            catch (Exception e)
 626            {
 627                throw new CookieException(SR.Format(SR.net_cookie_parse_header, uri.AbsoluteUri), e);
 628            }
 629
 630            int cookiesCount = cookies.Count;
 631            for (int i = 0; i < cookiesCount; i++)
 632            {
 633                InternalAdd((Cookie)cookies[i]);
 634            }
 635
 636            return cookies;
 637        }
 638
 639        public CookieCollection GetCookies(Uri uri)
 640        {
 641            ArgumentNullException.ThrowIfNull(uri);
 642
 643            return InternalGetCookies(uri) ?? new CookieCollection();
 644        }
 645
 646        /// <summary>Gets a <see cref="CookieCollection"/> that contains all of the <see cref="Cookie"/> instances in th
 647        /// <returns>A <see cref="CookieCollection"/> that contains all of the <see cref="Cookie"/> instances in the con
 648        public CookieCollection GetAllCookies()
 649        {
 650            var result = new CookieCollection();
 651
 652            lock (m_domainTable.SyncRoot)
 653            {
 654                IDictionaryEnumerator lists = m_domainTable.GetEnumerator();
 655                while (lists.MoveNext())
 656                {
 657                    PathList list = (PathList)lists.Value!;
 658                    lock (list.SyncRoot)
 659                    {
 660                        IDictionaryEnumerator collections = list.List.GetEnumerator();
 661                        while (collections.MoveNext())
 662                        {
 663                            result.Add((CookieCollection)collections.Value!);
 664                        }
 665                    }
 666                }
 667            }
 668
 669            return result;
 670        }
 671
 672        internal CookieCollection? InternalGetCookies(Uri uri)
 673        {
 674            if (m_count == 0)
 675            {
 676                return null;
 677            }
 678
 679            bool isSecure = (uri.Scheme == UriScheme.Https || uri.Scheme == UriScheme.Wss);
 680            int port = uri.Port;
 681            CookieCollection? cookies = null;
 682
 683            List<string> matchingDomainKeys = [uri.Host];
 684            ReadOnlySpan<char> host = uri.Host;
 685            int lastDot = host.LastIndexOf('.');
 686            while (lastDot > 0)
 687            {
 688                int dot = host[..lastDot].LastIndexOf('.');
 689                if (dot > 0)
 690                {
 691                    string match = host[(dot + 1)..].ToString();
 692                    matchingDomainKeys.Add(match);
 693                }
 694
 695                lastDot = dot;
 696            }
 697
 698            BuildCookieCollectionFromDomainMatches(uri, isSecure, port, ref cookies, matchingDomainKeys);
 699            return cookies;
 700        }
 701
 702        private void BuildCookieCollectionFromDomainMatches(Uri uri, bool isSecure, int port, ref CookieCollection? cook
 703        {
 704            for (int i = 0; i < matchingDomainKeys.Count; i++)
 705            {
 706                PathList pathList;
 707                lock (m_domainTable.SyncRoot)
 708                {
 709                    pathList = (PathList)m_domainTable[matchingDomainKeys[i]]!;
 710                    if (pathList == null)
 711                    {
 712                        continue;
 713                    }
 714                }
 715
 716                lock (pathList.SyncRoot)
 717                {
 718                    SortedList list = pathList.List;
 719                    int listCount = list.Count;
 720                    for (int e = 0; e < listCount; e++)
 721                    {
 722                        string path = (string)list.GetKey(e);
 723                        if (PathMatch(uri.AbsolutePath, path))
 724                        {
 725                            CookieCollection cc = (CookieCollection)list.GetByIndex(e)!;
 726                            cc.TimeStamp(CookieCollection.Stamp.Set);
 727                            MergeUpdateCollections(ref cookies, uri.Host, cc, port, isSecure);
 728                        }
 729                    }
 730                }
 731
 732                // Remove unused domain.
 733                if (pathList.Count == 0)
 734                {
 735                    lock (m_domainTable.SyncRoot)
 736                    {
 737                        m_domainTable.Remove(matchingDomainKeys[i]);
 738                    }
 739                }
 740            }
 741        }
 742
 743        // Implement path-matching according to https://tools.ietf.org/html/rfc6265#section-5.1.4:
 744        // | A request-path path-matches a given cookie-path if at least one of the following conditions holds:
 745        // | - The cookie-path and the request-path are identical.
 746        // | - The cookie-path is a prefix of the request-path, and the last character of the cookie-path is %x2F ("/").
 747        // | - The cookie-path is a prefix of the request-path, and the first character of the request-path that is not 
 748        // The latter conditions are needed to make sure that
 749        // PathMatch("/fooBar, "/foo") == false
 750        // but:
 751        // PathMatch("/foo/bar", "/foo") == true, PathMatch("/foo/bar", "/foo/") == true
 752        private static bool PathMatch(string requestPath, string cookiePath)
 753        {
 754            cookiePath = CookieParser.CheckQuoted(cookiePath);
 755
 756            if (!requestPath.StartsWith(cookiePath, StringComparison.Ordinal))
 757                return false;
 758            return requestPath.Length == cookiePath.Length ||
 759                   cookiePath.EndsWith('/') ||
 760                   requestPath[cookiePath.Length] == '/';
 761        }
 762
 763        private void MergeUpdateCollections(ref CookieCollection? destination, string host, CookieCollection source, int
 764        {
 765            lock (source)
 766            {
 767                // Cannot use foreach as we are going to update 'source'
 768                for (int idx = 0; idx < source.Count; ++idx)
 769                {
 770                    bool to_add = false;
 771
 772                    Cookie cookie = source[idx];
 773
 774                    if (cookie.Expired)
 775                    {
 776                        // If expired, remove from container and don't add to the destination
 777                        source.RemoveAt(idx);
 778                        --m_count;
 779                        --idx;
 780                    }
 781                    else
 782                    {
 783                        if (cookie.PortList != null)
 784                        {
 785                            foreach (int p in cookie.PortList)
 786                            {
 787                                if (p == port)
 788                                {
 789                                    to_add = true;
 790                                    break;
 791                                }
 792                            }
 793                        }
 794                        else
 795                        {
 796                            // It was implicit Port, always OK to add.
 797                            to_add = true;
 798                        }
 799
 800                        // Refuse to add a secure cookie into an 'unsecure' destination
 801                        if (cookie.Secure && !isSecure)
 802                        {
 803                            to_add = false;
 804                        }
 805
 806                        // For implicit domains exact match is needed
 807                        if (cookie.DomainImplicit && !string.Equals(host, cookie.Domain, StringComparison.OrdinalIgnoreC
 808                        {
 809                            to_add = false;
 810                        }
 811
 812                        if (to_add)
 813                        {
 814                            // In 'source' are already ordered.
 815                            // If two same cookies come from different 'source' then they
 816                            // will follow (not replace) each other.
 817                            destination ??= new CookieCollection();
 818                            destination.InternalAdd(cookie, false);
 819                        }
 820                    }
 821                }
 822            }
 823        }
 824
 825        public string GetCookieHeader(Uri uri)
 826        {
 827            ArgumentNullException.ThrowIfNull(uri);
 828
 829            return GetCookieHeader(uri, out _);
 830        }
 831
 832        internal string GetCookieHeader(Uri uri, out string optCookie2)
 833        {
 834            CookieCollection? cookies = InternalGetCookies(uri);
 835            if (cookies == null)
 836            {
 837                optCookie2 = string.Empty;
 838                return string.Empty;
 839            }
 840
 841            string delimiter = string.Empty;
 842
 843            StringBuilder builder = StringBuilderCache.Acquire();
 844            for (int i = 0; i < cookies.Count; i++)
 845            {
 846                builder.Append(delimiter);
 847                cookies[i].ToString(builder);
 848
 849                delimiter = "; ";
 850            }
 851
 852            optCookie2 = cookies.IsOtherVersionSeen ?
 853                          (Cookie.SpecialAttributeLiteral +
 854                           CookieFields.VersionAttributeName +
 855                           Cookie.EqualsLiteral +
 856                           Cookie.MaxSupportedVersionString) : string.Empty;
 857
 858            return StringBuilderCache.GetStringAndRelease(builder);
 859        }
 860
 861        public void SetCookies(Uri uri, string cookieHeader)
 862        {
 863            ArgumentNullException.ThrowIfNull(uri);
 864            ArgumentNullException.ThrowIfNull(cookieHeader);
 865
 866            CookieCutter(uri, null, cookieHeader); // Will throw on error
 867        }
 868    }
 869
 870    // PathList needs to be public in order to maintain binary serialization compatibility as the System shim
 871    // needs to have access to type-forward it.
 872    [Serializable]
 873    [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c5
 874    public sealed class PathList
 875    {
 876        // Usage of PathList depends on it being shallowly immutable;
 877        // adding any mutable fields to it would result in breaks.
 0878        private readonly SortedList m_list = SortedList.Synchronized(new SortedList(PathListComparer.StaticInstance)); /
 879
 0880        internal int Count => m_list.Count;
 881
 882        internal int GetCookiesCount()
 0883        {
 0884            int count = 0;
 0885            lock (SyncRoot)
 0886            {
 0887                IList list = m_list.GetValueList();
 0888                int listCount = list.Count;
 0889                for (int i = 0; i < listCount; i++)
 0890                {
 0891                    count += ((CookieCollection)list[i]!).Count;
 0892                }
 0893            }
 0894            return count;
 0895        }
 896
 897        internal ICollection Values
 898        {
 899            get
 0900            {
 0901                return m_list.Values;
 0902            }
 903        }
 904
 905        internal object? this[string s]
 906        {
 907            get
 0908            {
 0909                lock (SyncRoot)
 0910                {
 0911                    return m_list[s];
 912                }
 0913            }
 914            set
 0915            {
 0916                lock (SyncRoot)
 0917                {
 0918                    Debug.Assert(value != null);
 0919                    m_list[s] = value;
 0920                }
 0921            }
 922        }
 923
 924        internal IDictionaryEnumerator GetEnumerator()
 0925        {
 0926            lock (SyncRoot)
 0927            {
 0928                return m_list.GetEnumerator();
 929            }
 0930        }
 931
 932        internal void Remove(object key)
 0933        {
 0934            lock (SyncRoot)
 0935            {
 0936                m_list.Remove(key);
 0937            }
 0938        }
 939
 0940        internal SortedList List => m_list;
 941
 0942        internal object SyncRoot => m_list.SyncRoot;
 943
 944        [Serializable]
 945        [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77
 946        private sealed class PathListComparer : IComparer
 947        {
 0948            internal static readonly PathListComparer StaticInstance = new PathListComparer();
 949
 950            int IComparer.Compare(object? ol, object? or)
 0951            {
 0952                string pathLeft = CookieParser.CheckQuoted((string)ol!);
 0953                string pathRight = CookieParser.CheckQuoted((string)or!);
 0954                int ll = pathLeft.Length;
 0955                int lr = pathRight.Length;
 0956                int length = Math.Min(ll, lr);
 957
 0958                for (int i = 0; i < length; ++i)
 0959                {
 0960                    if (pathLeft[i] != pathRight[i])
 0961                    {
 0962                        return pathLeft[i] - pathRight[i];
 963                    }
 0964                }
 0965                return lr - ll;
 0966            }
 967        }
 968    }
 969}
 970