Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / fx / src / Net / System / Net / cookie.cs / 1 / cookie.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net { using System.Collections; 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") // ////// [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) ); } } ///[To be supplied.] ////// public bool Expired { get { return (m_expires <= DateTime.Now) && (m_expires != DateTime.MinValue); } 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? "[To be supplied.] ///": 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; } /// /// 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; } } // // 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? "[To be supplied.] ///": 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('.'); bool is_local = false; // 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.Length < 4 || (!(is_local = (string.Compare(domain, ".local", StringComparison.OrdinalIgnoreCase)) == 0) && domain.IndexOf('.', 1, domain.Length-2) == -1)) { // explicit domains must contain 'inside' dots or be ".local" valid = false; } else if (is_local && isLocalDomain) { // valid ".local" found for the local domain Uri valid = true; } else if (is_local && !isLocalDomain) { // ".local" cannot happen on non-local domain 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 (!(host.Length + 1 == domain.Length && string.Compare(host, 0, domain, 1, host.Length, StringComparison.OrdinalIgnoreCase) == 0)) { if (host.Length <= domain.Length || string.Compare(host, host.Length-domain.Length, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) { valid = false; } } } else if ( !is_local && (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 valid = false; } if (valid) { if(is_local) { m_domainKey = localDomain.ToLower(CultureInfo.InvariantCulture); } else { 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; } /// /// 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 { m_port = value; // Parse port list if (value[0] != '\"' || value[value.Length-1] != '\"') { throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port)); } string[] ports = value.Split(PortSplitDelimiters); m_port_list = new int[ports.Length]; for (int i = 0; i < ports.Length; ++i) { m_port_list[i] = -1; if(ports[i].Length == 0) { //ignore spaces this way, and leave port=-1 in the slot continue; } if (!Int32.TryParse(ports[i], out m_port_list[i])) throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port)); } 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.] ////// [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) ; } ////// 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 (!Plain && !m_domain_implicit && m_domain!=null && m_domain.Length>0) { result += SeparatorLiteral + DomainAttributeName + EqualsLiteral + m_domain; } int seconds = (Expires-DateTime.UtcNow).Seconds; if (seconds>0) { result += SeparatorLiteral + MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberFormatInfo.InvariantInfo); } if (!Plain && !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: //[To be supplied.] ///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; // ParseCookieDate() does many formats for the date. // Also note that the parser will eat day of the week // plus comma and leading spaces DateTime expires; if (HttpDateParse.ParseCookieDate(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. //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net { using System.Collections; 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") // ////// [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) ); } } ///[To be supplied.] ////// public bool Expired { get { return (m_expires <= DateTime.Now) && (m_expires != DateTime.MinValue); } 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? "[To be supplied.] ///": 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; } /// /// 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; } } // // 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? "[To be supplied.] ///": 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('.'); bool is_local = false; // 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.Length < 4 || (!(is_local = (string.Compare(domain, ".local", StringComparison.OrdinalIgnoreCase)) == 0) && domain.IndexOf('.', 1, domain.Length-2) == -1)) { // explicit domains must contain 'inside' dots or be ".local" valid = false; } else if (is_local && isLocalDomain) { // valid ".local" found for the local domain Uri valid = true; } else if (is_local && !isLocalDomain) { // ".local" cannot happen on non-local domain 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 (!(host.Length + 1 == domain.Length && string.Compare(host, 0, domain, 1, host.Length, StringComparison.OrdinalIgnoreCase) == 0)) { if (host.Length <= domain.Length || string.Compare(host, host.Length-domain.Length, domain, 0, domain.Length, StringComparison.OrdinalIgnoreCase) != 0) { valid = false; } } } else if ( !is_local && (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 valid = false; } if (valid) { if(is_local) { m_domainKey = localDomain.ToLower(CultureInfo.InvariantCulture); } else { 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; } /// /// 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 { m_port = value; // Parse port list if (value[0] != '\"' || value[value.Length-1] != '\"') { throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port)); } string[] ports = value.Split(PortSplitDelimiters); m_port_list = new int[ports.Length]; for (int i = 0; i < ports.Length; ++i) { m_port_list[i] = -1; if(ports[i].Length == 0) { //ignore spaces this way, and leave port=-1 in the slot continue; } if (!Int32.TryParse(ports[i], out m_port_list[i])) throw new CookieException(SR.GetString(SR.net_cookie_attribute, PortAttributeName, m_port)); } 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.] ////// [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) ; } ////// 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 (!Plain && !m_domain_implicit && m_domain!=null && m_domain.Length>0) { result += SeparatorLiteral + DomainAttributeName + EqualsLiteral + m_domain; } int seconds = (Expires-DateTime.UtcNow).Seconds; if (seconds>0) { result += SeparatorLiteral + MaxAgeAttributeName + EqualsLiteral + seconds.ToString(NumberFormatInfo.InvariantInfo); } if (!Plain && !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: //[To be supplied.] ///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; // ParseCookieDate() does many formats for the date. // Also note that the parser will eat day of the week // plus comma and leading spaces DateTime expires; if (HttpDateParse.ParseCookieDate(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

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- HtmlEmptyTagControlBuilder.cs
- SiteMapHierarchicalDataSourceView.cs
- CngKey.cs
- SafeRightsManagementSessionHandle.cs
- mediaclock.cs
- SiteMembershipCondition.cs
- EventEntry.cs
- FlowDocumentPage.cs
- SqlCacheDependencySection.cs
- TypeConverterValueSerializer.cs
- CryptoApi.cs
- DecimalAnimationUsingKeyFrames.cs
- IteratorFilter.cs
- FixedPosition.cs
- InkPresenter.cs
- WindowsPen.cs
- BatchServiceHost.cs
- WorkflowService.cs
- LookupNode.cs
- SchemaEntity.cs
- MessageQueueTransaction.cs
- GenericAuthenticationEventArgs.cs
- ReadingWritingEntityEventArgs.cs
- XsltInput.cs
- SmiContextFactory.cs
- DataBinding.cs
- SchemaConstraints.cs
- WindowsFormsDesignerOptionService.cs
- ThemeableAttribute.cs
- MetadataExchangeClient.cs
- sqlinternaltransaction.cs
- WebPartConnectionsDisconnectVerb.cs
- BaseCollection.cs
- ChannelManager.cs
- WeakHashtable.cs
- ZipIOCentralDirectoryFileHeader.cs
- ActiveXSite.cs
- XmlReflectionImporter.cs
- MetaForeignKeyColumn.cs
- File.cs
- XmlnsPrefixAttribute.cs
- WebControlsSection.cs
- XmlTextAttribute.cs
- UIElement3D.cs
- XmlResolver.cs
- TextRangeEditLists.cs
- GregorianCalendarHelper.cs
- PhonemeConverter.cs
- InfoCardMetadataExchangeClient.cs
- TdsParserStateObject.cs
- TabletDeviceInfo.cs
- NetworkInterface.cs
- ContainerSelectorGlyph.cs
- SafeLocalMemHandle.cs
- CheckPair.cs
- SqlCommandBuilder.cs
- DefaultBinder.cs
- FrameDimension.cs
- TriggerBase.cs
- Queue.cs
- ResourceProviderFactory.cs
- KnownTypeAttribute.cs
- MouseOverProperty.cs
- SystemIPv4InterfaceProperties.cs
- HijriCalendar.cs
- TableItemStyle.cs
- CriticalHandle.cs
- ChannelManager.cs
- CancelEventArgs.cs
- NameValueFileSectionHandler.cs
- VirtualizingStackPanel.cs
- OutputCacheSettingsSection.cs
- CommandID.cs
- ScrollPatternIdentifiers.cs
- SupportsEventValidationAttribute.cs
- InlineObject.cs
- XmlBufferReader.cs
- TabControl.cs
- DataSetMappper.cs
- Vector3DAnimationBase.cs
- UnsafeNativeMethodsCLR.cs
- Perspective.cs
- ButtonDesigner.cs
- XmlSchemaParticle.cs
- SplayTreeNode.cs
- PerformanceCounterPermission.cs
- ColumnResizeAdorner.cs
- MessageEncodingBindingElement.cs
- HostedHttpRequestAsyncResult.cs
- DnsCache.cs
- EntityDataSourceStatementEditor.cs
- Cursor.cs
- EventWaitHandleSecurity.cs
- ConfigurationLocation.cs
- CustomValidator.cs
- AbandonedMutexException.cs
- CodeDomComponentSerializationService.cs
- Win32.cs
- HighlightVisual.cs
- EntityWithChangeTrackerStrategy.cs