123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- #if !BESTHTTP_DISABLE_COOKIES && (!UNITY_WEBGL || UNITY_EDITOR)
- using System;
- using System.Collections.Generic;
- using BestHTTP.Extensions;
- using System.IO;
- namespace BestHTTP.Cookies
- {
- /// <summary>
- /// The Cookie implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
- /// </summary>
- public sealed class Cookie : IComparable<Cookie>, IEquatable<Cookie>
- {
- private const int Version = 1;
- #region Public Properties
- /// <summary>
- /// The name of the cookie.
- /// </summary>
- public string Name { get; private set; }
- /// <summary>
- /// The value of the cookie.
- /// </summary>
- public string Value { get; private set; }
- /// <summary>
- /// The Date when the Cookie is registered.
- /// </summary>
- public DateTime Date { get; internal set; }
- /// <summary>
- /// When this Cookie last used in a request.
- /// </summary>
- public DateTime LastAccess { get; set; }
- /// <summary>
- /// The Expires attribute indicates the maximum lifetime of the cookie, represented as the date and time at which the cookie expires.
- /// The user agent is not required to retain the cookie until the specified date has passed.
- /// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
- /// </summary>
- public DateTime Expires { get; private set; }
- /// <summary>
- /// The Max-Age attribute indicates the maximum lifetime of the cookie, represented as the number of seconds until the cookie expires.
- /// The user agent is not required to retain the cookie for the specified duration.
- /// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
- /// </summary>
- public long MaxAge { get; private set; }
- /// <summary>
- /// If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie until "the current session is over".
- /// </summary>
- public bool IsSession { get; private set; }
- /// <summary>
- /// The Domain attribute specifies those hosts to which the cookie will be sent.
- /// For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie
- /// in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com.
- /// If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.
- /// </summary>
- public string Domain { get; private set; }
- /// <summary>
- /// The scope of each cookie is limited to a set of paths, controlled by the Path attribute.
- /// If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value.
- /// </summary>
- public string Path { get; private set; }
- /// <summary>
- /// The Secure attribute limits the scope of the cookie to "secure" channels (where "secure" is defined by the user agent).
- /// When a cookie has the Secure attribute, the user agent will include the cookie in an HTTP request only if the request is
- /// transmitted over a secure channel (typically HTTP over Transport Layer Security (TLS)).
- /// </summary>
- public bool IsSecure { get; private set; }
- /// <summary>
- /// The HttpOnly attribute limits the scope of the cookie to HTTP requests.
- /// In particular, the attribute instructs the user agent to omit the cookie when providing access to
- /// cookies via "non-HTTP" APIs (such as a web browser API that exposes cookies to scripts).
- /// </summary>
- public bool IsHttpOnly { get; private set; }
- #endregion
- #region Public Constructors
- public Cookie(string name, string value)
- :this(name, value, "/", string.Empty)
- {}
- public Cookie(string name, string value, string path)
- : this(name, value, path, string.Empty)
- {}
- public Cookie(string name, string value, string path, string domain)
- :this() // call the parameter-less constructor to set default values
- {
- this.Name = name;
- this.Value = value;
- this.Path = path;
- this.Domain = domain;
- }
- public Cookie(Uri uri, string name, string value, DateTime expires, bool isSession = true)
- :this(name, value, uri.AbsolutePath, uri.Host)
- {
- this.Expires = expires;
- this.IsSession = isSession;
- this.Date = DateTime.UtcNow;
- }
- public Cookie(Uri uri, string name, string value, long maxAge = -1, bool isSession = true)
- :this(name, value, uri.AbsolutePath, uri.Host)
- {
- this.MaxAge = maxAge;
- this.IsSession = isSession;
- this.Date = DateTime.UtcNow;
- }
- #endregion
- internal Cookie()
- {
- // If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie
- // until "the current session is over" (as defined by the user agent).
- IsSession = true;
- MaxAge = -1;
- LastAccess = DateTime.UtcNow;
- }
- public bool WillExpireInTheFuture()
- {
- // No Expires or Max-Age value sent from the server, we will fake the return value so we will not delete the newly came Cookie
- if (IsSession)
- return true;
- // If a cookie has both the Max-Age and the Expires attribute, the Max-Age attribute has precedence and controls the expiration date of the cookie.
- return MaxAge != -1 ?
- Math.Max(0, (long)(DateTime.UtcNow - Date).TotalSeconds) < MaxAge :
- Expires > DateTime.UtcNow;
- }
- /// <summary>
- /// Guess the storage size of the cookie.
- /// </summary>
- /// <returns></returns>
- public uint GuessSize()
- {
- return (uint)((Name != null ? Name.Length * sizeof(char) : 0) +
- (Value != null ? Value.Length * sizeof(char) : 0) +
- (Domain != null ? Domain.Length * sizeof(char) : 0) +
- (Path != null ? Path.Length * sizeof(char) : 0) +
- (sizeof(long) * 4) +
- (sizeof(bool) * 3));
- }
- public static Cookie Parse(string header, Uri defaultDomain)
- {
- Cookie cookie = new Cookie();
- try
- {
- var kvps = ParseCookieHeader(header);
- foreach (var kvp in kvps)
- {
- switch (kvp.Key.ToLowerInvariant())
- {
- case "path":
- // If the attribute-value is empty or if the first character of the attribute-value is not %x2F ("/"):
- // Let cookie-path be the default-path.
- cookie.Path = string.IsNullOrEmpty(kvp.Value) || !kvp.Value.StartsWith("/") ? "/" : cookie.Path = kvp.Value;
- break;
- case "domain":
- // If the attribute-value is empty, the behavior is undefined. However, the user agent SHOULD ignore the cookie-av entirely.
- if (string.IsNullOrEmpty(kvp.Value))
- return null;
- // If the first character of the attribute-value string is %x2E ("."):
- // Let cookie-domain be the attribute-value without the leading %x2E (".") character.
- cookie.Domain = kvp.Value.StartsWith(".") ? kvp.Value.Substring(1) : kvp.Value;
- break;
- case "expires":
- cookie.Expires = kvp.Value.ToDateTime(DateTime.FromBinary(0));
- cookie.IsSession = false;
- break;
- case "max-age":
- cookie.MaxAge = kvp.Value.ToInt64(-1);
- cookie.IsSession = false;
- break;
- case "secure":
- cookie.IsSecure = true;
- break;
- case "httponly":
- cookie.IsHttpOnly = true;
- break;
- default:
- cookie.Name = kvp.Key;
- cookie.Value = kvp.Value;
- break;
- }
- }
- // Some user agents provide users the option of preventing persistent storage of cookies across sessions.
- // When configured thusly, user agents MUST treat all received cookies as if the persistent-flag were set to false.
- if (HTTPManager.EnablePrivateBrowsing)
- cookie.IsSession = true;
- // http://tools.ietf.org/html/rfc6265#section-4.1.2.3
- // WARNING: Some existing user agents treat an absent Domain attribute as if the Domain attribute were present and contained the current host name.
- // For example, if example.com returns a SaveLocal-Cookie header without a Domain attribute, these user agents will erroneously send the cookie to www.example.com as well.
- if (string.IsNullOrEmpty(cookie.Domain))
- cookie.Domain = defaultDomain.Host;
- // http://tools.ietf.org/html/rfc6265#section-5.3 section 7:
- // If the cookie-attribute-list contains an attribute with an attribute-name of "Path",
- // set the cookie's path to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Path".
- // __Otherwise, set the cookie's path to the default-path of the request-uri.__
- if (string.IsNullOrEmpty(cookie.Path))
- cookie.Path = defaultDomain.AbsolutePath;
- cookie.Date = cookie.LastAccess = DateTime.UtcNow;
- }
- catch
- {
- }
- return cookie;
- }
- #region Save & Load
- internal void SaveTo(BinaryWriter stream)
- {
- stream.Write(Version);
- stream.Write(Name ?? string.Empty);
- stream.Write(Value ?? string.Empty);
- stream.Write(Date.ToBinary());
- stream.Write(LastAccess.ToBinary());
- stream.Write(Expires.ToBinary());
- stream.Write(MaxAge);
- stream.Write(IsSession);
- stream.Write(Domain ?? string.Empty);
- stream.Write(Path ?? string.Empty);
- stream.Write(IsSecure);
- stream.Write(IsHttpOnly);
- }
- internal void LoadFrom(BinaryReader stream)
- {
- /*int version = */stream.ReadInt32();
- this.Name = stream.ReadString();
- this.Value = stream.ReadString();
- this.Date = DateTime.FromBinary(stream.ReadInt64());
- this.LastAccess = DateTime.FromBinary(stream.ReadInt64());
- this.Expires = DateTime.FromBinary(stream.ReadInt64());
- this.MaxAge = stream.ReadInt64();
- this.IsSession = stream.ReadBoolean();
- this.Domain = stream.ReadString();
- this.Path = stream.ReadString();
- this.IsSecure = stream.ReadBoolean();
- this.IsHttpOnly = stream.ReadBoolean();
- }
- #endregion
- #region Overrides and new Equals function
- public override string ToString()
- {
- return string.Concat(this.Name, "=", this.Value);
- }
- public override bool Equals(object obj)
- {
- if (obj == null)
- return false;
- return this.Equals(obj as Cookie);
- }
- public bool Equals(Cookie cookie)
- {
- if (cookie == null)
- return false;
- if (Object.ReferenceEquals(this, cookie))
- return true;
- return this.Name.Equals(cookie.Name, StringComparison.Ordinal) &&
- ((this.Domain == null && cookie.Domain == null) || this.Domain.Equals(cookie.Domain, StringComparison.Ordinal)) &&
- ((this.Path == null && cookie.Path == null) || this.Path.Equals(cookie.Path, StringComparison.Ordinal));
- }
- public override int GetHashCode()
- {
- return this.ToString().GetHashCode();
- }
- #endregion
- #region Private Helper Functions
- private static string ReadValue(string str, ref int pos)
- {
- string result = string.Empty;
- if (str == null)
- return result;
- return str.Read(ref pos, ';');
- }
- private static List<HeaderValue> ParseCookieHeader(string str)
- {
- List<HeaderValue> result = new List<HeaderValue>();
- if (str == null)
- return result;
- int idx = 0;
- // process the rest of the text
- while (idx < str.Length)
- {
- // Read key
- string key = str.Read(ref idx, (ch) => ch != '=' && ch != ';').Trim();
- HeaderValue qp = new HeaderValue(key);
- if (idx < str.Length && str[idx - 1] == '=')
- qp.Value = ReadValue(str, ref idx);
- result.Add(qp);
- }
- return result;
- }
- #endregion
- #region IComparable<Cookie> implementation
- public int CompareTo(Cookie other)
- {
- return this.LastAccess.CompareTo(other.LastAccess);
- }
- #endregion
- }
- }
- #endif
|