//! HTTP cookie parsing and cookie jar management. //! //! This crates provides the [`Cookie`](struct.Cookie.html) type, which directly //! maps to an HTTP cookie, and the [`CookieJar`](struct.CookieJar.html) type, //! which allows for simple management of many cookies as well as encryption and //! signing of cookies for session management. //! //! # Usage //! //! Add the following to the `[dependencies]` section of your `Cargo.toml`: //! //! ```ignore //! cookie = "0.12" //! ``` //! //! Then add the following line to your crate root: //! //! ```ignore //! extern crate cookie; //! ``` //! //! # Features //! //! This crates can be configured at compile-time through the following Cargo //! features: //! //! //! * **secure** (disabled by default) //! //! Enables signed and private (signed + encrypted) cookie jars. //! //! When this feature is enabled, the //! [signed](struct.CookieJar.html#method.signed) and //! [private](struct.CookieJar.html#method.private) method of `CookieJar` and //! [`SignedJar`](struct.SignedJar.html) and //! [`PrivateJar`](struct.PrivateJar.html) structures are available. The jars //! act as "children jars", allowing for easy retrieval and addition of signed //! and/or encrypted cookies to a cookie jar. When this feature is disabled, //! none of the types are available. //! //! * **percent-encode** (disabled by default) //! //! Enables percent encoding and decoding of names and values in cookies. //! //! When this feature is enabled, the //! [encoded](struct.Cookie.html#method.encoded) and //! [`parse_encoded`](struct.Cookie.html#method.parse_encoded) methods of //! `Cookie` become available. The `encoded` method returns a wrapper around a //! `Cookie` whose `Display` implementation percent-encodes the name and value //! of the cookie. The `parse_encoded` method percent-decodes the name and //! value of a `Cookie` during parsing. When this feature is disabled, the //! `encoded` and `parse_encoded` methods are not available. //! //! You can enable features via the `Cargo.toml` file: //! //! ```ignore //! [dependencies.cookie] //! features = ["secure", "percent-encode"] //! ``` #![doc(html_root_url = "https://docs.rs/cookie/0.12")] #![deny(missing_docs)] #[cfg(feature = "percent-encode")] extern crate url; extern crate time; mod builder; mod parse; mod jar; mod delta; mod draft; #[cfg(feature = "secure")] #[macro_use] mod secure; #[cfg(feature = "secure")] pub use secure::*; use std::borrow::Cow; use std::fmt; use std::str::FromStr; #[allow(unused_imports, deprecated)] use std::ascii::AsciiExt; #[cfg(feature = "percent-encode")] use url::percent_encoding::{USERINFO_ENCODE_SET, percent_encode}; use time::{Tm, Duration}; use parse::parse_cookie; pub use parse::ParseError; pub use builder::CookieBuilder; pub use jar::{CookieJar, Delta, Iter}; pub use draft::*; #[derive(Debug, Clone)] enum CookieStr { /// An string derived from indexes (start, end). Indexed(usize, usize), /// A string derived from a concrete string. Concrete(Cow<'static, str>), } impl CookieStr { /// Retrieves the string `self` corresponds to. If `self` is derived from /// indexes, the corresponding subslice of `string` is returned. Otherwise, /// the concrete string is returned. /// /// # Panics /// /// Panics if `self` is an indexed string and `string` is None. fn to_str<'s>(&'s self, string: Option<&'s Cow>) -> &'s str { match *self { CookieStr::Indexed(i, j) => { let s = string.expect("`Some` base string must exist when \ converting indexed str to str! (This is a module invariant.)"); &s[i..j] }, CookieStr::Concrete(ref cstr) => &*cstr, } } fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> { match *self { CookieStr::Indexed(i, j) => { match *string { Cow::Borrowed(s) => Some(&s[i..j]), Cow::Owned(_) => None, } }, CookieStr::Concrete(_) => None, } } } /// Representation of an HTTP cookie. /// /// # Constructing a `Cookie` /// /// To construct a cookie with only a name/value, use the [new](#method.new) /// method: /// /// ```rust /// use cookie::Cookie; /// /// let cookie = Cookie::new("name", "value"); /// assert_eq!(&cookie.to_string(), "name=value"); /// ``` /// /// To construct more elaborate cookies, use the [build](#method.build) method /// and [`CookieBuilder`](struct.CookieBuilder.html) methods: /// /// ```rust /// use cookie::Cookie; /// /// let cookie = Cookie::build("name", "value") /// .domain("www.rust-lang.org") /// .path("/") /// .secure(true) /// .http_only(true) /// .finish(); /// ``` #[derive(Debug, Clone)] pub struct Cookie<'c> { /// Storage for the cookie string. Only used if this structure was derived /// from a string that was subsequently parsed. cookie_string: Option>, /// The cookie's name. name: CookieStr, /// The cookie's value. value: CookieStr, /// The cookie's expiration, if any. expires: Option, /// The cookie's maximum age, if any. max_age: Option, /// The cookie's domain, if any. domain: Option, /// The cookie's path domain, if any. path: Option, /// Whether this cookie was marked Secure. secure: Option, /// Whether this cookie was marked HttpOnly. http_only: Option, /// The draft `SameSite` attribute. same_site: Option, } impl Cookie<'static> { /// Creates a new `Cookie` with the given name and value. /// /// # Example /// /// ```rust /// use cookie::Cookie; /// /// let cookie = Cookie::new("name", "value"); /// assert_eq!(cookie.name_value(), ("name", "value")); /// ``` pub fn new(name: N, value: V) -> Cookie<'static> where N: Into>, V: Into> { Cookie { cookie_string: None, name: CookieStr::Concrete(name.into()), value: CookieStr::Concrete(value.into()), expires: None, max_age: None, domain: None, path: None, secure: None, http_only: None, same_site: None, } } /// Creates a new `Cookie` with the given name and an empty value. /// /// # Example /// /// ```rust /// use cookie::Cookie; /// /// let cookie = Cookie::named("name"); /// assert_eq!(cookie.name(), "name"); /// assert!(cookie.value().is_empty()); /// ``` pub fn named(name: N) -> Cookie<'static> where N: Into> { Cookie::new(name, "") } /// Creates a new `CookieBuilder` instance from the given key and value /// strings. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::build("foo", "bar").finish(); /// assert_eq!(c.name_value(), ("foo", "bar")); /// ``` pub fn build(name: N, value: V) -> CookieBuilder where N: Into>, V: Into> { CookieBuilder::new(name, value) } } impl<'c> Cookie<'c> { /// Parses a `Cookie` from the given HTTP cookie header value string. Does /// not perform any percent-decoding. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap(); /// assert_eq!(c.name_value(), ("foo", "bar%20baz")); /// assert_eq!(c.http_only(), Some(true)); /// ``` pub fn parse(s: S) -> Result, ParseError> where S: Into> { parse_cookie(s, false) } /// Parses a `Cookie` from the given HTTP cookie header value string where /// the name and value fields are percent-encoded. Percent-decodes the /// name/value fields. /// /// This API requires the `percent-encode` feature to be enabled on this /// crate. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap(); /// assert_eq!(c.name_value(), ("foo", "bar baz")); /// assert_eq!(c.http_only(), Some(true)); /// ``` #[cfg(feature = "percent-encode")] pub fn parse_encoded(s: S) -> Result, ParseError> where S: Into> { parse_cookie(s, true) } /// Wraps `self` in an `EncodedCookie`: a cost-free wrapper around `Cookie` /// whose `Display` implementation percent-encodes the name and value of the /// wrapped `Cookie`. /// /// This method is only available when the `percent-encode` feature is /// enabled. /// /// # Example /// /// ```rust /// use cookie::Cookie; /// /// let mut c = Cookie::new("my name", "this; value?"); /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); /// ``` #[cfg(feature = "percent-encode")] pub fn encoded<'a>(&'a self) -> EncodedCookie<'a, 'c> { EncodedCookie(self) } /// Converts `self` into a `Cookie` with a static lifetime. This method /// results in at most one allocation. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::new("a", "b"); /// let owned_cookie = c.into_owned(); /// assert_eq!(owned_cookie.name_value(), ("a", "b")); /// ``` pub fn into_owned(self) -> Cookie<'static> { Cookie { cookie_string: self.cookie_string.map(|s| s.into_owned().into()), name: self.name, value: self.value, expires: self.expires, max_age: self.max_age, domain: self.domain, path: self.path, secure: self.secure, http_only: self.http_only, same_site: self.same_site, } } /// Returns the name of `self`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::new("name", "value"); /// assert_eq!(c.name(), "name"); /// ``` #[inline] pub fn name(&self) -> &str { self.name.to_str(self.cookie_string.as_ref()) } /// Returns the value of `self`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::new("name", "value"); /// assert_eq!(c.value(), "value"); /// ``` #[inline] pub fn value(&self) -> &str { self.value.to_str(self.cookie_string.as_ref()) } /// Returns the name and value of `self` as a tuple of `(name, value)`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::new("name", "value"); /// assert_eq!(c.name_value(), ("name", "value")); /// ``` #[inline] pub fn name_value(&self) -> (&str, &str) { (self.name(), self.value()) } /// Returns whether this cookie was marked `HttpOnly` or not. Returns /// `Some(true)` when the cookie was explicitly set (manually or parsed) as /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`, /// and `None` otherwise. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value; httponly").unwrap(); /// assert_eq!(c.http_only(), Some(true)); /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.http_only(), None); /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.http_only(), None); /// /// // An explicitly set "false" value. /// c.set_http_only(false); /// assert_eq!(c.http_only(), Some(false)); /// /// // An explicitly set "true" value. /// c.set_http_only(true); /// assert_eq!(c.http_only(), Some(true)); /// ``` #[inline] pub fn http_only(&self) -> Option { self.http_only } /// Returns whether this cookie was marked `Secure` or not. Returns /// `Some(true)` when the cookie was explicitly set (manually or parsed) as /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and /// `None` otherwise. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value; Secure").unwrap(); /// assert_eq!(c.secure(), Some(true)); /// /// let mut c = Cookie::parse("name=value").unwrap(); /// assert_eq!(c.secure(), None); /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.secure(), None); /// /// // An explicitly set "false" value. /// c.set_secure(false); /// assert_eq!(c.secure(), Some(false)); /// /// // An explicitly set "true" value. /// c.set_secure(true); /// assert_eq!(c.secure(), Some(true)); /// ``` #[inline] pub fn secure(&self) -> Option { self.secure } /// Returns the `SameSite` attribute of this cookie if one was specified. /// /// # Example /// /// ``` /// use cookie::{Cookie, SameSite}; /// /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap(); /// assert_eq!(c.same_site(), Some(SameSite::Lax)); /// ``` #[inline] pub fn same_site(&self) -> Option { self.same_site } /// Returns the specified max-age of the cookie if one was specified. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value").unwrap(); /// assert_eq!(c.max_age(), None); /// /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap(); /// assert_eq!(c.max_age().map(|age| age.num_hours()), Some(1)); /// ``` #[inline] pub fn max_age(&self) -> Option { self.max_age } /// Returns the `Path` of the cookie if one was specified. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value").unwrap(); /// assert_eq!(c.path(), None); /// /// let c = Cookie::parse("name=value; Path=/").unwrap(); /// assert_eq!(c.path(), Some("/")); /// /// let c = Cookie::parse("name=value; path=/sub").unwrap(); /// assert_eq!(c.path(), Some("/sub")); /// ``` #[inline] pub fn path(&self) -> Option<&str> { match self.path { Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), None => None, } } /// Returns the `Domain` of the cookie if one was specified. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value").unwrap(); /// assert_eq!(c.domain(), None); /// /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap(); /// assert_eq!(c.domain(), Some("crates.io")); /// ``` #[inline] pub fn domain(&self) -> Option<&str> { match self.domain { Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), None => None, } } /// Returns the `Expires` time of the cookie if one was specified. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let c = Cookie::parse("name=value").unwrap(); /// assert_eq!(c.expires(), None); /// /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT"; /// let cookie_str = format!("name=value; Expires={}", expire_time); /// let c = Cookie::parse(cookie_str).unwrap(); /// assert_eq!(c.expires().map(|t| t.tm_year), Some(117)); /// ``` #[inline] pub fn expires(&self) -> Option { self.expires } /// Sets the name of `self` to `name`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.name(), "name"); /// /// c.set_name("foo"); /// assert_eq!(c.name(), "foo"); /// ``` pub fn set_name>>(&mut self, name: N) { self.name = CookieStr::Concrete(name.into()) } /// Sets the value of `self` to `value`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.value(), "value"); /// /// c.set_value("bar"); /// assert_eq!(c.value(), "bar"); /// ``` pub fn set_value>>(&mut self, value: V) { self.value = CookieStr::Concrete(value.into()) } /// Sets the value of `http_only` in `self` to `value`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.http_only(), None); /// /// c.set_http_only(true); /// assert_eq!(c.http_only(), Some(true)); /// ``` #[inline] pub fn set_http_only(&mut self, value: bool) { self.http_only = Some(value); } /// Sets the value of `secure` in `self` to `value`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.secure(), None); /// /// c.set_secure(true); /// assert_eq!(c.secure(), Some(true)); /// ``` #[inline] pub fn set_secure(&mut self, value: bool) { self.secure = Some(value); } /// Sets the value of `same_site` in `self` to `value`. /// /// # Example /// /// ``` /// use cookie::{Cookie, SameSite}; /// /// let mut c = Cookie::new("name", "value"); /// assert!(c.same_site().is_none()); /// /// c.set_same_site(SameSite::Strict); /// assert_eq!(c.same_site(), Some(SameSite::Strict)); /// ``` #[inline] pub fn set_same_site(&mut self, value: SameSite) { self.same_site = Some(value); } /// Sets the value of `max_age` in `self` to `value`. /// /// # Example /// /// ``` /// # extern crate cookie; /// extern crate time; /// /// use cookie::Cookie; /// use time::Duration; /// /// # fn main() { /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.max_age(), None); /// /// c.set_max_age(Duration::hours(10)); /// assert_eq!(c.max_age(), Some(Duration::hours(10))); /// # } /// ``` #[inline] pub fn set_max_age(&mut self, value: Duration) { self.max_age = Some(value); } /// Sets the `path` of `self` to `path`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.path(), None); /// /// c.set_path("/"); /// assert_eq!(c.path(), Some("/")); /// ``` pub fn set_path>>(&mut self, path: P) { self.path = Some(CookieStr::Concrete(path.into())); } /// Sets the `domain` of `self` to `domain`. /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.domain(), None); /// /// c.set_domain("rust-lang.org"); /// assert_eq!(c.domain(), Some("rust-lang.org")); /// ``` pub fn set_domain>>(&mut self, domain: D) { self.domain = Some(CookieStr::Concrete(domain.into())); } /// Sets the expires field of `self` to `time`. /// /// # Example /// /// ``` /// # extern crate cookie; /// extern crate time; /// /// use cookie::Cookie; /// /// # fn main() { /// let mut c = Cookie::new("name", "value"); /// assert_eq!(c.expires(), None); /// /// let mut now = time::now(); /// now.tm_year += 1; /// /// c.set_expires(now); /// assert!(c.expires().is_some()) /// # } /// ``` #[inline] pub fn set_expires(&mut self, time: Tm) { self.expires = Some(time); } /// Makes `self` a "permanent" cookie by extending its expiration and max /// age 20 years into the future. /// /// # Example /// /// ```rust /// # extern crate cookie; /// extern crate time; /// /// use cookie::Cookie; /// use time::Duration; /// /// # fn main() { /// let mut c = Cookie::new("foo", "bar"); /// assert!(c.expires().is_none()); /// assert!(c.max_age().is_none()); /// /// c.make_permanent(); /// assert!(c.expires().is_some()); /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); /// # } /// ``` pub fn make_permanent(&mut self) { let twenty_years = Duration::days(365 * 20); self.set_max_age(twenty_years); self.set_expires(time::now() + twenty_years); } fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(true) = self.http_only() { write!(f, "; HttpOnly")?; } if let Some(true) = self.secure() { write!(f, "; Secure")?; } if let Some(same_site) = self.same_site() { if !same_site.is_none() { write!(f, "; SameSite={}", same_site)?; } } if let Some(path) = self.path() { write!(f, "; Path={}", path)?; } if let Some(domain) = self.domain() { write!(f, "; Domain={}", domain)?; } if let Some(max_age) = self.max_age() { write!(f, "; Max-Age={}", max_age.num_seconds())?; } if let Some(time) = self.expires() { write!(f, "; Expires={}", time.rfc822())?; } Ok(()) } /// Returns the name of `self` as a string slice of the raw string `self` /// was originally parsed from. If `self` was not originally parsed from a /// raw string, returns `None`. /// /// This method differs from [name](#method.name) in that it returns a /// string with the same lifetime as the originally parsed string. This /// lifetime may outlive `self`. If a longer lifetime is not required, or /// you're unsure if you need a longer lifetime, use [name](#method.name). /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let cookie_string = format!("{}={}", "foo", "bar"); /// /// // `c` will be dropped at the end of the scope, but `name` will live on /// let name = { /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); /// c.name_raw() /// }; /// /// assert_eq!(name, Some("foo")); /// ``` #[inline] pub fn name_raw(&self) -> Option<&'c str> { self.cookie_string.as_ref() .and_then(|s| self.name.to_raw_str(s)) } /// Returns the value of `self` as a string slice of the raw string `self` /// was originally parsed from. If `self` was not originally parsed from a /// raw string, returns `None`. /// /// This method differs from [value](#method.value) in that it returns a /// string with the same lifetime as the originally parsed string. This /// lifetime may outlive `self`. If a longer lifetime is not required, or /// you're unsure if you need a longer lifetime, use [value](#method.value). /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let cookie_string = format!("{}={}", "foo", "bar"); /// /// // `c` will be dropped at the end of the scope, but `value` will live on /// let value = { /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); /// c.value_raw() /// }; /// /// assert_eq!(value, Some("bar")); /// ``` #[inline] pub fn value_raw(&self) -> Option<&'c str> { self.cookie_string.as_ref() .and_then(|s| self.value.to_raw_str(s)) } /// Returns the `Path` of `self` as a string slice of the raw string `self` /// was originally parsed from. If `self` was not originally parsed from a /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has /// changed since parsing, returns `None`. /// /// This method differs from [path](#method.path) in that it returns a /// string with the same lifetime as the originally parsed string. This /// lifetime may outlive `self`. If a longer lifetime is not required, or /// you're unsure if you need a longer lifetime, use [path](#method.path). /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let cookie_string = format!("{}={}; Path=/", "foo", "bar"); /// /// // `c` will be dropped at the end of the scope, but `path` will live on /// let path = { /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); /// c.path_raw() /// }; /// /// assert_eq!(path, Some("/")); /// ``` #[inline] pub fn path_raw(&self) -> Option<&'c str> { match (self.path.as_ref(), self.cookie_string.as_ref()) { (Some(path), Some(string)) => path.to_raw_str(string), _ => None, } } /// Returns the `Domain` of `self` as a string slice of the raw string /// `self` was originally parsed from. If `self` was not originally parsed /// from a raw string, or if `self` doesn't contain a `Domain`, or if the /// `Domain` has changed since parsing, returns `None`. /// /// This method differs from [domain](#method.domain) in that it returns a /// string with the same lifetime as the originally parsed string. This /// lifetime may outlive `self` struct. If a longer lifetime is not /// required, or you're unsure if you need a longer lifetime, use /// [domain](#method.domain). /// /// # Example /// /// ``` /// use cookie::Cookie; /// /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar"); /// /// //`c` will be dropped at the end of the scope, but `domain` will live on /// let domain = { /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); /// c.domain_raw() /// }; /// /// assert_eq!(domain, Some("crates.io")); /// ``` #[inline] pub fn domain_raw(&self) -> Option<&'c str> { match (self.domain.as_ref(), self.cookie_string.as_ref()) { (Some(domain), Some(string)) => domain.to_raw_str(string), _ => None, } } } /// Wrapper around `Cookie` whose `Display` implementation percent-encodes the /// cookie's name and value. /// /// A value of this type can be obtained via the /// [encoded](struct.Cookie.html#method.encoded) method on /// [Cookie](struct.Cookie.html). This type should only be used for its /// `Display` implementation. /// /// This type is only available when the `percent-encode` feature is enabled. /// /// # Example /// /// ```rust /// use cookie::Cookie; /// /// let mut c = Cookie::new("my name", "this; value?"); /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); /// ``` #[cfg(feature = "percent-encode")] pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); #[cfg(feature = "percent-encode")] impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Percent-encode the name and value. let name = percent_encode(self.0.name().as_bytes(), USERINFO_ENCODE_SET); let value = percent_encode(self.0.value().as_bytes(), USERINFO_ENCODE_SET); // Write out the name/value pair and the cookie's parameters. write!(f, "{}={}", name, value)?; self.0.fmt_parameters(f) } } impl<'c> fmt::Display for Cookie<'c> { /// Formats the cookie `self` as a `Set-Cookie` header value. /// /// # Example /// /// ```rust /// use cookie::Cookie; /// /// let mut cookie = Cookie::build("foo", "bar") /// .path("/") /// .finish(); /// /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); /// ``` fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}={}", self.name(), self.value())?; self.fmt_parameters(f) } } impl FromStr for Cookie<'static> { type Err = ParseError; fn from_str(s: &str) -> Result, ParseError> { Cookie::parse(s).map(|c| c.into_owned()) } } impl<'a, 'b> PartialEq> for Cookie<'a> { fn eq(&self, other: &Cookie<'b>) -> bool { let so_far_so_good = self.name() == other.name() && self.value() == other.value() && self.http_only() == other.http_only() && self.secure() == other.secure() && self.max_age() == other.max_age() && self.expires() == other.expires(); if !so_far_so_good { return false; } match (self.path(), other.path()) { (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} (None, None) => {} _ => return false, }; match (self.domain(), other.domain()) { (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} (None, None) => {} _ => return false, }; true } } #[cfg(test)] mod tests { use ::{Cookie, SameSite}; use ::time::{strptime, Duration}; #[test] fn format() { let cookie = Cookie::new("foo", "bar"); assert_eq!(&cookie.to_string(), "foo=bar"); let cookie = Cookie::build("foo", "bar") .http_only(true).finish(); assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); let cookie = Cookie::build("foo", "bar") .max_age(Duration::seconds(10)).finish(); assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); let cookie = Cookie::build("foo", "bar") .secure(true).finish(); assert_eq!(&cookie.to_string(), "foo=bar; Secure"); let cookie = Cookie::build("foo", "bar") .path("/").finish(); assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); let cookie = Cookie::build("foo", "bar") .domain("www.rust-lang.org").finish(); assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org"); let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap(); let cookie = Cookie::build("foo", "bar") .expires(expires).finish(); assert_eq!(&cookie.to_string(), "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT"); let cookie = Cookie::build("foo", "bar") .same_site(SameSite::Strict).finish(); assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict"); let cookie = Cookie::build("foo", "bar") .same_site(SameSite::Lax).finish(); assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax"); let cookie = Cookie::build("foo", "bar") .same_site(SameSite::None).finish(); assert_eq!(&cookie.to_string(), "foo=bar"); } #[test] fn cookie_string_long_lifetimes() { let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); let (name, value, path, domain) = { // Create a cookie passing a slice let c = Cookie::parse(cookie_string.as_str()).unwrap(); (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) }; assert_eq!(name, Some("bar")); assert_eq!(value, Some("baz")); assert_eq!(path, Some("/subdir")); assert_eq!(domain, Some("crates.io")); } #[test] fn owned_cookie_string() { let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); let (name, value, path, domain) = { // Create a cookie passing an owned string let c = Cookie::parse(cookie_string).unwrap(); (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) }; assert_eq!(name, None); assert_eq!(value, None); assert_eq!(path, None); assert_eq!(domain, None); } #[test] fn owned_cookie_struct() { let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io"; let (name, value, path, domain) = { // Create an owned cookie let c = Cookie::parse(cookie_string).unwrap().into_owned(); (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) }; assert_eq!(name, None); assert_eq!(value, None); assert_eq!(path, None); assert_eq!(domain, None); } #[test] #[cfg(feature = "percent-encode")] fn format_encoded() { let cookie = Cookie::build("foo !?=", "bar;; a").finish(); let cookie_str = cookie.encoded().to_string(); assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a"); let cookie = Cookie::parse_encoded(cookie_str).unwrap(); assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a")); } }