cookie.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / cookie.cs / 1305376 / cookie.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Net { 
    using System.Collections; 
    using System.Collections.Generic;
    using System.Globalization; 
    using System.Threading;

    internal enum CookieVariant {
        Unknown, 
        Plain,
        Rfc2109, 
        Rfc2965, 
        Default = Rfc2109
    } 

    //
    // Cookie class
    // 
    //  Adheres to RFC 2965
    // 
    //  Currently, only client-side cookies. The cookie classes know how to 
    //  parse a set-cookie format string, but not a cookie format string
    //  (e.g. "Cookie: $Version=1; name=value; $Path=/foo; $Secure") 
    //

    /// 
    ///    [To be supplied.] 
    /// 
    [Serializable] 
    public sealed class Cookie { 

        internal const int    MaxSupportedVersion = 1; 
        internal const string CommentAttributeName = "Comment";
        internal const string CommentUrlAttributeName = "CommentURL";
        internal const string DiscardAttributeName = "Discard";
        internal const string DomainAttributeName = "Domain"; 
        internal const string ExpiresAttributeName = "Expires";
        internal const string MaxAgeAttributeName = "Max-Age"; 
        internal const string PathAttributeName = "Path"; 
        internal const string PortAttributeName = "Port";
        internal const string SecureAttributeName = "Secure"; 
        internal const string VersionAttributeName = "Version";
        internal const string HttpOnlyAttributeName = "HttpOnly";

        internal const string SeparatorLiteral = "; "; 
        internal const string EqualsLiteral = "=";
        internal const string QuotesLiteral = "\""; 
        internal const string SpecialAttributeLiteral = "$"; 

        internal static readonly char[] PortSplitDelimiters =  new char[] {' ', ',', '\"'}; 
        internal static readonly char[] Reserved2Name =  new char[] {' ', '\t', '\r', '\n', '=', ';', ',' };
        internal static readonly char[] Reserved2Value =  new char[] {';', ',' };
        private  static Comparer staticComparer = new Comparer();
 
    // fields
 
        string  m_comment = string.Empty; 
        Uri     m_commentUri = null;
        CookieVariant m_cookieVariant = CookieVariant.Plain; 
        bool    m_discard = false;
        string  m_domain = string.Empty;
        bool    m_domain_implicit = true;
        DateTime m_expires = DateTime.MinValue; 
        string  m_name = string.Empty;
        string  m_path = string.Empty; 
        bool    m_path_implicit = true; 
        string  m_port = string.Empty;
        bool    m_port_implicit = true; 
        int[]   m_port_list = null;
        bool    m_secure = false;
        [System.Runtime.Serialization.OptionalField]
        bool    m_httpOnly=false; 
        DateTime m_timeStamp = DateTime.Now;
        string  m_value = string.Empty; 
        int     m_version = 0; 

        string  m_domainKey = string.Empty; 
        internal bool IsQuotedVersion = false;
        internal bool IsQuotedDomain = false;

 
    // constructors
 
        ///  
        ///    [To be supplied.]
        ///  
        public Cookie() {
        }

 
        //public Cookie(string cookie) {
        //    if ((cookie == null) || (cookie == String.Empty)) { 
        //        throw new ArgumentException("cookie"); 
        //    }
        //    Parse(cookie.Trim()); 
        //    Validate();
        //}

        ///  
        ///    [To be supplied.]
        ///  
        public Cookie(string name, string value) { 
            Name = name;
            m_value = value; 
        }

        /// 
        ///    [To be supplied.] 
        /// 
        public Cookie(string name, string value, string path) 
            : this(name, value) { 
            Path = path;
        } 

        /// 
        ///    [To be supplied.]
        ///  
        public Cookie(string name, string value, string path, string domain)
            : this(name, value, path) { 
            Domain = domain; 
        }
 
    // properties

        /// 
        ///    [To be supplied.] 
        /// 
        public string Comment { 
            get { 
                return m_comment;
            } 
            set {
                if (value == null) {
                    value = string.Empty;
                } 
                m_comment = value;
            } 
        } 

        ///  
        ///    [To be supplied.]
        /// 
        public Uri CommentUri {
            get { 
                return m_commentUri;
            } 
            set { 
                m_commentUri = value;
            } 
        }


        public bool HttpOnly{ 
            get{
                return m_httpOnly; 
            } 
            set{
                m_httpOnly = value; 
            }
        }

 
        /// 
        ///    [To be supplied.] 
        ///  
        public bool Discard {
            get { 
                return m_discard;
            }
            set {
                m_discard = value; 
            }
        } 
 
        /// 
        ///    [To be supplied.] 
        /// 
        public string Domain {
            get {
                return m_domain; 
            }
            set { 
                m_domain = (value == null? String.Empty : value); 
                m_domain_implicit = false;
                m_domainKey = string.Empty; //this will get it value when adding into the Container. 
            }
        }

        private string _Domain { 
            get {
                return (Plain || m_domain_implicit || (m_domain.Length == 0)) 
                    ? string.Empty 
                    : (SpecialAttributeLiteral
                       + DomainAttributeName 
                       + EqualsLiteral + (IsQuotedDomain? "\"": string.Empty)
                       + m_domain + (IsQuotedDomain? "\"": string.Empty)
                       );
            } 
        }
 
        internal bool DomainImplicit { 
            get {
                return m_domain_implicit; 
            }
            set {
                m_domain_implicit = value;
            } 
        }
 
        ///  
        ///    [To be supplied.]
        ///  
        public bool Expired {
            get {
                return (m_expires != DateTime.MinValue) && (m_expires.ToLocalTime() <= DateTime.Now);
            } 
            set {
                if (value == true) { 
                    m_expires = DateTime.Now; 
                }
            } 
        }

        /// 
        ///    [To be supplied.] 
        /// 
        public DateTime Expires { 
            get { 
                return m_expires;
            } 
            set {
                m_expires = value;
            }
        } 

        ///  
        ///    [To be supplied.] 
        /// 
        public string Name { 
            get {
                return m_name;
            }
            set { 
                if (ValidationHelper.IsBlankString(value) || !InternalSetName(value)) {
                    throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", value == null? "": value)); 
                } 
            }
        } 

        internal bool InternalSetName(string value) {
            if (ValidationHelper.IsBlankString(value) || value[0] == '$' || value.IndexOfAny(Reserved2Name) != -1) {
                m_name = string.Empty; 
                return false;
            } 
            m_name = value; 
            return true;
        } 

        /// 
        ///    [To be supplied.]
        ///  
        public string Path {
            get { 
                return m_path; 
            }
            set { 
                m_path = (value == null? String.Empty : value);
                m_path_implicit = false;
            }
        } 

        private string _Path { 
            get { 
                return (Plain || m_path_implicit || (m_path.Length == 0))
                    ? string.Empty 
                    : (SpecialAttributeLiteral
                       + PathAttributeName
                       + EqualsLiteral
                       + m_path 
                       );
            } 
        } 

        internal bool Plain { 
            get {
                return Variant == CookieVariant.Plain;
            }
        } 

        internal Cookie Clone() { 
            Cookie clonedCookie = new Cookie(m_name, m_value); 

            // 
            // Copy over all the properties from the original cookie
            //
            if (!m_port_implicit) {
                clonedCookie.Port = m_port; 
            }
            if (!m_path_implicit) { 
                clonedCookie.Path = m_path; 
            }
            clonedCookie.Domain = m_domain; 
            //
            // If the domain in the original cookie was implicit, we should preserve that property
            clonedCookie.DomainImplicit = m_domain_implicit;
            clonedCookie.m_timeStamp = m_timeStamp; 
            clonedCookie.Comment = m_comment;
            clonedCookie.CommentUri = m_commentUri; 
            clonedCookie.HttpOnly = m_httpOnly; 
            clonedCookie.Discard = m_discard;
            clonedCookie.Expires = m_expires; 
            clonedCookie.Version = m_version;
            clonedCookie.Secure = m_secure;

            // 
            // The variant is set when we set properties like port/version. So,
            // we should copy over the variant from the original cookie after 
            // we set all other properties 
            clonedCookie.m_cookieVariant = m_cookieVariant;
 
            return clonedCookie;
        }

        private static bool IsDomainEqualToHost(string domain, string host) { 
            //
            // +1 in the host length is to account for the leading dot in domain 
            if ((host.Length + 1 == domain.Length) && 
                (string.Compare(host, 0, domain, 1, host.Length, StringComparison.OrdinalIgnoreCase) == 0)) {
                return true; 
            }
            else {
                return false;
            } 
        }
 
        // 
        // According to spec we must assume default values for attributes but still
        // keep in mind that we must not include them into the requests. 
        // We also check the validiy of all attributes based on the version and variant (read RFC)
        //
        // To work properly this function must be called after cookie construction with
        // default (response) URI AND set_default == true 
        //
        // Afterwards, the function can be called many times with other URIs and 
        // set_default == false to check whether this cookie matches given uri 
        //
 
        internal bool VerifySetDefaults(CookieVariant variant, Uri uri, bool isLocalDomain, string localDomain, bool set_default, bool isThrow) {

            string host = uri.Host;
            int    port = uri.Port; 
            string path = uri.AbsolutePath;
            bool   valid= true; 
 
            if (set_default) {
                // Set Variant. If version is zero => reset cookie to Version0 style 
                if (Version == 0) {
                    variant = CookieVariant.Plain;
                }
                else if (Version == 1 && variant == CookieVariant.Unknown) { 
                     //since we don't expose Variant to an app, set it to Default
                     variant = CookieVariant.Default; 
                } 
                m_cookieVariant = variant;
            } 

            //Check the name
            if (m_name == null || m_name.Length == 0 || m_name[0] == '$' || m_name.IndexOfAny(Reserved2Name) != -1) {
                if (isThrow) { 
                    throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Name", m_name == null? "": m_name));
                } 
                return false; 
            }
 
            //Check the value
            if (m_value == null ||
                (!(m_value.Length > 2 && m_value[0] == '\"' && m_value[m_value.Length-1] == '\"') && m_value.IndexOfAny(Reserved2Value) != -1)) {
                if (isThrow) { 
                    throw new CookieException(SR.GetString(SR.net_cookie_attribute, "Value", m_value == null? "": m_value));
                } 
                return false; 
            }
 
            //Check Comment syntax
            if (Comment != null && !(Comment.Length > 2 && Comment[0] == '\"' && Comment[Comment.Length-1] == '\"')
                && (Comment.IndexOfAny(Reserved2Value) != -1)) {
                if (isThrow) 
                   throw new CookieException(SR.GetString(SR.net_cookie_attribute, CommentAttributeName, Comment));
                return false; 
            } 

            //Check Path syntax 
            if (Path != null  && !(Path.Length > 2 && Path[0] == '\"' && Path[Path.Length-1] == '\"')
                && (Path.IndexOfAny(Reserved2Value) != -1)) {
                if (isThrow) {
                    throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, Path)); 
                }
                return false; 
            } 

            //Check/set domain 
            // if domain is implicit => assume a) uri is valid, b) just set domain to uri hostname
            if (set_default && m_domain_implicit == true) {
                m_domain = host;
            } 
            else {
                if (!m_domain_implicit) { 
                    // Forwarding note: If Uri.Host is of IP address form then the only supported case 
                    // is for IMPLICIT domain property of a cookie.
                    // The below code (explicit cookie.Domain value) will try to parse Uri.Host IP string 
                    // as a fqdn and reject the cookie

                    //aliasing since we might need the KeyValue (but not the original one)
                    string domain = m_domain; 

                    //Syntax check for Domain charset plus empty string 
                    if (!DomainCharsTest(domain)) { 
                        if (isThrow) {
                            throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, domain == null? "": domain)); 
                        }
                        return false;
                    }
 
                    //domain must start with '.' if set explicitly
                    if(domain[0] != '.' ) { 
                        if (!(variant == CookieVariant.Rfc2965 || variant == CookieVariant.Plain)) { 
                            if (isThrow) {
                                throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain)); 
                            }
                            return false;
                        }
                        domain = '.' + domain; 
                    }
 
                    int  host_dot = host.IndexOf('.'); 

                    // First quick check is for pushing a cookie into the local domain 
                    if (isLocalDomain && (string.Compare(localDomain, domain, StringComparison.OrdinalIgnoreCase ) == 0)) {
                        valid = true;
                    }
                    else if (domain.IndexOf('.', 1, domain.Length-2) == -1) { 

                        // A single label domain is valid only if the domain is exactly the same as the host specified in the URI 
                        if (!IsDomainEqualToHost(domain, host)) { 
                            valid = false;
                        } 
                    }
                    else if (variant == CookieVariant.Plain) {
                        // We distiguish between Version0 cookie and other versions on domain issue
                        // According to Version0 spec a domain must be just a substring of the hostname 

                        if (!IsDomainEqualToHost(domain, host)) { 
                            if (host.Length <= domain.Length || 
                                string.Compare(host, host.Length-domain.Length, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) {
                                valid = false; 
                            }
                        }
                    }
                    else if (host_dot == -1 || 
                             domain.Length != host.Length-host_dot ||
                             string.Compare(host, host_dot, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) { 
                            //starting the first dot, the host must match the domain 

                            // for null hosts, the host must match the domain exactly 
                            if (!IsDomainEqualToHost(domain, host)) {
                                valid = false;
                            }
                    } 

                    if (valid) { 
                            m_domainKey = domain.ToLower(CultureInfo.InvariantCulture); 
                    }
                } 
                else {
                    // for implicitly set domain AND at the set_default==false time
                    // we simply got to match uri.Host against m_domain
                    if (string.Compare(host, m_domain, StringComparison.OrdinalIgnoreCase) != 0) { 
                        valid = false;
                    } 
 
                }
                if(!valid) { 
                    if (isThrow) {
                        throw new CookieException(SR.GetString(SR.net_cookie_attribute, DomainAttributeName, m_domain));
                    }
                    return false; 
                }
            } 
 

            //Check/Set Path 

            if (set_default && m_path_implicit == true) {
                //assuming that uri path is always valid and contains at least one '/'
                switch (m_cookieVariant) { 
                case CookieVariant.Plain:
                                        m_path = path; 
                                        break; 
                case CookieVariant.Rfc2109:
                                        m_path = path.Substring(0, path.LastIndexOf('/')); //may be empty 
                                        break;

                case CookieVariant.Rfc2965:
                default:                //hope future versions will have same 'Path' semantic? 
                                        m_path = path.Substring(0, path.LastIndexOf('/')+1);
                                        break; 
 
                }
            } 
            else {
                //check current path (implicit/explicit) against given uri
                if (!path.StartsWith(CookieParser.CheckQuoted(m_path))) {
                    if (isThrow) { 
                        throw new CookieException(SR.GetString(SR.net_cookie_attribute, PathAttributeName, m_path));
                    } 
                    return false; 
                }
            } 

            // set the default port if Port attribute was present but had no value
            if (set_default && (m_port_implicit == false && m_port.Length == 0)) {
                m_port_list = new int[1] {port}; 
            }
 
            if(m_port_implicit == false) { 
                // Port must match agaist the one from the uri
                valid = false; 
                foreach (int p in m_port_list) {
                    if (p == port) {
                        valid = true;
                        break; 
                    }
                } 
                if (!valid) { 
                    if (isThrow) {
                        throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port)); 
                    }
                    return false;
                }
            } 
            return true;
        } 
 
        //Very primitive test to make sure that the name does not have illegal characters
        // As per RFC 952 (relaxed on first char could be a digit and string can have '_') 
        private static bool DomainCharsTest(string name) {
            if (name == null || name.Length == 0) {
                return false;
            } 
            for(int i=0; i < name.Length; ++i) {
                char ch = name[i]; 
                if (!( 
                      (ch >= '0' && ch <= '9') ||
                      (ch == '.' || ch == '-') || 
                      (ch >= 'a' && ch <= 'z') ||
                      (ch >= 'A' && ch <= 'Z') ||
                      (ch == '_')
                    )) 
                    return false;
            } 
            return true; 
        }
 
        /// 
        ///    [To be supplied.]
        /// 
        public string Port { 
            get {
                return m_port; 
            } 
            set {
                m_port_implicit = false; 
                if ((value == null || value.Length == 0)) {
                    //"Port" is present but has no value.
                    m_port = string.Empty;
                } 
                else {
                    // Parse port list 
                    if (value[0] != '\"' || value[value.Length-1] != '\"') { 
                        throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, value));
                    } 
                    string[] ports = value.Split(PortSplitDelimiters);

                    List portList = new List();
                    int port; 

                    for (int i = 0; i < ports.Length; ++i) { 
                        // Skip spaces 
                        if (ports[i] != string.Empty) {
                            if (!Int32.TryParse(ports[i], out port)) 
                                throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, value));
                            // valid values for port 0 - 0xFFFF
                            if ((port < 0) || (port > 0xFFFF))
                                throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, value)); 
                            portList.Add(port);
                        } 
                    } 
                    m_port_list = portList.ToArray();
                    m_port = value; 
                    m_version = MaxSupportedVersion;
                    m_cookieVariant = CookieVariant.Rfc2965;
                }
            } 
        }
 
 
        internal int[] PortList {
            get { 
                //It must be null if Port Attribute was ommitted in the response
                return m_port_list;
            }
        } 

        private string _Port { 
            get { 
                return m_port_implicit ? string.Empty :
                      (SpecialAttributeLiteral 
                       + PortAttributeName
                       + ((m_port.Length == 0) ? string.Empty : (EqualsLiteral + m_port))
                       );
            } 
        }
 
        ///  
        ///    [To be supplied.]
        ///  
        public bool Secure {
            get {
                return m_secure;
            } 
            set {
                m_secure = value; 
            } 
        }
 
        /// 
        ///    [To be supplied.]
        /// 
        public DateTime TimeStamp { 
            get {
                return m_timeStamp; 
            } 
        }
 
        /// 
        ///    [To be supplied.]
        /// 
        public string Value { 
            get {
                return m_value; 
            } 
            set {
                m_value = (value == null? String.Empty : value); 
            }
        }

        internal CookieVariant Variant { 
            get {
                return m_cookieVariant; 
            } 
            set {
                // only set by HttpListenerRequest::Cookies_get() 
                GlobalLog.Assert(value == CookieVariant.Rfc2965, "Cookie#{0}::set_Variant()|value:{1}" , ValidationHelper.HashString(this), value);
                m_cookieVariant = value;
            }
        } 

        // m_domainKey member is set internally in VerifySetDefaults() 
        // If it is not set then verification function was not called 
        // and this should never happen
        internal string DomainKey { 
            get {
                return m_domain_implicit ? Domain : m_domainKey;
            }
        } 

 
        //public Version Version { 
        /// 
        ///    [To be supplied.] 
        /// 
        public int Version {
            get {
                return m_version; 
            }
            set { 
                if (value<0) { 
                    throw new ArgumentOutOfRangeException("value");
                } 
                m_version = value;
                if (value>0 && m_cookieVariant 
        ///    [To be supplied.]
        /// 
        public override bool Equals(object comparand) {
            if (!(comparand is Cookie)) { 
                return false;
            } 
 
            Cookie other = (Cookie)comparand;
 
            return (string.Compare(Name, other.Name, StringComparison.OrdinalIgnoreCase) == 0)
                    && (string.Compare(Value, other.Value, StringComparison.Ordinal) == 0)
                    && (string.Compare(Path, other.Path, StringComparison.Ordinal) == 0)
                    && (string.Compare(Domain, other.Domain, StringComparison.OrdinalIgnoreCase) == 0) 
                    && (Version == other.Version)
                    ; 
        } 

        ///  
        ///    [To be supplied.]
        /// 
        public override int GetHashCode() {
            // 
            //string hashString = Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version;
            //int hash = 0; 
            // 
            //foreach (char ch in hashString) {
            //    hash = unchecked(hash << 1 ^ (int)ch); 
            //}
            //return hash;
            return (Name + "=" + Value + ";" + Path + "; " + Domain + "; " + Version).GetHashCode();
        } 

        ///  
        ///    [To be supplied.] 
        /// 
        public override string ToString() { 

            string domain = _Domain;
            string path = _Path;
            string port = _Port; 
            string version = _Version;
 
            string result = 
                    ((version.Length == 0)? string.Empty : (version + SeparatorLiteral))
                    + Name + EqualsLiteral + Value 
                    + ((path.Length == 0)   ? string.Empty : (SeparatorLiteral + path))
                    + ((domain.Length == 0) ? string.Empty : (SeparatorLiteral + domain))
                    + ((port.Length == 0)   ? string.Empty : (SeparatorLiteral + port))
                    ; 
            if (result == "=") {
                return string.Empty; 
            } 
            return result;
        } 

        internal string ToServerString() {
            string result = Name + EqualsLiteral + Value;
            if (m_comment!=null && m_comment.Length>0) { 
                result += SeparatorLiteral + CommentAttributeName + EqualsLiteral + m_comment;
            } 
            if (m_commentUri!=null) { 
                result += SeparatorLiteral + CommentUrlAttributeName + EqualsLiteral + QuotesLiteral + m_commentUri.ToString() + QuotesLiteral;
            } 
            if (m_discard) {
                result += SeparatorLiteral + DiscardAttributeName;
            }
            if (!m_domain_implicit && m_domain!=null && m_domain.Length>0) { 
                result += SeparatorLiteral + DomainAttributeName + EqualsLiteral + m_domain;
            } 
            if (Expires != DateTime.MinValue) { 
                int seconds = (int) (Expires.ToLocalTime() - DateTime.Now).TotalSeconds;
                if (seconds < 0) { 
                    // This means that the cookie has already expired. Set Max-Age to 0
                    // so that the client will discard the cookie immediately
                    seconds = 0;
                } 
                result += SeparatorLiteral + MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberFormatInfo.InvariantInfo);
            } 
            if (!m_path_implicit && m_path!=null && m_path.Length>0) { 
                result += SeparatorLiteral + PathAttributeName + EqualsLiteral + m_path;
            } 
            if (!Plain && !m_port_implicit && m_port!=null && m_port.Length>0) {
                // QuotesLiteral are included in m_port
                result += SeparatorLiteral + PortAttributeName + EqualsLiteral + m_port;
            } 
            if (m_version>0) {
                result += SeparatorLiteral + VersionAttributeName + EqualsLiteral + m_version.ToString(NumberFormatInfo.InvariantInfo); 
            } 
            return result==EqualsLiteral ? null : result;
        } 

        //private void Validate() {
        //    if ((m_name == String.Empty) && (m_value == String.Empty)) {
        //        throw new CookieException(); 
        //    }
        //    if ((m_name != String.Empty) && (m_name[0] == '$')) { 
        //        throw new CookieException(); 
        //    }
        //} 

#if DEBUG
        /// 
        ///    [To be supplied.] 
        /// 
        internal void Dump() { 
            GlobalLog.Print("Cookie: " + ToString() + "->\n" 
                            + "\tComment    = " + Comment + "\n"
                            + "\tCommentUri = " + CommentUri + "\n" 
                            + "\tDiscard    = " + Discard + "\n"
                            + "\tDomain     = " + Domain + "\n"
                            + "\tExpired    = " + Expired + "\n"
                            + "\tExpires    = " + Expires + "\n" 
                            + "\tName       = " + Name + "\n"
                            + "\tPath       = " + Path + "\n" 
                            + "\tPort       = " + Port + "\n" 
                            + "\tSecure     = " + Secure + "\n"
                            + "\tTimeStamp  = " + TimeStamp + "\n" 
                            + "\tValue      = " + Value + "\n"
                            + "\tVariant    = " + Variant + "\n"
                            + "\tVersion    = " + Version + "\n"
                            + "\tHttpOnly    = " + HttpOnly + "\n" 
                            );
        } 
#endif 
    }
 
    internal enum CookieToken {

    // state types
 
        Nothing,
        NameValuePair,  // X=Y 
        Attribute,      // X 
        EndToken,       // ';'
        EndCookie,      // ',' 
        End,            // EOLN
        Equals,

    // value types 

        Comment, 
        CommentUrl, 
        CookieName,
        Discard, 
        Domain,
        Expires,
        MaxAge,
        Path, 
        Port,
        Secure, 
        HttpOnly, 
        Unknown,
        Version 
    }

    //
    // CookieTokenizer 
    //
    //  Used to split a single or multi-cookie (header) string into individual 
    //  tokens 
    //
 
    internal class CookieTokenizer {

    // fields
 
        bool m_eofCookie;
        int m_index; 
        int m_length; 
        string m_name;
        bool m_quoted; 
        int m_start;
        CookieToken m_token;
        int m_tokenLength;
        string m_tokenStream; 
        string m_value;
 
    // constructors 

        internal CookieTokenizer(string tokenStream) { 
            m_length = tokenStream.Length;
            m_tokenStream = tokenStream;
        }
 
    // properties
 
        internal bool EndOfCookie { 
            get {
                return m_eofCookie; 
            }
            set {
                m_eofCookie = value;
            } 
        }
 
        internal bool Eof { 
            get {
                return m_index >= m_length; 
            }
        }

        internal string Name { 
            get {
                return m_name; 
            } 
            set {
                m_name = value; 
            }
        }

        internal bool Quoted { 
            get {
                return m_quoted; 
            } 
            set {
                m_quoted = value; 
            }
        }

        internal CookieToken Token { 
            get {
                return m_token; 
            } 
            set {
                m_token = value; 
            }
        }

        internal string Value { 
            get {
                return m_value; 
            } 
            set {
                m_value = value; 
            }
        }

    // methods 

        // 
        // Extract 
        //
        //  extract the current token 
        //

        internal string Extract() {
 
            string tokenString = string.Empty;
 
            if (m_tokenLength != 0) { 
                tokenString = m_tokenStream.Substring(m_start, m_tokenLength);
                if (!Quoted) { 
                    tokenString = tokenString.Trim();
                }
            }
            return tokenString; 
        }
 
        // 
        // FindNext
        // 
        //  Find the start and length of the next token. The token is terminated
        //  by one of:
        //
        //      - end-of-line 
        //      - end-of-cookie: unquoted comma separates multiple cookies
        //      - end-of-token: unquoted semi-colon 
        //      - end-of-name: unquoted equals 
        //
        // Inputs: 
        //    ignoreComma
        //      true if parsing doesn't stop at a comma. This is only true when
        //      we know we're parsing an original cookie that has an expires=
        //      attribute, because the format of the time/date used in expires 
        //      is:
        //          Wdy, dd-mmm-yyyy HH:MM:SS GMT 
        // 
        //    ignoreEquals
        //      true if parsing doesn't stop at an equals sign. The LHS of the 
        //      first equals sign is an attribute name. The next token may
        //      include one or more equals signs. E.g.,
        //
        //          SESSIONID=ID=MSNx45&q=33 
        //
        // Outputs: 
        //      m_index 
        //      incremented to the last position in m_tokenStream contained by
        //      the current token 
        //
        //      m_start
        //      incremented to the start of the current token
        // 
        //      m_tokenLength
        //      set to the length of the current token 
        // 
        // Assumes:
        //  Nothing 
        //
        // Returns:
        //  type of CookieToken found:
        // 
        //      End         - end of the cookie string
        //      EndCookie   - end of current cookie in (potentially) a 
        //                    multi-cookie string 
        //      EndToken    - end of name=value pair, or end of an attribute
        //      Equals      - end of name= 
        //
        // Throws:
        //  Nothing
        // 

        internal CookieToken FindNext(bool ignoreComma, bool ignoreEquals) { 
            m_tokenLength = 0; 
            m_start = m_index;
            while ((m_index < m_length) && Char.IsWhiteSpace(m_tokenStream[m_index])) { 
                ++m_index;
                ++m_start;
            }
 
            CookieToken token = CookieToken.End;
            int increment = 1; 
 
            if (!Eof) {
                if (m_tokenStream[m_index] == '"') { 
                    Quoted = true;
                    ++m_index;
                    bool quoteOn = false;
                    while (m_index < m_length) { 
                        char currChar = m_tokenStream[m_index];
                        if (!quoteOn && currChar == '"') 
                            break; 
                        if (quoteOn)
                            quoteOn = false; 
                        else if (currChar == '\\')
                            quoteOn = true;
                        ++m_index;
                    } 
                    if (m_index < m_length) {
                        ++m_index; 
                    } 
                    m_tokenLength = m_index - m_start;
                    increment = 0; 
                    // if we are here, reset ignoreComma
                    // In effect, we ignore everything after quoted string till next delimiter
                    ignoreComma = false;
                } 
                while ((m_index < m_length)
                       && (m_tokenStream[m_index] != ';') 
                       && (ignoreEquals || (m_tokenStream[m_index] != '=')) 
                       && (ignoreComma || (m_tokenStream[m_index] != ','))) {
 
                    // Fixing 2 things:
                    // 1) ignore day of week in cookie string
                    // 2) revert ignoreComma once meet it, so won't miss the next cookie)
                    if (m_tokenStream[m_index] == ',') { 
                        m_start = m_index+1;
                        m_tokenLength = -1; 
                        ignoreComma = false; 
                    }
                    ++m_index; 
                    m_tokenLength += increment;

                }
                if (!Eof) { 
                    switch (m_tokenStream[m_index]) {
                        case ';': 
                            token = CookieToken.EndToken; 
                            break;
 
                        case '=':
                            token = CookieToken.Equals;
                            break;
 
                        default:
                            token = CookieToken.EndCookie; 
                            break; 
                    }
                    ++m_index; 
                }
            }
            return token;
        } 

        // 
        // Next 
        //
        //  Get the next cookie name/value or attribute 
        //
        //  Cookies come in the following formats:
        //
        //      1. Version0 
        //          Set-Cookie: [][=][]
        //                      [; expires=] 
        //                      [; path=] 
        //                      [; domain=]
        //                      [; secure] 
        //          Cookie: =
        //
        //          Notes:  and/or  may be blank
        //                  is the RFC 822/1123 date format that 
        //                 incorporates commas, e.g.
        //                 "Wednesday, 09-Nov-99 23:12:40 GMT" 
        // 
        //      2. RFC 2109
        //          Set-Cookie: 1#{ 
        //                          =
        //                          [; comment=]
        //                          [; domain=]
        //                          [; max-age=] 
        //                          [; path=]
        //                          [; secure] 
        //                          ; Version= 
        //                      }
        //          Cookie: $Version= 
        //                  1#{
        //                      ; =
        //                      [; path=]
        //                      [; domain=] 
        //                  }
        // 
        //      3. RFC 2965 
        //          Set-Cookie2: 1#{
        //                          = 
        //                          [; comment=]
        //                          [; commentURL=]
        //                          [; discard]
        //                          [; domain=] 
        //                          [; max-age=]
        //                          [; path=] 
        //                          [; ports=] 
        //                          [; secure]
        //                          ; Version= 
        //                       }
        //          Cookie: $Version=
        //                  1#{
        //                      ; = 
        //                      [; path=]
        //                      [; domain=] 
        //                      [; port=""] 
        //                  }
        //          [Cookie2: $Version=] 
        //
        // Inputs:
        //    first
        //      true if this is the first name/attribute that we have looked for 
        //      in the cookie stream
        // 
        // Outputs: 
        //
        // Assumes: 
        //  Nothing
        //
        // Returns:
        //  type of CookieToken found: 
        //
        //      - Attribute 
        //          - token was single-value. May be empty. Caller should check 
        //            Eof or EndCookie to determine if any more action needs to
        //            be taken 
        //
        //      - NameValuePair
        //          - Name and Value are meaningful. Either may be empty
        // 
        // Throws:
        //  Nothing 
        // 

        internal CookieToken Next(bool first, bool parseResponseCookies) { 

            Reset();

            CookieToken terminator = FindNext(false, false); 
            if (terminator == CookieToken.EndCookie) {
                EndOfCookie = true; 
            } 

            if ((terminator == CookieToken.End) || (terminator == CookieToken.EndCookie)) { 
                if ((Name = Extract()).Length != 0) {
                    Token = TokenFromName(parseResponseCookies);
                    return CookieToken.Attribute;
                } 
                return terminator;
            } 
            Name = Extract(); 
            if (first) {
                Token = CookieToken.CookieName; 
            }
            else {
                Token = TokenFromName(parseResponseCookies);
            } 
            if (terminator == CookieToken.Equals) {
                terminator = FindNext(!first && (Token == CookieToken.Expires), true); 
                if (terminator == CookieToken.EndCookie) { 
                    EndOfCookie = true;
                } 
                Value = Extract();
                return CookieToken.NameValuePair;
            }
            else { 
                return CookieToken.Attribute;
            } 
        } 

        // 
        // Reset
        //
        //  set up this tokenizer for finding the next name/value pair or
        //  attribute, or end-of-[token, cookie, or line] 
        //
 
        internal void Reset() { 
            m_eofCookie = false;
            m_name = string.Empty; 
            m_quoted = false;
            m_start = m_index;
            m_token = CookieToken.Nothing;
            m_tokenLength = 0; 
            m_value = string.Empty;
        } 
 
        private struct RecognizedAttribute {
 
            string m_name;
            CookieToken m_token;

            internal RecognizedAttribute(string name, CookieToken token) { 
                m_name = name;
                m_token = token; 
            } 

            internal CookieToken Token { 
                get {
                    return m_token;
                }
            } 

            internal bool IsEqualTo(string value) { 
                return string.Compare(m_name, value, StringComparison.OrdinalIgnoreCase) == 0; 
            }
        } 

        //
        // recognized attributes in order of expected commonality
        // 

        static RecognizedAttribute[] RecognizedAttributes = { 
            new RecognizedAttribute(Cookie.PathAttributeName, CookieToken.Path), 
            new RecognizedAttribute(Cookie.MaxAgeAttributeName, CookieToken.MaxAge),
            new RecognizedAttribute(Cookie.ExpiresAttributeName, CookieToken.Expires), 
            new RecognizedAttribute(Cookie.VersionAttributeName, CookieToken.Version),
            new RecognizedAttribute(Cookie.DomainAttributeName, CookieToken.Domain),
            new RecognizedAttribute(Cookie.SecureAttributeName, CookieToken.Secure),
            new RecognizedAttribute(Cookie.DiscardAttributeName, CookieToken.Discard), 
            new RecognizedAttribute(Cookie.PortAttributeName, CookieToken.Port),
            new RecognizedAttribute(Cookie.CommentAttributeName, CookieToken.Comment), 
            new RecognizedAttribute(Cookie.CommentUrlAttributeName, CookieToken.CommentUrl), 
            new RecognizedAttribute(Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly),
        }; 

        static RecognizedAttribute[] RecognizedServerAttributes = {
            new RecognizedAttribute('$' + Cookie.PathAttributeName, CookieToken.Path),
            new RecognizedAttribute('$' + Cookie.VersionAttributeName, CookieToken.Version), 
            new RecognizedAttribute('$' + Cookie.DomainAttributeName, CookieToken.Domain),
            new RecognizedAttribute('$' + Cookie.PortAttributeName, CookieToken.Port), 
            new RecognizedAttribute('$' + Cookie.HttpOnlyAttributeName, CookieToken.HttpOnly), 
        };
 
        internal CookieToken TokenFromName(bool parseResponseCookies) {
            if (!parseResponseCookies) {
                for (int i = 0; i < RecognizedServerAttributes.Length; ++i) {
                    if (RecognizedServerAttributes[i].IsEqualTo(Name)) { 
                        return RecognizedServerAttributes[i].Token;
                    } 
                } 
            }
            else { 
                for (int i = 0; i < RecognizedAttributes.Length; ++i) {
                    if (RecognizedAttributes[i].IsEqualTo(Name)) {
                        return RecognizedAttributes[i].Token;
                    } 
                }
            } 
            return CookieToken.Unknown; 
        }
    } 

    //
    // CookieParser
    // 
    //  Takes a cookie header, makes cookies
    // 
 
    internal class CookieParser {
 
    // fields

        CookieTokenizer m_tokenizer;
        Cookie m_savedCookie; 

    // constructors 
 
        internal CookieParser(string cookieString) {
            m_tokenizer = new CookieTokenizer(cookieString); 
        }

    // properties
 
    // methods
 
        // 
        // Get
        // 
        //  Gets the next cookie
        //
        // Inputs:
        //  Nothing 
        //
        // Outputs: 
        //  Nothing 
        //
        // Assumes: 
        //  Nothing
        //
        // Returns:
        //  new cookie object, or null if there's no more 
        //
        // Throws: 
        //  Nothing 
        //
 
        internal Cookie Get() {

            Cookie cookie = null;
 
            // only first ocurence of an attribute value must be counted
            bool   commentSet = false; 
            bool   commentUriSet = false; 
            bool   domainSet = false;
            bool   expiresSet = false; 
            bool   pathSet = false;
            bool   portSet = false; //special case as it may have no value in header
            bool   versionSet = false;
            bool   secureSet = false; 
            bool   discardSet = false;
 
            do { 
                CookieToken token = m_tokenizer.Next(cookie==null, true);
                if (cookie==null && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) { 
                    cookie = new Cookie();
                    if (cookie.InternalSetName(m_tokenizer.Name) == false) {
                        //will be rejected
                        cookie.InternalSetName(string.Empty); 
                    }
                    cookie.Value = m_tokenizer.Value; 
                } 
                else {
                    switch (token) { 
                    case CookieToken.NameValuePair:
                        switch (m_tokenizer.Token) {
                            case CookieToken.Comment:
                                if (!commentSet) { 
                                    commentSet = true;
                                    cookie.Comment = m_tokenizer.Value; 
                                } 
                                break;
 
                            case CookieToken.CommentUrl:
                                if (!commentUriSet) {
                                    commentUriSet = true;
                                    Uri parsed; 
                                    if (Uri.TryCreate(CheckQuoted(m_tokenizer.Value), UriKind.Absolute, out parsed)) {
                                        cookie.CommentUri = parsed; 
                                    } 
                                }
                                break; 

                            case CookieToken.Domain:
                                if (!domainSet) {
                                    domainSet = true; 
                                    cookie.Domain = CheckQuoted(m_tokenizer.Value);
                                    cookie.IsQuotedDomain = m_tokenizer.Quoted; 
                                } 
                                break;
 
                            case CookieToken.Expires:
                                if (!expiresSet) {
                                    expiresSet = true;
 
                                    DateTime expires;
                                    if (DateTime.TryParse(CheckQuoted(m_tokenizer.Value), out expires)) { 
                                        cookie.Expires = expires; 
                                    }
                                    else { 
                                        //this cookie will be rejected
                                        cookie.InternalSetName(string.Empty);
                                    }
                                } 
                                break;
 
                            case CookieToken.MaxAge: 
                                if (!expiresSet) {
                                    expiresSet = true; 
                                    int parsed;
                                    if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) {
                                        cookie.Expires = DateTime.Now.AddSeconds((double)parsed);
                                    } 
                                    else {
                                        //this cookie will be rejected 
                                        cookie.InternalSetName(string.Empty); 
                                    }
                                } 
                                break;

                            case CookieToken.Path:
                                if (!pathSet) { 
                                    pathSet = true;
                                    cookie.Path = m_tokenizer.Value; 
                                } 
                                break;
 
                            case CookieToken.Port:
                                if (!portSet) {
                                    portSet = true;
                                    try { 
                                        cookie.Port = m_tokenizer.Value;
                                    } 
                                    catch { 
                                        //this cookie will be rejected
                                        cookie.InternalSetName(string.Empty); 
                                    }
                                }
                                break;
 
                            case CookieToken.Version:
                                if (!versionSet) { 
                                    versionSet = true; 
                                    int parsed;
                                    if (int.TryParse(CheckQuoted(m_tokenizer.Value), out parsed)) { 
                                        cookie.Version = parsed;
                                        cookie.IsQuotedVersion = m_tokenizer.Quoted;
                                    }
                                    else { 
                                        //this cookie will be rejected
                                        cookie.InternalSetName(string.Empty); 
                                    } 
                                }
                                break; 
                        }
                        break;

                    case CookieToken.Attribute: 
                        switch (m_tokenizer.Token) {
                            case CookieToken.Discard: 
                                if (!discardSet) { 
                                    discardSet = true;
                                    cookie.Discard = true; 
                                }
                                break;

                            case CookieToken.Secure: 
                                if (!secureSet) {
                                    secureSet = true; 
                                    cookie.Secure = true; 
                                }
                                break; 

                            case CookieToken.HttpOnly:
                                cookie.HttpOnly = true;
                                break; 

                            case CookieToken.Port: 
                                if (!portSet) { 
                                    portSet = true;
                                    cookie.Port  = string.Empty; 
                                }
                                break;
                        }
                        break; 
                    }
                } 
            } while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie); 
            return cookie;
        } 

        // twin parsing method, different enough that it's better to split it into
        // a different method
        internal Cookie GetServer() { 

            Cookie cookie = m_savedCookie; 
            m_savedCookie = null; 

            // only first ocurence of an attribute value must be counted 
            bool   domainSet = false;
            bool   pathSet = false;
            bool   portSet = false; //special case as it may have no value in header
 
            do {
                bool first = cookie==null || cookie.Name==null || cookie.Name.Length==0; 
                CookieToken token = m_tokenizer.Next(first, false); 

                if (first && (token==CookieToken.NameValuePair || token==CookieToken.Attribute)) { 
                    if (cookie==null) {
                        cookie = new Cookie();
                    }
                    if (cookie.InternalSetName(m_tokenizer.Name) == false) { 
                        //will be rejected
                        cookie.InternalSetName(string.Empty); 
                    } 
                    cookie.Value = m_tokenizer.Value;
                } 
                else {
                    switch (token) {
                    case CookieToken.NameValuePair:
                        switch (m_tokenizer.Token) { 
                            case CookieToken.Domain:
                                if (!domainSet) { 
                                    domainSet = true; 
                                    cookie.Domain = CheckQuoted(m_tokenizer.Value);
                                    cookie.IsQuotedDomain = m_tokenizer.Quoted; 
                                }
                                break;

                            case CookieToken.Path: 
                                if (!pathSet) {
                                    pathSet = true; 
                                    cookie.Path = m_tokenizer.Value; 
                                }
                                break; 

                            case CookieToken.Port:
                                if (!portSet) {
                                    portSet = true; 
                                    try {
                                        cookie.Port = m_tokenizer.Value; 
                                    } 
                                    catch (CookieException) {
                                        //this cookie will be rejected 
                                        cookie.InternalSetName(string.Empty);
                                    }
                                }
                                break; 

                            case CookieToken.Version: 
                                // this is a new cookie, this token is for the next cookie. 
                                m_savedCookie = new Cookie();
                                int parsed; 
                                if (int.TryParse(m_tokenizer.Value, out parsed)) {
                                    m_savedCookie.Version = parsed;
                                }
                                return cookie; 

                            case CookieToken.Unknown: 
                                // this is a new cookie, the token is for the next cookie. 
                                m_savedCookie = new Cookie();
                                if (m_savedCookie.InternalSetName(m_tokenizer.Name) == false) { 
                                    //will be rejected
                                    m_savedCookie.InternalSetName(string.Empty);
                                }
                                m_savedCookie.Value = m_tokenizer.Value; 
                                return cookie;
 
                        } 
                        break;
 
                    case CookieToken.Attribute:
                        switch (m_tokenizer.Token) {
                            case CookieToken.Port:
                                if (!portSet) { 
                                    portSet = true;
                                    cookie.Port  = string.Empty; 
                                } 
                                break;
                        } 
                        break;
                    }
                }
            } while (!m_tokenizer.Eof && !m_tokenizer.EndOfCookie); 
            return cookie;
        } 
 
        internal static string CheckQuoted(string value) {
            if (value.Length < 2 || value[0] != '\"' || value[value.Length-1] != '\"') 
                return value;

            return value.Length == 2? string.Empty: value.Substring(1, value.Length-2);
        } 
    }
 
 
    internal class Comparer: IComparer {
 
            int IComparer.Compare(object ol, object or) {

                Cookie left  = (Cookie) ol;
                Cookie right = (Cookie) or; 

                int result; 
 
                if ((result = string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)) != 0) {
                    return result; 
                }

                if ((result = string.Compare(left.Domain, right.Domain, StringComparison.OrdinalIgnoreCase)) != 0) {
                    return result; 
                }
 
                // 
                //NB: The only path is case sensitive as per spec.
                //    However, on Win Platform that may break some lazy applications. 
                //
                if ((result = string.Compare(left.Path, right.Path, StringComparison.Ordinal)) != 0) {
                    return result;
                } 
                // They are equal here even if variants are still different.
                return 0; 
           } 
   }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.


                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK