< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 541
Coverable lines: 541
Total lines: 970
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 184
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor()100%110%
.ctor(...)100%110%
.ctor(...)0%660%
Add(...)0%12120%
InternalAdd(...)0%22220%
AgeCookies(...)0%38380%
DomainTableCleanup()0%12120%
ExpireCollection(...)0%440%
Add(...)0%220%
Add(...)100%110%
Add(...)0%220%
CookieCutter(...)0%18180%
GetCookies(...)0%220%
GetAllCookies()0%440%
InternalGetCookies(...)0%880%
BuildCookieCollectionFromDomainMatches(...)0%10100%
PathMatch(...)0%660%
MergeUpdateCollections(...)0%22220%
GetCookieHeader(...)100%110%
GetCookieHeader(...)0%660%
SetCookies(...)100%110%

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
 073        private static readonly HeaderVariantInfo[] s_headerInfo = {
 074            new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie,  CookieVariant.Rfc2109),
 075            new HeaderVariantInfo(HttpKnownHeaderNames.SetCookie2, CookieVariant.Rfc2965)
 076        };
 77
 078        private readonly Hashtable m_domainTable = new Hashtable(); // Do not rename (binary serialization)
 079        private int m_maxCookieSize = DefaultCookieLengthLimit; // Do not rename (binary serialization)
 080        private int m_maxCookies = DefaultCookieLimit; // Do not rename (binary serialization)
 081        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
 084        private readonly string m_fqdnMyDomain = string.Empty;
 85#pragma warning restore CA1823 // Avoid unused private fields
 86
 087        public CookieContainer()
 088        {
 089        }
 90
 091        public CookieContainer(int capacity)
 092        {
 093            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
 094            m_maxCookies = capacity;
 095        }
 96
 097        public CookieContainer(int capacity, int perDomainCapacity, int maxCookieSize) : this(capacity)
 098        {
 099            if (perDomainCapacity != int.MaxValue && (perDomainCapacity <= 0 || perDomainCapacity > capacity))
 0100            {
 0101                throw new ArgumentOutOfRangeException(nameof(perDomainCapacity), SR.Format(SR.net_cookie_capacity_range,
 102            }
 0103            m_maxCookiesPerDomain = perDomainCapacity;
 0104            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxCookieSize);
 0105            m_maxCookieSize = maxCookieSize;
 0106        }
 107
 108        // NOTE: after shrinking the capacity, Count can become greater than Capacity.
 109        public int Capacity
 110        {
 111            get
 0112            {
 0113                return m_maxCookies;
 0114            }
 115            set
 0116            {
 0117                if (value <= 0 || (value < m_maxCookiesPerDomain && m_maxCookiesPerDomain != int.MaxValue))
 0118                {
 0119                    throw new ArgumentOutOfRangeException(nameof(value), SR.Format(SR.net_cookie_capacity_range, "Capaci
 120                }
 0121                if (value < m_maxCookies)
 0122                {
 0123                    m_maxCookies = value;
 0124                    AgeCookies(null);
 0125                }
 0126                m_maxCookies = value;
 0127            }
 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
 0136            {
 0137                return m_count;
 0138            }
 139        }
 140
 141        public int MaxCookieSize
 142        {
 143            get
 0144            {
 0145                return m_maxCookieSize;
 0146            }
 147            set
 0148            {
 0149                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 0150                m_maxCookieSize = value;
 0151            }
 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
 0160            {
 0161                return m_maxCookiesPerDomain;
 0162            }
 163            set
 0164            {
 0165                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
 0166                if (value != int.MaxValue)
 0167                {
 0168                    ArgumentOutOfRangeException.ThrowIfGreaterThan(value, m_maxCookies);
 0169                }
 170
 0171                if (value < m_maxCookiesPerDomain)
 0172                {
 0173                    m_maxCookiesPerDomain = value;
 0174                    AgeCookies(null);
 0175                }
 0176                m_maxCookiesPerDomain = value;
 0177            }
 178        }
 179
 180        // This method will construct a faked URI: the Domain property is required for param.
 181        public void Add(Cookie cookie)
 0182        {
 0183            ArgumentNullException.ThrowIfNull(cookie);
 184
 0185            if (cookie.Domain.Length == 0)
 0186            {
 0187                throw new ArgumentException(
 0188                    SR.Format(SR.net_emptystringcall, nameof(cookie) + "." + nameof(cookie.Domain)),
 0189                    nameof(cookie));
 190            }
 191
 192            Uri? uri;
 0193            var uriSb = new StringBuilder();
 194
 195            // We cannot add an invalid cookie into the container.
 196            // Trying to prepare Uri for the cookie verification.
 0197            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.
 0200            if (!cookie.DomainImplicit)
 0201            {
 0202                if (cookie.Domain[0] == '.')
 0203                {
 0204                    uriSb.Append('0'); // URI cctor should consume this faked host.
 0205                }
 0206            }
 0207            uriSb.Append(cookie.Domain);
 208
 209
 210            // Either keep Port as implicit or set it according to original cookie.
 0211            if (cookie.PortList != null)
 0212            {
 0213                uriSb.Append(':').Append(cookie.PortList[0]);
 0214            }
 215
 216            // Path must be present, set to root by default.
 0217            uriSb.Append(cookie.Path);
 218
 0219            if (!Uri.TryCreate(uriSb.ToString(), UriKind.Absolute, out uri))
 0220                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.
 0223            Cookie new_cookie = cookie.Clone();
 0224            new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 225
 0226            InternalAdd(new_cookie);
 0227        }
 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)
 0232        {
 233            PathList? pathList;
 234
 0235            if (cookie.Value.Length > m_maxCookieSize)
 0236            {
 0237                throw new CookieException(SR.Format(SR.net_cookie_size, cookie, m_maxCookieSize));
 238            }
 239
 240            try
 0241            {
 0242                lock (m_domainTable.SyncRoot)
 0243                {
 0244                    pathList = (PathList?)m_domainTable[cookie.DomainKey];
 0245                    if (pathList == null)
 0246                    {
 0247                        m_domainTable[cookie.DomainKey] = (pathList = new PathList());
 0248                    }
 0249                }
 0250                int domain_count = pathList.GetCookiesCount();
 251
 252                CookieCollection? cookies;
 0253                lock (pathList.SyncRoot)
 0254                {
 0255                    cookies = (CookieCollection?)pathList[cookie.Path]!;
 256
 0257                    if (cookies == null)
 0258                    {
 0259                        cookies = new CookieCollection();
 0260                        pathList[cookie.Path] = cookies;
 0261                    }
 0262                }
 263
 0264                if (cookie.Expired)
 0265                {
 266                    // Explicit removal command (Max-Age == 0)
 0267                    lock (cookies)
 0268                    {
 0269                        int idx = cookies.IndexOf(cookie);
 0270                        if (idx != -1)
 0271                        {
 0272                            cookies.RemoveAt(idx);
 0273                            --m_count;
 0274                        }
 0275                    }
 0276                }
 277                else
 0278                {
 279                    // This is about real cookie adding, check Capacity first
 0280                    if (domain_count >= m_maxCookiesPerDomain && !AgeCookies(cookie.DomainKey))
 0281                    {
 0282                        return; // Cannot age: reject new cookie
 283                    }
 0284                    else if (m_count >= m_maxCookies && !AgeCookies(null))
 0285                    {
 0286                        return; // Cannot age: reject new cookie
 287                    }
 288
 289                    // About to change the collection.
 0290                    lock (cookies)
 0291                    {
 0292                        m_count += cookies.InternalAdd(cookie, true);
 0293                    }
 0294                }
 295
 296                // We don't want to cleanup m_domaintable/m_list too often. Add check to avoid overhead.
 0297                if (m_domainTable.Count > m_count || pathList.Count > m_maxCookiesPerDomain)
 0298                {
 0299                    DomainTableCleanup();
 0300                }
 0301            }
 0302            catch (OutOfMemoryException)
 0303            {
 0304                throw;
 305            }
 0306            catch (Exception e)
 0307            {
 0308                throw new CookieException(SR.net_container_add_cookie, e);
 309            }
 0310        }
 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)
 0322        {
 0323            Debug.Assert(m_maxCookies != 0);
 0324            Debug.Assert(m_maxCookiesPerDomain != 0);
 325
 0326            int removed = 0;
 0327            DateTime oldUsed = DateTime.MaxValue;
 328            DateTime tempUsed;
 329
 0330            CookieCollection? lruCc = null;
 331            string? lruDomain;
 332            string tempDomain;
 333
 334            PathList pathList;
 335            int domain_count;
 0336            int itemp = 0;
 0337            float remainingFraction = 1.0F;
 338
 339            // The container was shrunk, might need additional cleanup for each domain
 0340            if (m_count > m_maxCookies)
 0341            {
 342                // Means the fraction of the container to be left.
 343                // Each domain will be cut accordingly.
 0344                remainingFraction = (float)m_maxCookies / (float)m_count;
 0345            }
 0346            lock (m_domainTable.SyncRoot)
 0347            {
 0348                foreach (object item in m_domainTable)
 0349                {
 0350                    DictionaryEntry entry = (DictionaryEntry)item;
 0351                    if (domain == null)
 0352                    {
 0353                        tempDomain = (string)entry.Key;
 0354                        pathList = (PathList)entry.Value!; // Aliasing to trick foreach
 0355                    }
 356                    else
 0357                    {
 0358                        tempDomain = domain;
 0359                        pathList = (PathList)m_domainTable[domain]!;
 0360                    }
 361
 0362                    domain_count = 0; // Cookies in the domain
 0363                    lock (pathList.SyncRoot)
 0364                    {
 0365                        foreach (CookieCollection? cc in pathList.Values)
 0366                        {
 0367                            Debug.Assert(cc != null);
 0368                            itemp = ExpireCollection(cc);
 0369                            removed += itemp;
 0370                            m_count -= itemp; // Update this container's count
 0371                            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.
 0375                            if (cc.Count > 0 && (tempUsed = cc.TimeStamp(CookieCollection.Stamp.Check)) < oldUsed)
 0376                            {
 0377                                lruDomain = tempDomain;
 0378                                lruCc = cc;
 0379                                oldUsed = tempUsed;
 0380                            }
 0381                        }
 0382                    }
 383
 384                    // Check if we have reduced to the limit of the domain by expiration only.
 0385                    int min_count = Math.Min((int)(domain_count * remainingFraction), Math.Min(m_maxCookiesPerDomain, m_
 0386                    if (domain_count > min_count)
 0387                    {
 388                        // This case requires sorting all domain collections by timestamp.
 389                        CookieCollection[] cookies;
 390                        DateTime[] stamps;
 0391                        lock (pathList.SyncRoot)
 0392                        {
 0393                            cookies = new CookieCollection[pathList.Count];
 0394                            stamps = new DateTime[pathList.Count];
 0395                            foreach (CookieCollection? cc in pathList.Values)
 0396                            {
 0397                                stamps[itemp] = cc!.TimeStamp(CookieCollection.Stamp.Check);
 0398                                cookies[itemp] = cc;
 0399                                ++itemp;
 0400                            }
 0401                        }
 0402                        Array.Sort(stamps, cookies);
 403
 0404                        itemp = 0;
 0405                        for (int i = 0; i < cookies.Length; ++i)
 0406                        {
 0407                            CookieCollection cc = cookies[i];
 408
 0409                            lock (cc)
 0410                            {
 0411                                while (domain_count > min_count && cc.Count > 0)
 0412                                {
 0413                                    cc.RemoveAt(0);
 0414                                    --domain_count;
 0415                                    --m_count;
 0416                                    ++removed;
 0417                                }
 0418                            }
 0419                            if (domain_count <= min_count)
 0420                            {
 0421                                break;
 422                            }
 0423                        }
 424
 0425                        if (domain_count > min_count && domain != null)
 0426                        {
 427                            // Cannot complete aging of explicit domain (no cookie adding allowed).
 0428                            return false;
 429                        }
 0430                    }
 0431                }
 0432            }
 433
 434            // We have completed aging of the specified domain.
 0435            if (domain != null)
 0436            {
 0437                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.
 0444            if (removed != 0)
 0445            {
 0446                return true;
 447            }
 448
 0449            if (oldUsed == DateTime.MaxValue)
 0450            {
 451                // Something strange. Either capacity is 0 or all collections are locked with cc.Used.
 0452                return false;
 453            }
 454
 455            // Remove oldest cookies from the least used collection.
 0456            lock (lruCc!)
 0457            {
 0458                while (m_count >= m_maxCookies && lruCc.Count > 0)
 0459                {
 0460                    lruCc.RemoveAt(0);
 0461                    --m_count;
 0462                }
 0463            }
 0464            return true;
 0465        }
 466
 467        private void DomainTableCleanup()
 0468        {
 0469            var removePathList = new List<object>();
 0470            var removeDomainList = new List<string>();
 471
 472            string currentDomain;
 473            PathList pathList;
 474
 0475            lock (m_domainTable.SyncRoot)
 0476            {
 477                // Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations.
 0478                IDictionaryEnumerator enumerator = m_domainTable.GetEnumerator();
 0479                while (enumerator.MoveNext())
 0480                {
 0481                    currentDomain = (string)enumerator.Key;
 0482                    pathList = (PathList)enumerator.Value!;
 483
 0484                    lock (pathList.SyncRoot)
 0485                    {
 0486                        IDictionaryEnumerator e = pathList.GetEnumerator();
 0487                        while (e.MoveNext())
 0488                        {
 0489                            CookieCollection cc = (CookieCollection)e.Value!;
 0490                            if (cc.Count == 0)
 0491                            {
 0492                                removePathList.Add(e.Key);
 0493                            }
 0494                        }
 495
 0496                        foreach (var key in removePathList)
 0497                        {
 0498                            pathList.Remove(key);
 0499                        }
 500
 0501                        removePathList.Clear();
 0502                        if (pathList.Count == 0) removeDomainList.Add(currentDomain);
 0503                    }
 0504                }
 505
 0506                foreach (var key in removeDomainList)
 0507                {
 0508                    m_domainTable.Remove(key);
 0509                }
 0510            }
 0511        }
 512
 513        // Return number of cookies removed from the collection.
 514        private static int ExpireCollection(CookieCollection cc)
 0515        {
 0516            lock (cc)
 0517            {
 0518                int oldCount = cc.Count;
 0519                int idx = oldCount - 1;
 520
 521                // Cannot use enumerator as we are going to alter collection.
 0522                while (idx >= 0)
 0523                {
 0524                    Cookie cookie = cc[idx];
 0525                    if (cookie.Expired)
 0526                    {
 0527                        cc.RemoveAt(idx);
 0528                    }
 0529                    --idx;
 0530                }
 0531                return oldCount - cc.Count;
 532            }
 0533        }
 534
 535        public void Add(CookieCollection cookies)
 0536        {
 0537            ArgumentNullException.ThrowIfNull(cookies);
 538
 0539            foreach (Cookie c in (ICollection<Cookie>)cookies)
 0540            {
 0541                Add(c);
 0542            }
 0543        }
 544
 545        public void Add(Uri uri, Cookie cookie)
 0546        {
 0547            ArgumentNullException.ThrowIfNull(uri);
 0548            ArgumentNullException.ThrowIfNull(cookie);
 549
 0550            Cookie new_cookie = cookie.Clone();
 0551            new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 552
 0553            InternalAdd(new_cookie);
 0554        }
 555
 556        public void Add(Uri uri, CookieCollection cookies)
 0557        {
 0558            ArgumentNullException.ThrowIfNull(uri);
 0559            ArgumentNullException.ThrowIfNull(cookies);
 560
 0561            foreach (Cookie c in cookies)
 0562            {
 0563                Cookie new_cookie = c.Clone();
 0564                new_cookie.VerifyAndSetDefaults(new_cookie.Variant, uri);
 0565                InternalAdd(new_cookie);
 0566            }
 0567        }
 568
 569        internal CookieCollection CookieCutter(Uri uri, string? headerName, string setCookieHeader)
 0570        {
 0571            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"uri:{uri} headerName:{headerName} setCookieH
 572
 0573            CookieCollection cookies = new CookieCollection();
 0574            CookieVariant variant = CookieVariant.Unknown;
 0575            if (headerName == null)
 0576            {
 0577                variant = CookieVariant.Default;
 0578            }
 579            else
 0580            {
 0581                for (int i = 0; i < s_headerInfo.Length; ++i)
 0582                {
 0583                    if ((string.Equals(headerName, s_headerInfo[i].Name, StringComparison.OrdinalIgnoreCase)))
 0584                    {
 0585                        variant = s_headerInfo[i].Variant;
 0586                    }
 0587                }
 0588            }
 589
 590            try
 0591            {
 0592                CookieParser parser = new CookieParser(setCookieHeader);
 593                do
 0594                {
 0595                    Cookie? cookie = parser.Get();
 0596                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"CookieParser returned cookie:{cookie
 597
 0598                    if (cookie == null)
 0599                    {
 0600                        if (parser.EndofHeader())
 0601                        {
 0602                            break;
 603                        }
 0604                        continue;
 605                    }
 606
 607                    // Parser marks invalid cookies this way
 0608                    if (string.IsNullOrEmpty(cookie.Name))
 0609                    {
 0610                        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
 0615                    cookie.VerifyAndSetDefaults(variant, uri);
 616                    // If many same cookies arrive we collapse them into just one, hence setting
 617                    // parameter isStrict = true below
 0618                    cookies.InternalAdd(cookie, true);
 0619                } while (true);
 0620            }
 0621            catch (OutOfMemoryException)
 0622            {
 0623                throw;
 624            }
 0625            catch (Exception e)
 0626            {
 0627                throw new CookieException(SR.Format(SR.net_cookie_parse_header, uri.AbsoluteUri), e);
 628            }
 629
 0630            int cookiesCount = cookies.Count;
 0631            for (int i = 0; i < cookiesCount; i++)
 0632            {
 0633                InternalAdd((Cookie)cookies[i]);
 0634            }
 635
 0636            return cookies;
 0637        }
 638
 639        public CookieCollection GetCookies(Uri uri)
 0640        {
 0641            ArgumentNullException.ThrowIfNull(uri);
 642
 0643            return InternalGetCookies(uri) ?? new CookieCollection();
 0644        }
 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()
 0649        {
 0650            var result = new CookieCollection();
 651
 0652            lock (m_domainTable.SyncRoot)
 0653            {
 0654                IDictionaryEnumerator lists = m_domainTable.GetEnumerator();
 0655                while (lists.MoveNext())
 0656                {
 0657                    PathList list = (PathList)lists.Value!;
 0658                    lock (list.SyncRoot)
 0659                    {
 0660                        IDictionaryEnumerator collections = list.List.GetEnumerator();
 0661                        while (collections.MoveNext())
 0662                        {
 0663                            result.Add((CookieCollection)collections.Value!);
 0664                        }
 0665                    }
 0666                }
 0667            }
 668
 0669            return result;
 0670        }
 671
 672        internal CookieCollection? InternalGetCookies(Uri uri)
 0673        {
 0674            if (m_count == 0)
 0675            {
 0676                return null;
 677            }
 678
 0679            bool isSecure = (uri.Scheme == UriScheme.Https || uri.Scheme == UriScheme.Wss);
 0680            int port = uri.Port;
 0681            CookieCollection? cookies = null;
 682
 0683            List<string> matchingDomainKeys = [uri.Host];
 0684            ReadOnlySpan<char> host = uri.Host;
 0685            int lastDot = host.LastIndexOf('.');
 0686            while (lastDot > 0)
 0687            {
 0688                int dot = host[..lastDot].LastIndexOf('.');
 0689                if (dot > 0)
 0690                {
 0691                    string match = host[(dot + 1)..].ToString();
 0692                    matchingDomainKeys.Add(match);
 0693                }
 694
 0695                lastDot = dot;
 0696            }
 697
 0698            BuildCookieCollectionFromDomainMatches(uri, isSecure, port, ref cookies, matchingDomainKeys);
 0699            return cookies;
 0700        }
 701
 702        private void BuildCookieCollectionFromDomainMatches(Uri uri, bool isSecure, int port, ref CookieCollection? cook
 0703        {
 0704            for (int i = 0; i < matchingDomainKeys.Count; i++)
 0705            {
 706                PathList pathList;
 0707                lock (m_domainTable.SyncRoot)
 0708                {
 0709                    pathList = (PathList)m_domainTable[matchingDomainKeys[i]]!;
 0710                    if (pathList == null)
 0711                    {
 0712                        continue;
 713                    }
 0714                }
 715
 0716                lock (pathList.SyncRoot)
 0717                {
 0718                    SortedList list = pathList.List;
 0719                    int listCount = list.Count;
 0720                    for (int e = 0; e < listCount; e++)
 0721                    {
 0722                        string path = (string)list.GetKey(e);
 0723                        if (PathMatch(uri.AbsolutePath, path))
 0724                        {
 0725                            CookieCollection cc = (CookieCollection)list.GetByIndex(e)!;
 0726                            cc.TimeStamp(CookieCollection.Stamp.Set);
 0727                            MergeUpdateCollections(ref cookies, uri.Host, cc, port, isSecure);
 0728                        }
 0729                    }
 0730                }
 731
 732                // Remove unused domain.
 0733                if (pathList.Count == 0)
 0734                {
 0735                    lock (m_domainTable.SyncRoot)
 0736                    {
 0737                        m_domainTable.Remove(matchingDomainKeys[i]);
 0738                    }
 0739                }
 0740            }
 0741        }
 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)
 0753        {
 0754            cookiePath = CookieParser.CheckQuoted(cookiePath);
 755
 0756            if (!requestPath.StartsWith(cookiePath, StringComparison.Ordinal))
 0757                return false;
 0758            return requestPath.Length == cookiePath.Length ||
 0759                   cookiePath.EndsWith('/') ||
 0760                   requestPath[cookiePath.Length] == '/';
 0761        }
 762
 763        private void MergeUpdateCollections(ref CookieCollection? destination, string host, CookieCollection source, int
 0764        {
 0765            lock (source)
 0766            {
 767                // Cannot use foreach as we are going to update 'source'
 0768                for (int idx = 0; idx < source.Count; ++idx)
 0769                {
 0770                    bool to_add = false;
 771
 0772                    Cookie cookie = source[idx];
 773
 0774                    if (cookie.Expired)
 0775                    {
 776                        // If expired, remove from container and don't add to the destination
 0777                        source.RemoveAt(idx);
 0778                        --m_count;
 0779                        --idx;
 0780                    }
 781                    else
 0782                    {
 0783                        if (cookie.PortList != null)
 0784                        {
 0785                            foreach (int p in cookie.PortList)
 0786                            {
 0787                                if (p == port)
 0788                                {
 0789                                    to_add = true;
 0790                                    break;
 791                                }
 0792                            }
 0793                        }
 794                        else
 0795                        {
 796                            // It was implicit Port, always OK to add.
 0797                            to_add = true;
 0798                        }
 799
 800                        // Refuse to add a secure cookie into an 'unsecure' destination
 0801                        if (cookie.Secure && !isSecure)
 0802                        {
 0803                            to_add = false;
 0804                        }
 805
 806                        // For implicit domains exact match is needed
 0807                        if (cookie.DomainImplicit && !string.Equals(host, cookie.Domain, StringComparison.OrdinalIgnoreC
 0808                        {
 0809                            to_add = false;
 0810                        }
 811
 0812                        if (to_add)
 0813                        {
 814                            // In 'source' are already ordered.
 815                            // If two same cookies come from different 'source' then they
 816                            // will follow (not replace) each other.
 0817                            destination ??= new CookieCollection();
 0818                            destination.InternalAdd(cookie, false);
 0819                        }
 0820                    }
 0821                }
 0822            }
 0823        }
 824
 825        public string GetCookieHeader(Uri uri)
 0826        {
 0827            ArgumentNullException.ThrowIfNull(uri);
 828
 0829            return GetCookieHeader(uri, out _);
 0830        }
 831
 832        internal string GetCookieHeader(Uri uri, out string optCookie2)
 0833        {
 0834            CookieCollection? cookies = InternalGetCookies(uri);
 0835            if (cookies == null)
 0836            {
 0837                optCookie2 = string.Empty;
 0838                return string.Empty;
 839            }
 840
 0841            string delimiter = string.Empty;
 842
 0843            StringBuilder builder = StringBuilderCache.Acquire();
 0844            for (int i = 0; i < cookies.Count; i++)
 0845            {
 0846                builder.Append(delimiter);
 0847                cookies[i].ToString(builder);
 848
 0849                delimiter = "; ";
 0850            }
 851
 0852            optCookie2 = cookies.IsOtherVersionSeen ?
 0853                          (Cookie.SpecialAttributeLiteral +
 0854                           CookieFields.VersionAttributeName +
 0855                           Cookie.EqualsLiteral +
 0856                           Cookie.MaxSupportedVersionString) : string.Empty;
 857
 0858            return StringBuilderCache.GetStringAndRelease(builder);
 0859        }
 860
 861        public void SetCookies(Uri uri, string cookieHeader)
 0862        {
 0863            ArgumentNullException.ThrowIfNull(uri);
 0864            ArgumentNullException.ThrowIfNull(cookieHeader);
 865
 0866            CookieCutter(uri, null, cookieHeader); // Will throw on error
 0867        }
 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.
 878        private readonly SortedList m_list = SortedList.Synchronized(new SortedList(PathListComparer.StaticInstance)); /
 879
 880        internal int Count => m_list.Count;
 881
 882        internal int GetCookiesCount()
 883        {
 884            int count = 0;
 885            lock (SyncRoot)
 886            {
 887                IList list = m_list.GetValueList();
 888                int listCount = list.Count;
 889                for (int i = 0; i < listCount; i++)
 890                {
 891                    count += ((CookieCollection)list[i]!).Count;
 892                }
 893            }
 894            return count;
 895        }
 896
 897        internal ICollection Values
 898        {
 899            get
 900            {
 901                return m_list.Values;
 902            }
 903        }
 904
 905        internal object? this[string s]
 906        {
 907            get
 908            {
 909                lock (SyncRoot)
 910                {
 911                    return m_list[s];
 912                }
 913            }
 914            set
 915            {
 916                lock (SyncRoot)
 917                {
 918                    Debug.Assert(value != null);
 919                    m_list[s] = value;
 920                }
 921            }
 922        }
 923
 924        internal IDictionaryEnumerator GetEnumerator()
 925        {
 926            lock (SyncRoot)
 927            {
 928                return m_list.GetEnumerator();
 929            }
 930        }
 931
 932        internal void Remove(object key)
 933        {
 934            lock (SyncRoot)
 935            {
 936                m_list.Remove(key);
 937            }
 938        }
 939
 940        internal SortedList List => m_list;
 941
 942        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        {
 948            internal static readonly PathListComparer StaticInstance = new PathListComparer();
 949
 950            int IComparer.Compare(object? ol, object? or)
 951            {
 952                string pathLeft = CookieParser.CheckQuoted((string)ol!);
 953                string pathRight = CookieParser.CheckQuoted((string)or!);
 954                int ll = pathLeft.Length;
 955                int lr = pathRight.Length;
 956                int length = Math.Min(ll, lr);
 957
 958                for (int i = 0; i < length; ++i)
 959                {
 960                    if (pathLeft[i] != pathRight[i])
 961                    {
 962                        return pathLeft[i] - pathRight[i];
 963                    }
 964                }
 965                return lr - ll;
 966            }
 967        }
 968    }
 969}
 970