< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 406
Coverable lines: 406
Total lines: 753
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 216
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(...)100%110%
.ctor(...)100%110%
InternalSetName(...)0%10100%
Clone()0%440%
SetDomainAndKey(...)100%110%
HostMatchesDomain(...)0%880%
VerifyAndSetDefaults(...)0%80800%
IsValidDomainName(...)0%220%
Equals(...)0%10100%
GetHashCode()100%110%
ToString()100%110%
ToString(...)0%28280%
ToServerString()0%36360%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Globalization;
 9using System.Text;
 10
 11namespace System.Net
 12{
 13    [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c5
 14    public enum CookieVariant
 15    {
 16        Unknown,
 17        Plain,
 18        Rfc2109,
 19        Rfc2965,
 20        Default = Rfc2109
 21    }
 22
 23    // Cookie class
 24    //
 25    // Adheres to RFC 2965
 26    //
 27    // Currently, only represents client-side cookies. The cookie classes know
 28    // how to parse a set-cookie format string, but not a cookie format string
 29    // (e.g. "Cookie: $Version=1; name=value; $Path=/foo; $Secure")
 30    [Serializable]
 31    [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c5
 32    public sealed class Cookie
 33    {
 34        // NOTE: these two constants must change together.
 35        internal const int MaxSupportedVersion = 1;
 36        internal const string MaxSupportedVersionString = "1";
 37
 38        internal const string SeparatorLiteral = "; ";
 39        internal const char EqualsLiteral = '=';
 40        internal const string QuotesLiteral = "\"";
 41        internal const string SpecialAttributeLiteral = "$";
 42
 043        internal static readonly char[] PortSplitDelimiters = new char[] { ' ', ',', '\"' };
 44        // Space (' ') should be reserved as well per RFCs, but major web browsers support it and some web sites use it 
 045        private static readonly SearchValues<char> s_reservedToNameChars = SearchValues.Create("\t\0\r\n=;,");
 46
 047        private static readonly SearchValues<char> s_domainChars =
 048            SearchValues.Create("-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz");
 49
 050        private string m_comment = string.Empty; // Do not rename (binary serialization)
 51        private Uri? m_commentUri; // Do not rename (binary serialization)
 052        private CookieVariant m_cookieVariant = CookieVariant.Plain; // Do not rename (binary serialization)
 53        private bool m_discard; // Do not rename (binary serialization)
 054        private string m_domain = string.Empty; // Do not rename (binary serialization)
 055        private bool m_domain_implicit = true; // Do not rename (binary serialization)
 056        private DateTime m_expires = DateTime.MinValue; // Do not rename (binary serialization)
 057        private string m_name = string.Empty; // Do not rename (binary serialization)
 058        private string m_path = string.Empty; // Do not rename (binary serialization)
 059        private bool m_path_implicit = true; // Do not rename (binary serialization)
 060        private string m_port = string.Empty; // Do not rename (binary serialization)
 061        private bool m_port_implicit = true; // Do not rename (binary serialization)
 62        private int[]? m_port_list; // Do not rename (binary serialization)
 63        private bool m_secure; // Do not rename (binary serialization)
 64        [System.Runtime.Serialization.OptionalField]
 065        private bool m_httpOnly = false; // Do not rename (binary serialization)
 066        private DateTime m_timeStamp = DateTime.UtcNow; // Do not rename (binary serialization)
 067        private string m_value = string.Empty; // Do not rename (binary serialization)
 68        private int m_version; // Do not rename (binary serialization)
 69
 70        private string? m_domainKey; // Do not rename (binary serialization)
 71
 72#pragma warning disable 0649 // set via reflection by CookieParser: https://github.com/dotnet/runtime/issues/19348
 73        internal bool IsQuotedVersion; // Do not rename (binary serialization)
 74        internal bool IsQuotedDomain; // Do not rename (binary serialization)
 75#pragma warning restore 0649
 76
 77#if DEBUG
 78        static Cookie()
 079        {
 080            Debug.Assert(MaxSupportedVersion.ToString(NumberFormatInfo.InvariantInfo).Equals(MaxSupportedVersionString, 
 081        }
 82#endif
 83
 84        // These DynamicDependency attributes are a workaround for https://github.com/dotnet/runtime/issues/19348.
 85        // HttpListener uses the non-public ToServerString, which isn't used by anything else in this assembly,
 86        // and which accesses other internals and can't be moved to HttpListener (at least not without incurring
 87        // functional differences).  However, once we do our initial System.Net.Primitives build and ToServerString
 88        // survives to it, we no longer want the DynamicDependencyAttribute to remain around, so that ToServerString
 89        // can be trimmed out if the relevant functionality from HttpListener isn't used when performing whole-app
 90        // analysis.  As such, when trimming System.Net.Primitives, we build the assembly with ILLinkKeepDepAttributes=f
 91        // such that when this assembly is compiled, ToServerString will remain but the DynamicDependency attributes
 92        // will be removed.  This hack will need to be revisited if anything else in the assembly starts using
 93        // DynamicDependencyAttribute.
 94        // https://github.com/mono/linker/issues/802
 95
 96        [DynamicDependency("ToServerString")]
 097        public Cookie()
 098        {
 099        }
 100
 101        [DynamicDependency("ToServerString")] // Workaround for https://github.com/dotnet/runtime/issues/19348
 0102        public Cookie(string name, string? value)
 0103        {
 0104            Name = name;
 0105            Value = value;
 0106        }
 107
 108        public Cookie(string name, string? value, string? path)
 0109            : this(name, value)
 0110        {
 0111            Path = path;
 0112        }
 113
 114        public Cookie(string name, string? value, string? path, string? domain)
 0115            : this(name, value, path)
 0116        {
 0117            Domain = domain;
 0118        }
 119
 120        [AllowNull]
 121        public string Comment
 122        {
 123            get
 0124            {
 0125                return m_comment;
 0126            }
 127            set
 0128            {
 0129                m_comment = value ?? string.Empty;
 0130            }
 131        }
 132
 133        public Uri? CommentUri
 134        {
 135            get
 0136            {
 0137                return m_commentUri;
 0138            }
 139            set
 0140            {
 0141                m_commentUri = value;
 0142            }
 143        }
 144
 145
 146        public bool HttpOnly
 147        {
 148            get
 0149            {
 0150                return m_httpOnly;
 0151            }
 152            set
 0153            {
 0154                m_httpOnly = value;
 0155            }
 156        }
 157
 158
 159        public bool Discard
 160        {
 161            get
 0162            {
 0163                return m_discard;
 0164            }
 165            set
 0166            {
 0167                m_discard = value;
 0168            }
 169        }
 170
 171        [AllowNull]
 172        public string Domain
 173        {
 174            get
 0175            {
 0176                return m_domain;
 0177            }
 178            set
 0179            {
 0180                SetDomainAndKey(value ?? string.Empty);
 0181                m_domain_implicit = false;
 0182            }
 183        }
 184
 185        internal bool DomainImplicit
 186        {
 187            get
 0188            {
 0189                return m_domain_implicit;
 0190            }
 191            set
 0192            {
 0193                m_domain_implicit = value;
 0194            }
 195        }
 196
 197        public bool Expired
 198        {
 199            get
 0200            {
 0201                return (m_expires != DateTime.MinValue) && (m_expires.ToUniversalTime() <= DateTime.UtcNow);
 0202            }
 203            set
 0204            {
 0205                if (value)
 0206                {
 0207                    m_expires = DateTime.UtcNow;
 0208                }
 0209            }
 210        }
 211
 212        public DateTime Expires
 213        {
 214            get
 0215            {
 0216                return m_expires;
 0217            }
 218            set
 0219            {
 0220                m_expires = value;
 0221            }
 222        }
 223
 224        public string Name
 225        {
 226            get
 0227            {
 0228                return m_name;
 0229            }
 230            set
 0231            {
 0232                if (string.IsNullOrEmpty(value) || !InternalSetName(value))
 0233                {
 0234                    throw new CookieException(SR.Format(SR.net_cookie_attribute, "Name", value ?? "<null>"));
 235                }
 0236            }
 237        }
 238        internal bool InternalSetName(string? value)
 0239        {
 0240            if (string.IsNullOrEmpty(value)
 0241                || value.StartsWith('$')
 0242                || value.StartsWith(' ')
 0243                || value.EndsWith(' ')
 0244                || value.AsSpan().ContainsAny(s_reservedToNameChars))
 0245            {
 0246                m_name = string.Empty;
 0247                return false;
 248            }
 0249            m_name = value;
 0250            return true;
 0251        }
 252
 253        [AllowNull]
 254        public string Path
 255        {
 256            get
 0257            {
 0258                return m_path;
 0259            }
 260            set
 0261            {
 0262                m_path = value ?? string.Empty;
 0263                m_path_implicit = false;
 0264            }
 265        }
 266
 267        internal bool Plain
 268        {
 269            get
 0270            {
 0271                return Variant == CookieVariant.Plain;
 0272            }
 273        }
 274
 275        internal Cookie Clone()
 0276        {
 0277            Cookie clonedCookie = new Cookie(m_name, m_value);
 278
 279            // Copy over all the properties from the original cookie
 0280            if (!m_port_implicit)
 0281            {
 0282                clonedCookie.Port = m_port;
 0283            }
 0284            if (!m_path_implicit)
 0285            {
 0286                clonedCookie.Path = m_path;
 0287            }
 0288            clonedCookie.m_domain = m_domain;
 289
 290            // If the domain in the original cookie was implicit, we should preserve that property
 0291            clonedCookie.DomainImplicit = m_domain_implicit;
 0292            clonedCookie.m_domainKey = m_domainKey;
 0293            clonedCookie.m_timeStamp = m_timeStamp;
 0294            clonedCookie.Comment = m_comment;
 0295            clonedCookie.CommentUri = m_commentUri;
 0296            clonedCookie.HttpOnly = m_httpOnly;
 0297            clonedCookie.Discard = m_discard;
 0298            clonedCookie.Expires = m_expires;
 0299            clonedCookie.Version = m_version;
 0300            clonedCookie.Secure = m_secure;
 301
 302            // The variant is set when we set properties like port/version. So,
 303            // we should copy over the variant from the original cookie after
 304            // we set all other properties
 0305            clonedCookie.m_cookieVariant = m_cookieVariant;
 306
 0307            return clonedCookie;
 0308        }
 309
 310        private void SetDomainAndKey(string domain)
 0311        {
 0312            m_domain = domain;
 0313            m_domainKey = CookieComparer.StripLeadingDot(m_domain).ToString().ToLowerInvariant();
 0314        }
 315
 316        // Implements Domain Matching following RFC 6265 rules with a special handling for single-label domains.
 317        // The method assumes that 'domain' has been stripped of its optional leading dot and converted to lower case.
 318        // It checks if the condition defined in https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3 is met:
 319        // [rephrased] 'host' domain-matches 'domain' if at least one of the following conditions hold:
 320        // - 'domain' and 'host' are identical.
 321        // - All of the following conditions hold:
 322        //    * 'domain' is a suffix of 'host'
 323        //    * The last character of 'host' that is not included in 'domain' is a "."
 324        //    * 'host' is a host name (i.e., not an IP address).
 325        // Beside the RFC 6265 rules, an extra condition is included for compatibility:
 326        // in case 'domain' is a single-label domain, an exact match is required.
 327        // This is to avoid matching top-level domains, for example "test.com" should not match "com",
 328        // however this does not prevent matching multi-label public suffixes, eg. "co.uk".
 329        // Note that browsers handle this by validating against the Public Suffix List (https://publicsuffix.org/)
 330        // which is a behavior under standardization in the latest RFC drafts: https://datatracker.ietf.org/doc/draft-ie
 331        private static bool HostMatchesDomain(ReadOnlySpan<char> host, ReadOnlySpan<char> domain)
 0332        {
 0333            if (!host.EndsWith(domain, StringComparison.Ordinal))
 0334            {
 0335                return false;
 336            }
 337
 338            // The last character of the 'host' that is not included in the domain
 0339            int idxOfSeparator = host.Length - domain.Length - 1;
 0340            if (idxOfSeparator < 0)
 0341            {
 342                // 'host' and 'domain' are equal
 0343                Debug.Assert(idxOfSeparator == -1);
 0344                return true;
 345            }
 346
 0347            return host[idxOfSeparator] is '.' // The last character of 'host' that is not included in 'domain' is a "."
 0348                && domain.Contains('.') // In case of single-label domains, there should be an exact match.
 0349                && !IPAddress.IsValid(host); // If host is an IP address, there should be an exact match.
 0350        }
 351
 352        // According to spec we must assume default values for attributes but still
 353        // keep in mind that we must not include them into the requests.
 354        // We also check the validity of all attributes based on the version and variant (read RFC)
 355        //
 356        // To work properly this function must be called after cookie construction with
 357        // default (response) URI.
 358        internal void VerifyAndSetDefaults(CookieVariant variant, Uri uri)
 0359        {
 0360            string host = uri.Host;
 0361            int port = uri.Port;
 0362            string path = uri.AbsolutePath;
 363
 364            // Set Variant. If version is zero => reset cookie to Version0 style
 0365            if (Version == 0)
 0366            {
 0367                variant = CookieVariant.Plain;
 0368            }
 0369            else if (Version == 1 && variant == CookieVariant.Unknown)
 0370            {
 371                // Since we don't expose Variant to an app, set it to Default
 0372                variant = CookieVariant.Default;
 0373            }
 0374            m_cookieVariant = variant;
 375
 376            // Check the name
 0377            if (string.IsNullOrEmpty(m_name) ||
 0378                m_name.StartsWith('$') ||
 0379                m_name.StartsWith(' ') ||
 0380                m_name.EndsWith(' ') ||
 0381                m_name.AsSpan().ContainsAny(s_reservedToNameChars))
 0382            {
 0383                throw new CookieException(SR.Format(SR.net_cookie_attribute, "Name", m_name ?? "<null>"));
 384            }
 385
 386            // Check the value
 0387            if (m_value == null || m_value.ContainsAny('\r', '\n', '\0') ||
 0388                (!(m_value.Length > 2 && m_value.StartsWith('\"') && m_value.EndsWith('\"')) && m_value.AsSpan().Contain
 0389            {
 0390                throw new CookieException(SR.Format(SR.net_cookie_attribute, "Value", m_value ?? "<null>"));
 391            }
 392
 393            // Check Comment syntax
 0394            if (Comment != null && !(Comment.Length > 2 && Comment.StartsWith('\"') && Comment.EndsWith('\"'))
 0395                && (Comment.AsSpan().ContainsAny(';', ',')))
 0396            {
 0397                throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.CommentAttributeName, Comment)
 398            }
 399
 400            // Check Path syntax
 0401            if (Path != null && !(Path.Length > 2 && Path.StartsWith('\"') && Path.EndsWith('\"'))
 0402                && (Path.AsSpan().ContainsAny(';', ',')))
 0403            {
 0404                throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.PathAttributeName, Path));
 405            }
 406
 407            // Check/set domain
 408            //
 409            // If domain is implicit => assume a) uri is valid, b) just set domain to uri hostname.
 0410            if (m_domain_implicit)
 0411            {
 0412                SetDomainAndKey(host);
 0413            }
 414            else
 0415            {
 0416                Debug.Assert(m_domainKey is not null);
 417
 0418                if (!IsValidDomainName(m_domainKey) || !HostMatchesDomain(host, m_domainKey))
 0419                {
 0420                    throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.DomainAttributeName, m_dom
 421                }
 0422            }
 423
 424            // Check/Set Path
 0425            if (m_path_implicit)
 0426            {
 427                // This code assumes that the URI path is always valid and contains at least one '/'.
 0428                switch (m_cookieVariant)
 429                {
 430                    case CookieVariant.Plain:
 431                        // As per RFC6265 5.1.4. (https://tools.ietf.org/html/rfc6265#section-5.1.4):
 432                        // | 2. If the uri-path is empty or if the first character of the uri-
 433                        // |    path is not a %x2F ("/") character, output %x2F ("/") and skip
 434                        // |    the remaining steps.
 435                        // | 3. If the uri-path contains no more than one %x2F ("/") character,
 436                        // |    output %x2F ("/") and skip the remaining step.
 437                        // Note: Normally Uri.AbsolutePath contains at least one "/" after parsing,
 438                        //       but it's possible construct Uri with an empty path using a custom UriParser
 439                        int lastSlash;
 0440                        if (!path.StartsWith('/') || (lastSlash = path.LastIndexOf('/')) == 0)
 0441                        {
 0442                            m_path = "/";
 0443                            break;
 444                        }
 445
 446                        // | 4. Output the characters of the uri-path from the first character up
 447                        // |    to, but not including, the right-most %x2F ("/").
 0448                        m_path = path.Substring(0, lastSlash);
 0449                        break;
 450                    case CookieVariant.Rfc2109:
 0451                        m_path = path.Substring(0, path.LastIndexOf('/')); // May be empty
 0452                        break;
 453
 454                    case CookieVariant.Rfc2965:
 455                    default:
 456                        // NOTE: this code is not resilient against future versions with different 'Path' semantics.
 0457                        m_path = path.Substring(0, path.LastIndexOf('/') + 1);
 0458                        break;
 459                }
 0460            }
 461
 462            // Set the default port if Port attribute was present but had no value.
 0463            if (!m_port_implicit && m_port.Length == 0)
 0464            {
 0465                m_port_list = new int[1] { port };
 0466            }
 467
 0468            if (!m_port_implicit)
 0469            {
 470                // Port must match against the one from the uri.
 0471                bool valid = false;
 0472                foreach (int p in m_port_list!)
 0473                {
 0474                    if (p == port)
 0475                    {
 0476                        valid = true;
 0477                        break;
 478                    }
 0479                }
 0480                if (!valid)
 0481                {
 0482                    throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.PortAttributeName, m_port)
 483                }
 0484            }
 0485        }
 486
 487        // Very primitive test to make sure that the name does not have illegal characters
 488        // as per RFC 952 (relaxed on first char could be a digit and string can have '_').
 489        private static bool IsValidDomainName(ReadOnlySpan<char> name) =>
 0490            !name.IsEmpty &&
 0491            !name.ContainsAnyExcept(s_domainChars);
 492
 493        [AllowNull]
 494        public string Port
 495        {
 496            get
 0497            {
 0498                return m_port;
 0499            }
 500            set
 0501            {
 0502                if (string.IsNullOrEmpty(value))
 0503                {
 504                    // "Port" is present but has no value.
 505                    // Therefore; the effective port value is implicit.
 0506                    m_port_implicit = true;
 0507                    m_port = string.Empty;
 0508                }
 509                else
 0510                {
 511                    // "Port" value is present, so we use the provided value rather than an implicit one.
 0512                    m_port_implicit = false;
 513                    // Parse port list
 0514                    if (!value.StartsWith('\"') || !value.EndsWith('\"'))
 0515                    {
 0516                        throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.PortAttributeName, val
 517                    }
 0518                    string[] ports = value.Split(PortSplitDelimiters, StringSplitOptions.RemoveEmptyEntries);
 0519                    int[] parsedPorts = new int[ports.Length];
 520
 0521                    for (int i = 0; i < ports.Length; ++i)
 0522                    {
 0523                        if (!int.TryParse(ports[i], out int port))
 0524                        {
 0525                            throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.PortAttributeName,
 526                        }
 527
 528                        // valid values for port 0 - 0xFFFF
 0529                        if ((port < 0) || (port > 0xFFFF))
 0530                        {
 0531                            throw new CookieException(SR.Format(SR.net_cookie_attribute, CookieFields.PortAttributeName,
 532                        }
 533
 0534                        parsedPorts[i] = port;
 0535                    }
 0536                    m_port_list = parsedPorts;
 0537                    m_port = value;
 0538                    m_version = MaxSupportedVersion;
 0539                    m_cookieVariant = CookieVariant.Rfc2965;
 0540                }
 0541            }
 542        }
 543
 544
 545        internal int[]? PortList
 546        {
 547            get
 0548            {
 549                // PortList will be null if Port Attribute was omitted in the response.
 0550                return m_port_list;
 0551            }
 552        }
 553
 554        public bool Secure
 555        {
 556            get
 0557            {
 0558                return m_secure;
 0559            }
 560            set
 0561            {
 0562                m_secure = value;
 0563            }
 564        }
 565
 566        public DateTime TimeStamp
 567        {
 568            get
 0569            {
 0570                return m_timeStamp;
 0571            }
 572        }
 573
 574        [AllowNull]
 575        public string Value
 576        {
 577            get
 0578            {
 0579                return m_value;
 0580            }
 581            set
 0582            {
 0583                m_value = value ?? string.Empty;
 0584            }
 585        }
 586
 587        internal CookieVariant Variant
 588        {
 589            get
 0590            {
 0591                return m_cookieVariant;
 0592            }
 593        }
 594
 595        // _domainKey member is set internally in VerifySetDefaults().
 596        // If it is not set then verification function was not called;
 597        // this should never happen.
 598        internal string DomainKey
 599        {
 600            get
 0601            {
 0602                return m_domain_implicit ? Domain : m_domainKey!;
 0603            }
 604        }
 605
 606        public int Version
 607        {
 608            get
 0609            {
 0610                return m_version;
 0611            }
 612            set
 0613            {
 0614                ArgumentOutOfRangeException.ThrowIfNegative(value);
 0615                m_version = value;
 0616                if (value > 0 && m_cookieVariant < CookieVariant.Rfc2109)
 0617                {
 0618                    m_cookieVariant = CookieVariant.Rfc2109;
 0619                }
 0620            }
 621        }
 622
 623        public override bool Equals([NotNullWhen(true)] object? comparand)
 0624        {
 0625            return comparand is Cookie other
 0626                    && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase)
 0627                    && string.Equals(Value, other.Value, StringComparison.Ordinal)
 0628                    && string.Equals(Path, other.Path, StringComparison.Ordinal)
 0629                    && CookieComparer.EqualDomains(Domain, other.Domain)
 0630                    && (Version == other.Version);
 0631        }
 632
 633        public override int GetHashCode()
 0634        {
 0635            return HashCode.Combine(
 0636                StringComparer.OrdinalIgnoreCase.GetHashCode(Name),
 0637                StringComparer.Ordinal.GetHashCode(Value),
 0638                StringComparer.Ordinal.GetHashCode(Path),
 0639                StringComparer.OrdinalIgnoreCase.GetHashCode(DomainKey),
 0640                Version);
 0641        }
 642
 643        public override string ToString()
 0644        {
 0645            StringBuilder sb = StringBuilderCache.Acquire();
 0646            ToString(sb);
 0647            return StringBuilderCache.GetStringAndRelease(sb);
 0648        }
 649
 650        internal void ToString(StringBuilder sb)
 0651        {
 0652            int beforeLength = sb.Length;
 653
 654            // Add the Cookie version if necessary.
 0655            if (Version != 0)
 0656            {
 0657                sb.Append(SpecialAttributeLiteral + CookieFields.VersionAttributeName + EqualsLiteral); // const strings
 0658                if (IsQuotedVersion) sb.Append('"');
 0659                sb.Append(NumberFormatInfo.InvariantInfo, $"{m_version}");
 0660                if (IsQuotedVersion) sb.Append('"');
 0661                sb.Append(SeparatorLiteral);
 0662            }
 663
 664            // Add the Cookie Name=Value pair.
 0665            sb.Append(Name).Append(EqualsLiteral).Append(Value);
 666
 0667            if (!Plain)
 0668            {
 669                // Add the Path if necessary.
 0670                if (!m_path_implicit && m_path.Length > 0)
 0671                {
 0672                    sb.Append(SeparatorLiteral + SpecialAttributeLiteral + CookieFields.PathAttributeName + EqualsLitera
 0673                    sb.Append(m_path);
 0674                }
 675
 676                // Add the Domain if necessary.
 0677                if (!m_domain_implicit && m_domain.Length > 0)
 0678                {
 0679                    sb.Append(SeparatorLiteral + SpecialAttributeLiteral + CookieFields.DomainAttributeName + EqualsLite
 0680                    if (IsQuotedDomain) sb.Append('"');
 0681                    sb.Append(m_domain);
 0682                    if (IsQuotedDomain) sb.Append('"');
 0683                }
 0684            }
 685
 686            // Add the Port if necessary.
 0687            if (!m_port_implicit)
 0688            {
 0689                sb.Append(SeparatorLiteral + SpecialAttributeLiteral + CookieFields.PortAttributeName); // const strings
 0690                if (m_port.Length > 0)
 0691                {
 0692                    sb.Append(EqualsLiteral);
 0693                    sb.Append(m_port);
 0694                }
 0695            }
 696
 697            // Check to see whether the only thing we added was "=", and if so,
 698            // remove it so that we leave the StringBuilder unchanged in contents.
 0699            int afterLength = sb.Length;
 0700            if (afterLength == (1 + beforeLength) && sb[beforeLength] == '=')
 0701            {
 0702                sb.Length = beforeLength;
 0703            }
 0704        }
 705
 706        internal string? ToServerString()
 0707        {
 0708            string result = Name + EqualsLiteral + Value;
 0709            if (m_comment != null && m_comment.Length > 0)
 0710            {
 0711                result += SeparatorLiteral + CookieFields.CommentAttributeName + EqualsLiteral + m_comment;
 0712            }
 0713            if (m_commentUri != null)
 0714            {
 0715                result += SeparatorLiteral + CookieFields.CommentUrlAttributeName + EqualsLiteral + QuotesLiteral + m_co
 0716            }
 0717            if (m_discard)
 0718            {
 0719                result += SeparatorLiteral + CookieFields.DiscardAttributeName;
 0720            }
 0721            if (!m_domain_implicit && m_domain != null && m_domain.Length > 0)
 0722            {
 0723                result += SeparatorLiteral + CookieFields.DomainAttributeName + EqualsLiteral + m_domain;
 0724            }
 0725            if (Expires != DateTime.MinValue)
 0726            {
 0727                int seconds = (int)(Expires.ToUniversalTime() - DateTime.UtcNow).TotalSeconds;
 0728                if (seconds < 0)
 0729                {
 730                    // This means that the cookie has already expired. Set Max-Age to 0
 731                    // so that the client will discard the cookie immediately.
 0732                    seconds = 0;
 0733                }
 0734                result += SeparatorLiteral + CookieFields.MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberF
 0735            }
 0736            if (!m_path_implicit && m_path != null && m_path.Length > 0)
 0737            {
 0738                result += SeparatorLiteral + CookieFields.PathAttributeName + EqualsLiteral + m_path;
 0739            }
 0740            if (!Plain && !m_port_implicit && m_port != null && m_port.Length > 0)
 0741            {
 742                // QuotesLiteral are included in _port.
 0743                result += SeparatorLiteral + CookieFields.PortAttributeName + EqualsLiteral + m_port;
 0744            }
 0745            if (m_version > 0)
 0746            {
 0747                result += SeparatorLiteral + CookieFields.VersionAttributeName + EqualsLiteral + m_version.ToString(Numb
 0748            }
 0749            return result == "=" ? null : result;
 0750        }
 751    }
 752}
 753