blob: 8ce2ff7d56a9a68f8e00f96c103a8d84602f523a [file] [log] [blame] [edit]
// <copyright file="Cookie.cs" company="WebDriver Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using Newtonsoft.Json;
using OpenQA.Selenium.Internal;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace OpenQA.Selenium
{
/// <summary>
/// Represents a cookie in the browser.
/// </summary>
[Serializable]
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class Cookie
{
private string cookieName;
private string cookieValue;
private string cookiePath;
private string cookieDomain;
private string sameSite;
private bool isHttpOnly;
private bool secure;
private DateTime? cookieExpiry;
private readonly string[] sameSiteValues = { "Strict", "Lax", "None" };
/// <summary>
/// Initializes a new instance of the <see cref="Cookie"/> class with a specific name and value.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <exception cref="ArgumentException">If the name is <see langword="null"/> or an empty string,
/// or if it contains a semi-colon.</exception>
/// <exception cref="ArgumentNullException">If the value is <see langword="null"/>.</exception>
public Cookie(string name, string value)
: this(name, value, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Cookie"/> class with a specific name,
/// value, and path.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <param name="path">The path of the cookie.</param>
/// <exception cref="ArgumentException">If the name is <see langword="null"/> or an empty string,
/// or if it contains a semi-colon.</exception>
/// <exception cref="ArgumentNullException">If the value is <see langword="null"/>.</exception>
public Cookie(string name, string value, string path)
: this(name, value, path, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Cookie"/> class with a specific name,
/// value, path and expiration date.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <param name="path">The path of the cookie.</param>
/// <param name="expiry">The expiration date of the cookie.</param>
/// <exception cref="ArgumentException">If the name is <see langword="null"/> or an empty string,
/// or if it contains a semi-colon.</exception>
/// <exception cref="ArgumentNullException">If the value is <see langword="null"/>.</exception>
public Cookie(string name, string value, string path, DateTime? expiry)
: this(name, value, null, path, expiry)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Cookie"/> class with a specific name,
/// value, domain, path and expiration date.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <param name="domain">The domain of the cookie.</param>
/// <param name="path">The path of the cookie.</param>
/// <param name="expiry">The expiration date of the cookie.</param>
/// <exception cref="ArgumentException">If the name is <see langword="null"/> or an empty string,
/// or if it contains a semi-colon.</exception>
/// <exception cref="ArgumentNullException">If the value is <see langword="null"/>.</exception>
public Cookie(string name, string value, string domain, string path, DateTime? expiry)
: this(name, value, domain, path, expiry, false, false, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReturnedCookie"/> class with a specific name,
/// value, domain, path and expiration date.
/// </summary>
/// <param name="name">The name of the cookie.</param>
/// <param name="value">The value of the cookie.</param>
/// <param name="domain">The domain of the cookie.</param>
/// <param name="path">The path of the cookie.</param>
/// <param name="expiry">The expiration date of the cookie.</param>
/// <param name="secure"><see langword="true"/> if the cookie is secure; otherwise <see langword="false"/></param>
/// <param name="isHttpOnly"><see langword="true"/> if the cookie is an HTTP-only cookie; otherwise <see langword="false"/></param>
/// <param name="sameSite">The SameSite value of cookie.</param>
/// <exception cref="ArgumentException">If the name and value are both an empty string,
/// if the name contains a semi-colon, or if same site value is not valid.</exception>
/// <exception cref="ArgumentNullException">If the name, value or currentUrl is <see langword="null"/>.</exception>
public Cookie(string name, string value, string domain, string path, DateTime? expiry, bool secure, bool isHttpOnly, string sameSite)
{
if (name == null)
{
throw new ArgumentNullException(nameof(value), "Cookie name cannot be null");
}
if (value == null)
{
throw new ArgumentNullException(nameof(value), "Cookie value cannot be null");
}
if (name == string.Empty && value == string.Empty)
{
throw new ArgumentException("Cookie name and value cannot both be empty string");
}
if (name.IndexOf(';') != -1)
{
throw new ArgumentException("Cookie names cannot contain a ';': " + name, nameof(name));
}
this.cookieName = name;
this.cookieValue = value;
if (!string.IsNullOrEmpty(path))
{
this.cookiePath = path;
}
this.cookieDomain = StripPort(domain);
if (expiry != null)
{
this.cookieExpiry = expiry;
}
this.isHttpOnly = isHttpOnly;
this.secure = secure;
if (!string.IsNullOrEmpty(sameSite))
{
if (!sameSiteValues.Contains(sameSite))
{
throw new ArgumentException("Invalid sameSite cookie value. It should either \"Lax\", \"Strict\" or \"None\" ", nameof(sameSite));
}
this.sameSite = sameSite;
}
}
/// <summary>
/// Gets the name of the cookie.
/// </summary>
[JsonProperty("name")]
public string Name
{
get { return this.cookieName; }
}
/// <summary>
/// Gets the value of the cookie.
/// </summary>
[JsonProperty("value")]
public string Value
{
get { return this.cookieValue; }
}
/// <summary>
/// Gets the domain of the cookie.
/// </summary>
[JsonProperty("domain", NullValueHandling = NullValueHandling.Ignore)]
public string Domain
{
get { return this.cookieDomain; }
}
/// <summary>
/// Gets the path of the cookie.
/// </summary>
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Path
{
get { return this.cookiePath; }
}
/// <summary>
/// Gets a value indicating whether the cookie is secure.
/// </summary>
[JsonProperty("secure")]
public virtual bool Secure
{
get { return this.secure; }
}
/// <summary>
/// Gets a value indicating whether the cookie is an HTTP-only cookie.
/// </summary>
[JsonProperty("httpOnly")]
public virtual bool IsHttpOnly
{
get { return this.isHttpOnly; }
}
/// <summary>
/// Gets the SameSite setting for the cookie.
/// </summary>
[JsonProperty("sameSite", NullValueHandling = NullValueHandling.Ignore)]
public virtual string SameSite
{
get { return this.sameSite; }
}
/// <summary>
/// Gets the expiration date of the cookie.
/// </summary>
public DateTime? Expiry
{
get { return this.cookieExpiry; }
}
/// <summary>
/// Gets the cookie expiration date in seconds from the defined zero date (01 January 1970 00:00:00 UTC).
/// </summary>
/// <remarks>This property only exists so that the JSON serializer can serialize a
/// cookie without resorting to a custom converter.</remarks>
[JsonProperty("expiry", NullValueHandling = NullValueHandling.Ignore)]
internal long? ExpirySeconds
{
get
{
if (this.cookieExpiry == null)
{
return null;
}
DateTime zeroDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
TimeSpan span = this.cookieExpiry.Value.ToUniversalTime().Subtract(zeroDate);
long totalSeconds = Convert.ToInt64(span.TotalSeconds);
return totalSeconds;
}
}
/// <summary>
/// Converts a Dictionary to a Cookie.
/// </summary>
/// <param name="rawCookie">The Dictionary object containing the cookie parameters.</param>
/// <returns>A <see cref="Cookie"/> object with the proper parameters set.</returns>
public static Cookie FromDictionary(Dictionary<string, object> rawCookie)
{
if (rawCookie == null)
{
throw new ArgumentNullException(nameof(rawCookie), "Dictionary cannot be null");
}
string name = rawCookie["name"].ToString();
string value = string.Empty;
if (rawCookie["value"] != null)
{
value = rawCookie["value"].ToString();
}
string path = "/";
if (rawCookie.ContainsKey("path") && rawCookie["path"] != null)
{
path = rawCookie["path"].ToString();
}
string domain = string.Empty;
if (rawCookie.ContainsKey("domain") && rawCookie["domain"] != null)
{
domain = rawCookie["domain"].ToString();
}
DateTime? expires = null;
if (rawCookie.ContainsKey("expiry") && rawCookie["expiry"] != null)
{
expires = ConvertExpirationTime(rawCookie["expiry"].ToString());
}
bool secure = false;
if (rawCookie.ContainsKey("secure") && rawCookie["secure"] != null)
{
secure = bool.Parse(rawCookie["secure"].ToString());
}
bool isHttpOnly = false;
if (rawCookie.ContainsKey("httpOnly") && rawCookie["httpOnly"] != null)
{
isHttpOnly = bool.Parse(rawCookie["httpOnly"].ToString());
}
string sameSite = null;
if (rawCookie.ContainsKey("sameSite") && rawCookie["sameSite"] != null)
{
sameSite = rawCookie["sameSite"].ToString();
}
return new ReturnedCookie(name, value, domain, path, expires, secure, isHttpOnly, sameSite);
}
/// <summary>
/// Creates and returns a string representation of the cookie.
/// </summary>
/// <returns>A string representation of the cookie.</returns>
public override string ToString()
{
return this.cookieName + "=" + this.cookieValue
+ (this.cookieExpiry == null ? string.Empty : "; expires=" + this.cookieExpiry.Value.ToUniversalTime().ToString("ddd MM dd yyyy hh:mm:ss UTC", CultureInfo.InvariantCulture))
+ (string.IsNullOrEmpty(this.cookiePath) ? string.Empty : "; path=" + this.cookiePath)
+ (string.IsNullOrEmpty(this.cookieDomain) ? string.Empty : "; domain=" + this.cookieDomain)
+ "; isHttpOnly= " + this.isHttpOnly + "; secure= " + this.secure + (string.IsNullOrEmpty(this.sameSite) ? string.Empty : "; sameSite=" + this.sameSite);
}
/// <summary>
/// Determines whether the specified <see cref="object">Object</see> is equal
/// to the current <see cref="object">Object</see>.
/// </summary>
/// <param name="obj">The <see cref="object">Object</see> to compare with the
/// current <see cref="object">Object</see>.</param>
/// <returns><see langword="true"/> if the specified <see cref="object">Object</see>
/// is equal to the current <see cref="object">Object</see>; otherwise,
/// <see langword="false"/>.</returns>
public override bool Equals(object obj)
{
// Two cookies are equal if the name and value match
Cookie cookie = obj as Cookie;
if (this == obj)
{
return true;
}
if (cookie == null)
{
return false;
}
if (!this.cookieName.Equals(cookie.cookieName))
{
return false;
}
return !(this.cookieValue != null ? !this.cookieValue.Equals(cookie.cookieValue) : cookie.Value != null);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>A hash code for the current <see cref="object">Object</see>.</returns>
public override int GetHashCode()
{
return this.cookieName.GetHashCode();
}
private static string StripPort(string domain)
{
return string.IsNullOrEmpty(domain) ? null : domain.Split(':')[0];
}
private static DateTime? ConvertExpirationTime(string expirationTime)
{
DateTime? expires = null;
double seconds = 0;
if (double.TryParse(expirationTime, NumberStyles.Number, CultureInfo.InvariantCulture, out seconds))
{
try
{
expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds).ToLocalTime();
}
catch (ArgumentOutOfRangeException)
{
expires = DateTime.MaxValue.ToLocalTime();
}
}
return expires;
}
}
}