// // CookieCollection.cs // Copied from System.Net.CookieCollection.cs // // Authors: // Lawrence Pit (loz@cable.a2000.nl) // Gonzalo Paniagua Javier (gonzalo@ximian.com) // 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. // using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.Serialization; using System.Text; namespace WebSocketSharp.Net { /// /// Provides a collection container for instances of the class. /// [Serializable] public class CookieCollection : ICollection, IEnumerable { // not 100% identical to MS implementation sealed class CookieCollectionComparer : IComparer { public int Compare (Cookie x, Cookie y) { if (x == null || y == null) return 0; int c1 = x.Name.Length + x.Value.Length; int c2 = y.Name.Length + y.Value.Length; return (c1 - c2); } } #region Private Static Fields static CookieCollectionComparer Comparer = new CookieCollectionComparer (); #endregion #region Private Fields List list; object sync; #endregion #region Public Constructors /// /// Initializes a new instance of the class. /// public CookieCollection () { list = new List (); } #endregion #region Internal Properties internal IList List { get { return list; } } internal IEnumerable Sorted { get { return from cookie in list orderby cookie.Version, cookie.Name, cookie.Path.Length descending select cookie; } } #endregion #region Public Properties /// /// Gets the number of cookies contained in the . /// /// /// An that indicates the number of cookies contained in the . /// public int Count { get { return list.Count; } } // LAMESPEC: So how is one supposed to create a writable CookieCollection // instance?? We simply ignore this property, as this collection is always // writable. // /// /// Gets a value indicating whether the is read-only. /// /// /// true if the is read-only; otherwise, false. /// The default is true. /// public bool IsReadOnly { get { return true; } } /// /// Gets a value indicating whether access to the is thread safe. /// /// /// true if access to the is thread safe; otherwise, false. /// The default is false. /// public bool IsSynchronized { get { return false; } } /// /// Gets the with the specified from the . /// /// /// A with the specified in the . /// /// /// An is the zero-based index of the to find. /// /// /// is less than zero or is greater than or /// equal to . /// public Cookie this [int index] { get { if (index < 0 || index >= list.Count) throw new ArgumentOutOfRangeException ("index"); return list [index]; } } /// /// Gets the with the specified from the . /// /// /// A with the specified in the . /// /// /// A is the name of the to find. /// /// /// is . /// public Cookie this [string name] { get { if (name == null) throw new ArgumentNullException ("name"); foreach (var cookie in Sorted) { if (cookie.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) return cookie; } return null; } } /// /// Gets an object to use to synchronize access to the . /// /// /// An to use to synchronize access to the . /// public Object SyncRoot { get { if (sync == null) sync = new object (); return sync; } } #endregion #region Private Methods static CookieCollection ParseRequest (string value) { var cookies = new CookieCollection (); Cookie cookie = null; int version = 0; string [] pairs = Split(value).ToArray(); for (int i = 0; i < pairs.Length; i++) { string pair = pairs [i].Trim (); if (pair.Length == 0) continue; if (pair.StartsWith ("$version", StringComparison.InvariantCultureIgnoreCase)) { version = Int32.Parse (pair.GetValueInternal ("=").Trim ('"')); } else if (pair.StartsWith ("$path", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Path = pair.GetValueInternal ("="); } else if (pair.StartsWith ("$domain", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Domain = pair.GetValueInternal ("="); } else if (pair.StartsWith ("$port", StringComparison.InvariantCultureIgnoreCase)) { var port = pair.Equals ("$port", StringComparison.InvariantCultureIgnoreCase) ? "\"\"" : pair.GetValueInternal ("="); if (cookie != null) cookie.Port = port; } else { if (cookie != null) cookies.Add (cookie); string name; string val = String.Empty; int pos = pair.IndexOf ('='); if (pos == -1) { name = pair; } else if (pos == pair.Length - 1) { name = pair.Substring (0, pos).TrimEnd (' '); } else { name = pair.Substring (0, pos).TrimEnd (' '); val = pair.Substring (pos + 1).TrimStart (' '); } cookie = new Cookie (name, val); if (version != 0) cookie.Version = version; } } if (cookie != null) cookies.Add (cookie); return cookies; } static CookieCollection ParseResponse (string value) { var cookies = new CookieCollection (); Cookie cookie = null; string [] pairs = Split(value).ToArray(); for (int i = 0; i < pairs.Length; i++) { string pair = pairs [i].Trim (); if (pair.Length == 0) continue; if (pair.StartsWith ("version", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Version = Int32.Parse (pair.GetValueInternal ("=").Trim ('"')); } else if (pair.StartsWith ("expires", StringComparison.InvariantCultureIgnoreCase)) { var buffer = new StringBuilder (pair.GetValueInternal ("="), 32); if (i < pairs.Length - 1) buffer.AppendFormat (", {0}", pairs [++i].Trim ()); DateTime expires; if (!DateTime.TryParseExact (buffer.ToString (), new string [] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires)) expires = DateTime.Now; if (cookie != null && cookie.Expires == DateTime.MinValue) cookie.Expires = expires.ToLocalTime (); } else if (pair.StartsWith ("max-age", StringComparison.InvariantCultureIgnoreCase)) { int max = Int32.Parse (pair.GetValueInternal ("=").Trim ('"')); var expires = DateTime.Now.AddSeconds ((double) max); if (cookie != null) cookie.Expires = expires; } else if (pair.StartsWith ("path", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Path = pair.GetValueInternal ("="); } else if (pair.StartsWith ("domain", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Domain = pair.GetValueInternal ("="); } else if (pair.StartsWith ("port", StringComparison.InvariantCultureIgnoreCase)) { var port = pair.Equals ("port", StringComparison.InvariantCultureIgnoreCase) ? "\"\"" : pair.GetValueInternal ("="); if (cookie != null) cookie.Port = port; } else if (pair.StartsWith ("comment", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Comment = pair.GetValueInternal ("=").UrlDecode (); } else if (pair.StartsWith ("commenturl", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.CommentUri = pair.GetValueInternal ("=").Trim ('"').ToUri (); } else if (pair.StartsWith ("discard", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Discard = true; } else if (pair.StartsWith ("secure", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.Secure = true; } else if (pair.StartsWith ("httponly", StringComparison.InvariantCultureIgnoreCase)) { if (cookie != null) cookie.HttpOnly = true; } else { if (cookie != null) cookies.Add (cookie); string name; string val = String.Empty; int pos = pair.IndexOf ('='); if (pos == -1) { name = pair; } else if (pos == pair.Length - 1) { name = pair.Substring (0, pos).TrimEnd (' '); } else { name = pair.Substring (0, pos).TrimEnd (' '); val = pair.Substring (pos + 1).TrimStart (' '); } cookie = new Cookie (name, val); } } if (cookie != null) cookies.Add (cookie); return cookies; } int SearchCookie (Cookie cookie) { string name = cookie.Name; string path = cookie.Path; string domain = cookie.Domain; int version = cookie.Version; for (int i = list.Count - 1; i >= 0; i--) { Cookie c = list [i]; if (!c.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) continue; if (!c.Path.Equals (path, StringComparison.InvariantCulture)) continue; if (!c.Domain.Equals (domain, StringComparison.InvariantCultureIgnoreCase)) continue; if (c.Version != version) continue; return i; } return -1; } static IEnumerable Split (string value) { return value.SplitHeaderValue (',', ';'); } #endregion #region Internal Methods internal static CookieCollection Parse (string value, bool response) { return response ? ParseResponse (value) : ParseRequest (value); } internal void SetOrRemove (Cookie cookie) { int pos = SearchCookie (cookie); if (pos == -1) { if (!cookie.Expired) list.Add (cookie); } else { if (!cookie.Expired) list [pos] = cookie; else list.RemoveAt (pos); } } internal void SetOrRemove (CookieCollection cookies) { foreach (Cookie cookie in cookies) SetOrRemove (cookie); } internal void Sort () { if (list.Count > 0) list.Sort (Comparer); } #endregion #region Public Methods /// /// Add the specified to the . /// /// /// A to add to the . /// /// /// is . /// public void Add (Cookie cookie) { if (cookie == null) throw new ArgumentNullException ("cookie"); int pos = SearchCookie (cookie); if (pos == -1) list.Add (cookie); else list [pos] = cookie; } /// /// Add the elements of the specified to the current . /// /// /// A to add to the current . /// /// /// is . /// public void Add (CookieCollection cookies) { if (cookies == null) throw new ArgumentNullException ("cookies"); foreach (Cookie cookie in cookies) Add (cookie); } /// /// Copies the elements of the to the specified , /// starting at the specified in the . /// /// /// An is the destination of the elements copied from the . /// /// /// An that indicates the zero-based index in at which copying begins. /// /// /// is . /// /// /// is less than zero. /// /// /// /// is multidimensional. /// /// /// -or- /// /// /// The number of elements in the is greater than the available space /// from index to the end of the destination . /// /// /// /// The elements in the cannot be cast automatically /// to the type of the destination . /// public void CopyTo (Array array, int index) { if (array == null) throw new ArgumentNullException ("array"); if (index < 0) throw new ArgumentOutOfRangeException ("index", "Must not be less than zero."); if (array.Rank > 1) throw new ArgumentException ("Must not be multidimensional.", "array"); if (array.Length - index < list.Count) throw new ArgumentException ( "The number of elements in this collection is greater than the available space of the destination array."); if (!array.GetType ().GetElementType ().IsAssignableFrom (typeof (Cookie))) throw new InvalidCastException ( "The elements in this collection cannot be cast automatically to the type of the destination array."); (list as IList).CopyTo (array, index); } /// /// Copies the elements of the to the specified array of , /// starting at the specified in the . /// /// /// An array of is the destination of the elements copied from the . /// /// /// An that indicates the zero-based index in at which copying begins. /// /// /// is . /// /// /// is less than zero. /// /// /// The number of elements in the is greater than the available space /// from index to the end of the destination . /// public void CopyTo (Cookie [] array, int index) { if (array == null) throw new ArgumentNullException ("array"); if (index < 0) throw new ArgumentOutOfRangeException ("index", "Must not be less than zero."); if (array.Length - index < list.Count) throw new ArgumentException ( "The number of elements in this collection is greater than the available space of the destination array."); list.CopyTo (array, index); } /// /// Gets the enumerator to use to iterate through the . /// /// /// An instance of an implementation of the interface /// to use to iterate through the . /// public IEnumerator GetEnumerator () { return list.GetEnumerator (); } #endregion } }