#![doc(html_root_url = "https://docs.rs/cookie/0.6")]
#![allow(deprecated)]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#[cfg(feature = "percent-encode")]
extern crate url;
extern crate time;
mod builder;
mod jar;
mod parse;
use std::borrow::Cow;
use std::ascii::AsciiExt;
use std::fmt;
use std::str::FromStr;
#[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;
#[derive(Debug, Clone)]
enum CookieStr {
Indexed(usize, usize),
Concrete(Cow<'static, str>),
}
impl CookieStr {
fn is_indexed(&self) -> bool {
match *self {
CookieStr::Indexed(..) => true,
CookieStr::Concrete(..) => false,
}
}
fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str {
if self.is_indexed() && string.is_none() {
panic!("Cannot convert indexed str to str without base string!")
}
match *self {
CookieStr::Indexed(i, j) => &string.unwrap()[i..j],
CookieStr::Concrete(ref cstr) => &*cstr,
}
}
}
#[derive(Debug, Clone)]
pub struct Cookie<'c> {
cookie_string: Option<Cow<'c, str>>,
name: CookieStr,
value: CookieStr,
expires: Option<Tm>,
max_age: Option<Duration>,
domain: Option<CookieStr>,
path: Option<CookieStr>,
secure: bool,
http_only: bool,
}
impl Cookie<'static> {
pub fn new<N, V>(name: N, value: V) -> Cookie<'static>
where N: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>
{
Cookie {
cookie_string: None,
name: CookieStr::Concrete(name.into()),
value: CookieStr::Concrete(value.into()),
expires: None,
max_age: None,
domain: None,
path: None,
secure: false,
http_only: false,
}
}
pub fn build<N, V>(name: N, value: V) -> CookieBuilder
where N: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>
{
CookieBuilder::new(name, value)
}
}
impl<'c> Cookie<'c> {
pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
where S: Into<Cow<'c, str>>
{
parse_cookie(s, false)
}
#[cfg(feature = "percent-encode")]
pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
where S: Into<Cow<'c, str>>
{
parse_cookie(s, true)
}
#[cfg(feature = "percent-encode")]
pub fn encoded<'a>(&'a self) -> EncodedCookie<'a, 'c> {
EncodedCookie(self)
}
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,
}
}
#[inline]
pub fn name(&self) -> &str {
self.name.to_str(self.cookie_string.as_ref())
}
#[inline]
pub fn value(&self) -> &str {
self.value.to_str(self.cookie_string.as_ref())
}
#[inline]
pub fn name_value(&self) -> (&str, &str) {
(self.name(), self.value())
}
#[inline]
pub fn http_only(&self) -> bool {
self.http_only
}
#[inline]
pub fn secure(&self) -> bool {
self.secure
}
#[inline]
pub fn max_age(&self) -> Option<Duration> {
self.max_age
}
#[inline]
pub fn path(&self) -> Option<&str> {
match self.path {
Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
None => None,
}
}
#[inline]
pub fn domain(&self) -> Option<&str> {
match self.domain {
Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
None => None,
}
}
#[inline]
pub fn expires(&self) -> Option<Tm> {
self.expires
}
pub fn set_name<N: Into<Cow<'static, str>>>(&mut self, name: N) {
self.name = CookieStr::Concrete(name.into())
}
pub fn set_value<V: Into<Cow<'static, str>>>(&mut self, value: V) {
self.value = CookieStr::Concrete(value.into())
}
#[inline]
pub fn set_http_only(&mut self, value: bool) {
self.http_only = value;
}
#[inline]
pub fn set_secure(&mut self, value: bool) {
self.secure = value;
}
#[inline]
pub fn set_max_age(&mut self, value: Duration) {
self.max_age = Some(value);
}
pub fn set_path<P: Into<Cow<'static, str>>>(&mut self, path: P) {
self.path = Some(CookieStr::Concrete(path.into()));
}
pub fn set_domain<D: Into<Cow<'static, str>>>(&mut self, domain: D) {
self.domain = Some(CookieStr::Concrete(domain.into()));
}
#[inline]
pub fn set_expires(&mut self, time: Tm) {
self.expires = Some(time);
}
fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.http_only() {
write!(f, "; HttpOnly")?;
}
if self.secure() {
write!(f, "; Secure")?;
}
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(())
}
}
#[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 {
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!(f, "{}={}", name, value)?;
self.0.fmt_parameters(f)
}
}
impl<'c> fmt::Display for Cookie<'c> {
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<Cookie<'static>, ParseError> {
Cookie::parse(s).map(|c| c.into_owned())
}
}
impl<'a, 'b> PartialEq<Cookie<'b>> 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;
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");
}
#[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"));
}
}