#region License /* * Cookie.cs * * This code is derived from System.Net.Cookie.cs of Mono * (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com) * Copyright (c) 2012-2014 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion #region Authors /* * Authors: * - Lawrence Pit * - Gonzalo Paniagua Javier * - Daniel Nauck * - Sebastien Pouliot */ #endregion using System; using System.Globalization; using System.Text; namespace WebSocketSharp.Net { /// /// Provides a set of methods and properties used to manage an HTTP Cookie. /// /// /// /// The Cookie class supports the following cookie formats: /// Netscape specification, /// RFC 2109, and /// RFC 2965 /// /// /// The Cookie class cannot be inherited. /// /// [Serializable] public sealed class Cookie { #region Private Fields private string _comment; private Uri _commentUri; private bool _discard; private string _domain; private DateTime _expires; private bool _httpOnly; private string _name; private string _path; private string _port; private int[] _ports; private static readonly char[] _reservedCharsForName; private static readonly char[] _reservedCharsForValue; private bool _secure; private DateTime _timestamp; private string _value; private int _version; #endregion #region Static Constructor static Cookie () { _reservedCharsForName = new[] { ' ', '=', ';', ',', '\n', '\r', '\t' }; _reservedCharsForValue = new[] { ';', ',' }; } #endregion #region Public Constructors /// /// Initializes a new instance of the class. /// public Cookie () { _comment = String.Empty; _domain = String.Empty; _expires = DateTime.MinValue; _name = String.Empty; _path = String.Empty; _port = String.Empty; _ports = new int[0]; _timestamp = DateTime.Now; _value = String.Empty; _version = 0; } /// /// Initializes a new instance of the class with the specified /// and . /// /// /// A that represents the Name of the cookie. /// /// /// A that represents the Value of the cookie. /// /// /// /// is or empty. /// /// /// - or - /// /// /// contains an invalid character. /// /// /// - or - /// /// /// is . /// /// /// - or - /// /// /// contains a string not enclosed in double quotes /// that contains an invalid character. /// /// public Cookie (string name, string value) : this () { Name = name; Value = value; } /// /// Initializes a new instance of the class with the specified /// , , and . /// /// /// A that represents the Name of the cookie. /// /// /// A that represents the Value of the cookie. /// /// /// A that represents the value of the Path attribute of the cookie. /// /// /// /// is or empty. /// /// /// - or - /// /// /// contains an invalid character. /// /// /// - or - /// /// /// is . /// /// /// - or - /// /// /// contains a string not enclosed in double quotes /// that contains an invalid character. /// /// public Cookie (string name, string value, string path) : this (name, value) { Path = path; } /// /// Initializes a new instance of the class with the specified /// , , , and /// . /// /// /// A that represents the Name of the cookie. /// /// /// A that represents the Value of the cookie. /// /// /// A that represents the value of the Path attribute of the cookie. /// /// /// A that represents the value of the Domain attribute of the cookie. /// /// /// /// is or empty. /// /// /// - or - /// /// /// contains an invalid character. /// /// /// - or - /// /// /// is . /// /// /// - or - /// /// /// contains a string not enclosed in double quotes /// that contains an invalid character. /// /// public Cookie (string name, string value, string path, string domain) : this (name, value, path) { Domain = domain; } #endregion #region Internal Properties internal bool ExactDomain { get; set; } internal int MaxAge { get { if (_expires == DateTime.MinValue) return 0; var expires = _expires.Kind != DateTimeKind.Local ? _expires.ToLocalTime () : _expires; var span = expires - DateTime.Now; return span > TimeSpan.Zero ? (int) span.TotalSeconds : 0; } } internal int[] Ports { get { return _ports; } } #endregion #region Public Properties /// /// Gets or sets the value of the Comment attribute of the cookie. /// /// /// A that represents the comment to document intended use of the cookie. /// public string Comment { get { return _comment; } set { _comment = value ?? String.Empty; } } /// /// Gets or sets the value of the CommentURL attribute of the cookie. /// /// /// A that represents the URI that provides the comment to document intended /// use of the cookie. /// public Uri CommentUri { get { return _commentUri; } set { _commentUri = value; } } /// /// Gets or sets a value indicating whether the client discards the cookie unconditionally /// when the client terminates. /// /// /// true if the client discards the cookie unconditionally when the client terminates; /// otherwise, false. The default value is false. /// public bool Discard { get { return _discard; } set { _discard = value; } } /// /// Gets or sets the value of the Domain attribute of the cookie. /// /// /// A that represents the URI for which the cookie is valid. /// public string Domain { get { return _domain; } set { if (value.IsNullOrEmpty ()) { _domain = String.Empty; ExactDomain = true; } else { _domain = value; ExactDomain = value[0] != '.'; } } } /// /// Gets or sets a value indicating whether the cookie has expired. /// /// /// true if the cookie has expired; otherwise, false. /// The default value is false. /// public bool Expired { get { return _expires != DateTime.MinValue && _expires <= DateTime.Now; } set { _expires = value ? DateTime.Now : DateTime.MinValue; } } /// /// Gets or sets the value of the Expires attribute of the cookie. /// /// /// A that represents the date and time at which the cookie expires. /// The default value is . /// public DateTime Expires { get { return _expires; } set { _expires = value; } } /// /// Gets or sets a value indicating whether non-HTTP APIs can access the cookie. /// /// /// true if non-HTTP APIs cannot access the cookie; otherwise, false. /// The default value is false. /// public bool HttpOnly { get { return _httpOnly; } set { _httpOnly = value; } } /// /// Gets or sets the Name of the cookie. /// /// /// A that represents the Name of the cookie. /// /// /// /// The value specified for a set operation is or empty. /// /// /// - or - /// /// /// The value specified for a set operation contains an invalid character. /// /// public string Name { get { return _name; } set { string msg; if (!canSetName (value, out msg)) throw new CookieException (msg); _name = value; } } /// /// Gets or sets the value of the Path attribute of the cookie. /// /// /// A that represents the subset of URI on the origin server /// to which the cookie applies. /// public string Path { get { return _path; } set { _path = value ?? String.Empty; } } /// /// Gets or sets the value of the Port attribute of the cookie. /// /// /// A that represents the list of TCP ports to which the cookie applies. /// /// /// The value specified for a set operation isn't enclosed in double quotes or /// couldn't be parsed. /// public string Port { get { return _port; } set { if (value.IsNullOrEmpty ()) { _port = String.Empty; _ports = new int[0]; return; } if (!value.IsEnclosedIn ('"')) throw new CookieException ( "The value specified for the Port attribute isn't enclosed in double quotes."); string err; if (!tryCreatePorts (value, out _ports, out err)) throw new CookieException ( String.Format ( "The value specified for the Port attribute contains an invalid value: {0}", err)); _port = value; } } /// /// Gets or sets a value indicating whether the security level of the cookie is secure. /// /// /// When this property is true, the cookie may be included in the HTTP request /// only if the request is transmitted over the HTTPS. /// /// /// true if the security level of the cookie is secure; otherwise, false. /// The default value is false. /// public bool Secure { get { return _secure; } set { _secure = value; } } /// /// Gets the time when the cookie was issued. /// /// /// A that represents the time when the cookie was issued. /// public DateTime TimeStamp { get { return _timestamp; } } /// /// Gets or sets the Value of the cookie. /// /// /// A that represents the Value of the cookie. /// /// /// /// The value specified for a set operation is . /// /// /// - or - /// /// /// The value specified for a set operation contains a string not enclosed in double quotes /// that contains an invalid character. /// /// public string Value { get { return _value; } set { string msg; if (!canSetValue (value, out msg)) throw new CookieException (msg); _value = value.Length > 0 ? value : "\"\""; } } /// /// Gets or sets the value of the Version attribute of the cookie. /// /// /// An that represents the version of the HTTP state management /// to which the cookie conforms. /// /// /// The value specified for a set operation isn't 0 or 1. /// public int Version { get { return _version; } set { if (value < 0 || value > 1) throw new ArgumentOutOfRangeException ("value", "Not 0 or 1."); _version = value; } } #endregion #region Private Methods private static bool canSetName (string name, out string message) { if (name.IsNullOrEmpty ()) { message = "The value specified for the Name is null or empty."; return false; } if (name[0] == '$' || name.Contains (_reservedCharsForName)) { message = "The value specified for the Name contains an invalid character."; return false; } message = String.Empty; return true; } private static bool canSetValue (string value, out string message) { if (value == null) { message = "The value specified for the Value is null."; return false; } if (value.Contains (_reservedCharsForValue) && !value.IsEnclosedIn ('"')) { message = "The value specified for the Value contains an invalid character."; return false; } message = String.Empty; return true; } private static int hash (int i, int j, int k, int l, int m) { return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12); } private string toResponseStringVersion0 () { var output = new StringBuilder (64); output.AppendFormat ("{0}={1}", _name, _value); if (_expires != DateTime.MinValue) output.AppendFormat ( "; Expires={0}", _expires.ToUniversalTime ().ToString ( "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", CultureInfo.CreateSpecificCulture ("en-US"))); if (!_path.IsNullOrEmpty ()) output.AppendFormat ("; Path={0}", _path); if (!_domain.IsNullOrEmpty ()) output.AppendFormat ("; Domain={0}", _domain); if (_secure) output.Append ("; Secure"); if (_httpOnly) output.Append ("; HttpOnly"); return output.ToString (); } private string toResponseStringVersion1 () { var buff = new StringBuilder (64); buff.AppendFormat ("{0}={1}; Version={2}", _name, _value, _version); if (_expires != DateTime.MinValue) buff.AppendFormat ("; Max-Age={0}", MaxAge); if (!_path.IsNullOrEmpty ()) buff.AppendFormat ("; Path={0}", _path); if (!_domain.IsNullOrEmpty ()) buff.AppendFormat ("; Domain={0}", _domain); if (!_port.IsNullOrEmpty ()) { buff.Append ( _port != "\"\"" ? String.Format ("; Port={0}", _port) : "; Port" ); } if (!_comment.IsNullOrEmpty ()) buff.AppendFormat ("; Comment={0}", HttpUtility.UrlEncode (_comment)); if (_commentUri != null) { var url = _commentUri.OriginalString; buff.AppendFormat ( "; CommentURL={0}", !url.IsToken () ? url.Quote () : url ); } if (_discard) buff.Append ("; Discard"); if (_secure) buff.Append ("; Secure"); return buff.ToString (); } private static bool tryCreatePorts (string value, out int[] result, out string parseError) { var ports = value.Trim ('"').Split (','); var len = ports.Length; var res = new int[len]; for (var i = 0; i < len; i++) { res[i] = Int32.MinValue; var port = ports[i].Trim (); if (port.Length == 0) continue; if (!Int32.TryParse (port, out res[i])) { result = new int[0]; parseError = port; return false; } } result = res; parseError = String.Empty; return true; } #endregion #region Internal Methods // From client to server internal string ToRequestString (Uri uri) { if (_name.Length == 0) return String.Empty; if (_version == 0) return String.Format ("{0}={1}", _name, _value); var output = new StringBuilder (64); output.AppendFormat ("$Version={0}; {1}={2}", _version, _name, _value); if (!_path.IsNullOrEmpty ()) output.AppendFormat ("; $Path={0}", _path); else if (uri != null) output.AppendFormat ("; $Path={0}", uri.GetAbsolutePath ()); else output.Append ("; $Path=/"); var appendDomain = uri == null || uri.Host != _domain; if (appendDomain && !_domain.IsNullOrEmpty ()) output.AppendFormat ("; $Domain={0}", _domain); if (!_port.IsNullOrEmpty ()) { if (_port == "\"\"") output.Append ("; $Port"); else output.AppendFormat ("; $Port={0}", _port); } return output.ToString (); } // From server to client internal string ToResponseString () { return _name.Length > 0 ? (_version == 0 ? toResponseStringVersion0 () : toResponseStringVersion1 ()) : String.Empty; } #endregion #region Public Methods /// /// Determines whether the specified is equal to the current /// . /// /// /// An to compare with the current . /// /// /// true if is equal to the current ; /// otherwise, false. /// public override bool Equals (Object comparand) { var cookie = comparand as Cookie; return cookie != null && _name.Equals (cookie.Name, StringComparison.InvariantCultureIgnoreCase) && _value.Equals (cookie.Value, StringComparison.InvariantCulture) && _path.Equals (cookie.Path, StringComparison.InvariantCulture) && _domain.Equals (cookie.Domain, StringComparison.InvariantCultureIgnoreCase) && _version == cookie.Version; } /// /// Serves as a hash function for a object. /// /// /// An that represents the hash code for the current . /// public override int GetHashCode () { return hash ( StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name), _value.GetHashCode (), _path.GetHashCode (), StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain), _version); } /// /// Returns a that represents the current . /// /// /// This method returns a to use to send an HTTP Cookie to /// an origin server. /// /// /// A that represents the current . /// public override string ToString () { // i.e., only used for clients // See para 4.2.2 of RFC 2109 and para 3.3.4 of RFC 2965 // See also bug #316017 return ToRequestString (null); } #endregion } }