diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs index 0802dad3..a5a1b512 100644 --- a/websocket-sharp/Net/Cookie.cs +++ b/websocket-sharp/Net/Cookie.cs @@ -1,36 +1,44 @@ -// -// Cookie.cs -// Copied from System.Net.Cookie.cs -// -// Authors: -// Lawrence Pit (loz@cable.a2000.nl) -// Gonzalo Paniagua Javier (gonzalo@ximian.com) -// Daniel Nauck (dna@mono-project.de) -// Sebastien Pouliot (sebastien@ximian.com) -// sta (sta.blockhead@gmail.com) -// -// Copyright (c) 2004,2009 Novell, Inc (http://www.novell.com) -// Copyright (c) 2012-2013 sta.blockhead (sta.blockhead@gmail.com) -// -// 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. -// +#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.Text; @@ -39,697 +47,767 @@ using System.Collections; namespace WebSocketSharp.Net { - /// - /// Provides a set of properties and methods 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 Static Private Fields - - static char [] reservedCharsForName = new char [] {' ', '=', ';', ',', '\n', '\r', '\t'}; - static char [] reservedCharsForValue = new char [] {';', ','}; - - #endregion - - #region Private Fields - - string comment; - Uri commentUri; - bool discard; - string domain; - DateTime expires; - bool httpOnly; - string name; - string path; - string port; - int [] ports; - bool secure; - DateTime timestamp; - string val; - int version; - - #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 [] {}; - timestamp = DateTime.Now; - val = String.Empty; - version = 0; - } - - /// - /// Initializes a new instance of the class - /// with the specified and . - /// - /// - /// A that contains the Name of the cookie. - /// - /// - /// A that contains the Value of the cookie. - /// - /// - /// - /// is or . - /// - /// - /// - 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 contains the Name of the cookie. - /// - /// - /// A that contains the Value of the cookie. - /// - /// - /// A that contains the value of the Path attribute of the cookie. - /// - /// - /// - /// is or . - /// - /// - /// - 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 contains the Name of the cookie. - /// - /// - /// A that contains the Value of the cookie. - /// - /// - /// A that contains the value of the Path attribute of the cookie. - /// - /// - /// A that contains the value of the Domain attribute of the cookie. - /// - /// - /// - /// is or . - /// - /// - /// - 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 || Expired) - return 0; - - var tmp = expires.Kind == DateTimeKind.Local - ? expires - : expires.ToLocalTime (); - - var span = tmp - DateTime.Now; - return span <= TimeSpan.Zero - ? 0 - : (int) span.TotalSeconds; - } - } - - internal int [] Ports { - get { return ports; } - } - - #endregion - - #region Public Properties - - /// - /// Gets or sets the value of the Comment attribute of the cookie. - /// - /// - /// A that contains a 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 contains a 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 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 contains a 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 is false. - /// - public bool Expired { - get { - return expires <= DateTime.Now && - expires != DateTime.MinValue; - } - set { - expires = value - ? DateTime.Now - : DateTime.MinValue; - } - } - - /// - /// Gets or sets the value of the Expires attribute of the cookie. - /// - /// - /// A that contains the date and time at which the cookie expires. - /// The default is . - /// - public DateTime Expires { - get { return expires; } - set { expires = value; } - } - - /// - /// Gets or sets a value indicating non-HTTP APIs can access the cookie. - /// - /// - /// true if non-HTTP APIs can not access the cookie; otherwise, false. - /// - public bool HttpOnly { - get { return httpOnly; } - set { httpOnly = value; } - } - - /// - /// Gets or sets the Name of the cookie. - /// - /// - /// A that contains the Name of the cookie. - /// - /// - /// - /// The value specified for a set operation is or . - /// - /// - /// - 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 contains a 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 contains a list of the TCP ports to which the cookie applies. - /// - /// - /// The value specified for a set operation is not enclosed in double quotes or could not be parsed. - /// - public string Port { - get { return port; } - set { - if (value.IsNullOrEmpty ()) { - port = String.Empty; - ports = new int [] {}; - return; - } - - if (!value.IsEnclosedIn ('"')) - throw new CookieException (String.Format ( - "The 'Port={0}' attribute of the cookie is invalid. The value must be enclosed in double quotes.", value)); - - string error; - if (!TryCreatePorts (value, out ports, out error)) - throw new CookieException (String.Format ( - "The 'Port={0}' attribute of the cookie is invalid. Invalid value: {1}", value, error)); - - 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 is false. - /// - public bool Secure { - get { return secure; } - set { secure = value; } - } - - /// - /// Gets the time when the cookie was issued. - /// - /// - /// A that contains the time when the cookie was issued. - /// - public DateTime TimeStamp { - get { return timestamp; } - } - - /// - /// Gets or sets the Value of the cookie. - /// - /// - /// A that contains 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 val; } - set { - string msg; - if (!CanSetValue (value, out msg)) - throw new CookieException (msg); - - val = value.Length == 0 ? "\"\"" : value; - } - } - - /// - /// Gets or sets the value of the Version attribute of the cookie. - /// - /// - /// An that contains the version of the HTTP state management - /// to which the cookie conforms. - /// - /// - /// The value specified for a set operation is not allowed. The value must be 0 or 1. - /// - public int Version { - get { return version; } - set { - if (value < 0 || value > 1) - throw new ArgumentOutOfRangeException ("value", "Must be 0 or 1."); - else - version = value; - } - } - - #endregion - - #region Private Methods - - static bool CanSetName (string name, out string message) - { - if (name.IsNullOrEmpty ()) { - message = "Name must not be null or empty."; - return false; - } - - if (name [0] == '$' || name.Contains (reservedCharsForName)) { - message = "Name contains an invalid character."; - return false; - } - - message = String.Empty; - return true; - } - - static bool CanSetValue (string value, out string message) - { - if (value == null) { - message = "Value must not be null."; - return false; - } - - if (value.Contains (reservedCharsForValue)) { - if (!value.IsEnclosedIn ('"')) { - message = "Value contains an invalid character."; - return false; - } - } - - message = String.Empty; - return true; - } - - 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); - } - - string ToResponseStringVersion0 () - { - var result = new StringBuilder (64); - result.AppendFormat ("{0}={1}", name, val); - if (expires != DateTime.MinValue) - result.AppendFormat ("; Expires={0}", - expires.ToUniversalTime ().ToString ("ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", - CultureInfo.CreateSpecificCulture("en-US"))); - - if (!path.IsNullOrEmpty ()) - result.AppendFormat ("; Path={0}", path); - - if (!domain.IsNullOrEmpty ()) - result.AppendFormat ("; Domain={0}", domain); - - if (secure) - result.Append ("; Secure"); - - if (httpOnly) - result.Append ("; HttpOnly"); - - return result.ToString (); - } - - string ToResponseStringVersion1 () - { - var result = new StringBuilder (64); - result.AppendFormat ("{0}={1}; Version={2}", name, val, version); - if (expires != DateTime.MinValue) - result.AppendFormat ("; Max-Age={0}", MaxAge); - - if (!path.IsNullOrEmpty ()) - result.AppendFormat ("; Path={0}", path); - - if (!domain.IsNullOrEmpty ()) - result.AppendFormat ("; Domain={0}", domain); - - if (!port.IsNullOrEmpty ()) - if (port == "\"\"") - result.Append ("; Port"); - else - result.AppendFormat ("; Port={0}", port); - - if (!comment.IsNullOrEmpty ()) - result.AppendFormat ("; Comment={0}", comment.UrlEncode ()); - - if (commentUri != null) - result.AppendFormat ("; CommentURL={0}", commentUri.OriginalString.Quote ()); - - if (discard) - result.Append ("; Discard"); - - if (secure) - result.Append ("; Secure"); - - return result.ToString (); - } - - static bool TryCreatePorts (string value, out int [] result, out string parseError) - { - var values = value.Trim ('"').Split (','); - var tmp = new int [values.Length]; - for (int i = 0; i < values.Length; i++) { - tmp [i] = int.MinValue; - var v = values [i].Trim (); - if (v.Length == 0) - continue; - - if (!int.TryParse (v, out tmp [i])) { - result = new int [] {}; - parseError = v; - return false; - } - } - - result = tmp; - 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, val); - - var result = new StringBuilder (64); - result.AppendFormat ("$Version={0}; {1}={2}", version, name, val); - if (!path.IsNullOrEmpty ()) - result.AppendFormat ("; $Path={0}", path); - else if (uri != null) - result.AppendFormat ("; $Path={0}", uri.GetAbsolutePath ()); - else - result.Append ("; $Path=/"); - - bool append_domain = uri == null || uri.Host != domain; - if (append_domain && !domain.IsNullOrEmpty ()) - result.AppendFormat ("; $Domain={0}", domain); - - if (!port.IsNullOrEmpty ()) - if (port == "\"\"") - result.Append ("; $Port"); - else - result.AppendFormat ("; $Port={0}", port); - - return result.ToString (); - } - - // From server to client - internal string ToResponseString () - { - return name.Length == 0 - ? String.Empty - : version == 0 - ? ToResponseStringVersion0 () - : ToResponseStringVersion1 (); - } - - #endregion - - #region Public Methods - - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// An to compare with the current . - /// - /// - /// true if the specified 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) && - val.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 contains a hash code for this instance. - /// - public override int GetHashCode () - { - return Hash ( - StringComparer.InvariantCultureIgnoreCase.GetHashCode (name), - val.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 - } + /// + /// 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 Static Fields + + private static char [] _reservedCharsForName = { ' ', '=', ';', ',', '\n', '\r', '\t' }; + private static char [] _reservedCharsForValue = { ';', ',' }; + + #endregion + + #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 bool _secure; + private DateTime _timestamp; + private string _value; + private int _version; + + #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 of Port attribute must be enclosed in double quotes."); + + string error; + if (!tryCreatePorts (value, out _ports, out error)) + throw new CookieException ( + String.Format ("The value of Port attribute contains an invalid value: {0}", error)); + + _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", "Must be 0 or 1."); + + _version = value; + } + } + + #endregion + + #region Private Methods + + private static bool canSetName (string name, out string message) + { + if (name.IsNullOrEmpty ()) { + message = "Name must not be null or empty."; + return false; + } + + if (name [0] == '$' || name.Contains (_reservedCharsForName)) { + message = "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 = "Value must not be null."; + return false; + } + + if (value.Contains (_reservedCharsForValue) && !value.IsEnclosedIn ('"')) { + message = "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 result = new StringBuilder (64); + result.AppendFormat ("{0}={1}", _name, _value); + + if (_expires != DateTime.MinValue) + result.AppendFormat ( + "; Expires={0}", + _expires.ToUniversalTime ().ToString ( + "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", + CultureInfo.CreateSpecificCulture ("en-US"))); + + if (!_path.IsNullOrEmpty ()) + result.AppendFormat ("; Path={0}", _path); + + if (!_domain.IsNullOrEmpty ()) + result.AppendFormat ("; Domain={0}", _domain); + + if (_secure) + result.Append ("; Secure"); + + if (_httpOnly) + result.Append ("; HttpOnly"); + + return result.ToString (); + } + + private string toResponseStringVersion1 () + { + var result = new StringBuilder (64); + result.AppendFormat ("{0}={1}; Version={2}", _name, _value, _version); + + if (_expires != DateTime.MinValue) + result.AppendFormat ("; Max-Age={0}", MaxAge); + + if (!_path.IsNullOrEmpty ()) + result.AppendFormat ("; Path={0}", _path); + + if (!_domain.IsNullOrEmpty ()) + result.AppendFormat ("; Domain={0}", _domain); + + if (!_port.IsNullOrEmpty ()) { + if (_port == "\"\"") + result.Append ("; Port"); + else + result.AppendFormat ("; Port={0}", _port); + } + + if (!_comment.IsNullOrEmpty ()) + result.AppendFormat ("; Comment={0}", _comment.UrlEncode ()); + + if (_commentUri != null) + result.AppendFormat ("; CommentURL={0}", _commentUri.OriginalString.Quote ()); + + if (_discard) + result.Append ("; Discard"); + + if (_secure) + result.Append ("; Secure"); + + return result.ToString (); + } + + private static bool tryCreatePorts (string value, out int [] result, out string parseError) + { + var ports = value.Trim ('"').Split (','); + var tmp = new int [ports.Length]; + for (int i = 0; i < ports.Length; i++) { + tmp [i] = int.MinValue; + var port = ports [i].Trim (); + if (port.Length == 0) + continue; + + if (!int.TryParse (port, out tmp [i])) { + result = new int [0]; + parseError = port; + + return false; + } + } + + result = tmp; + 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 result = new StringBuilder (64); + result.AppendFormat ("$Version={0}; {1}={2}", _version, _name, _value); + + if (!_path.IsNullOrEmpty ()) + result.AppendFormat ("; $Path={0}", _path); + else if (uri != null) + result.AppendFormat ("; $Path={0}", uri.GetAbsolutePath ()); + else + result.Append ("; $Path=/"); + + bool appendDomain = uri == null || uri.Host != _domain; + if (appendDomain && !_domain.IsNullOrEmpty ()) + result.AppendFormat ("; $Domain={0}", _domain); + + if (!_port.IsNullOrEmpty ()) { + if (_port == "\"\"") + result.Append ("; $Port"); + else + result.AppendFormat ("; $Port={0}", _port); + } + + return result.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 + } }