use std::fmt::{self, Formatter};
use std::io::{BufRead, Write};
use std::iter;
use std::ops::Deref;
use cookie::Cookie as RawCookie;
use log::debug;
use serde::de::{SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use url::Url;
use crate::cookie::Cookie;
use crate::cookie_domain::is_match as domain_match;
use crate::cookie_path::is_match as path_match;
use crate::utils::{is_http_scheme, is_secure};
use crate::CookieError;
#[cfg(feature = "preserve_order")]
use indexmap::IndexMap;
#[cfg(not(feature = "preserve_order"))]
use std::collections::HashMap;
#[cfg(feature = "preserve_order")]
type Map<K, V> = IndexMap<K, V>;
#[cfg(not(feature = "preserve_order"))]
type Map<K, V> = HashMap<K, V>;
type NameMap = Map<String, Cookie<'static>>;
type PathMap = Map<String, NameMap>;
type DomainMap = Map<String, PathMap>;
#[derive(PartialEq, Clone, Debug, Eq)]
pub enum StoreAction {
Inserted,
ExpiredExisting,
UpdatedExisting,
}
pub type StoreResult<T> = Result<T, crate::Error>;
pub type InsertResult = Result<StoreAction, CookieError>;
#[derive(Debug, Default, Clone)]
pub struct CookieStore {
cookies: DomainMap,
#[cfg(feature = "public_suffix")]
public_suffix_list: Option<publicsuffix::List>,
}
impl CookieStore {
#[deprecated(
since = "0.14.1",
note = "Please use the `get_request_values` function instead"
)]
pub fn get_request_cookies(&self, url: &Url) -> impl Iterator<Item = &RawCookie<'static>> {
self.matches(url).into_iter().map(|c| c.deref())
}
pub fn get_request_values(&self, url: &Url) -> impl Iterator<Item = (&str, &str)> {
self.matches(url).into_iter().map(|c| c.name_value())
}
pub fn store_response_cookies<I: Iterator<Item = RawCookie<'static>>>(
&mut self,
cookies: I,
url: &Url,
) {
for cookie in cookies {
if cookie.secure() != Some(true) || cfg!(feature = "log_secure_cookie_values") {
debug!("inserting Set-Cookie '{:?}'", cookie);
} else {
debug!("inserting secure cookie '{}'", cookie.name());
}
if let Err(e) = self.insert_raw(&cookie, url) {
debug!("unable to store Set-Cookie: {:?}", e);
}
}
}
#[cfg(feature = "public_suffix")]
pub fn with_suffix_list(self, psl: publicsuffix::List) -> CookieStore {
CookieStore {
cookies: self.cookies,
public_suffix_list: Some(psl),
}
}
pub fn contains(&self, domain: &str, path: &str, name: &str) -> bool {
self.get(domain, path, name).is_some()
}
pub fn contains_any(&self, domain: &str, path: &str, name: &str) -> bool {
self.get_any(domain, path, name).is_some()
}
pub fn get(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'_>> {
self.get_any(domain, path, name).and_then(|cookie| {
if cookie.is_expired() {
None
} else {
Some(cookie)
}
})
}
fn get_mut(&mut self, domain: &str, path: &str, name: &str) -> Option<&mut Cookie<'static>> {
self.get_mut_any(domain, path, name).and_then(|cookie| {
if cookie.is_expired() {
None
} else {
Some(cookie)
}
})
}
pub fn get_any(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'static>> {
self.cookies.get(domain).and_then(|domain_cookies| {
domain_cookies
.get(path)
.and_then(|path_cookies| path_cookies.get(name))
})
}
fn get_mut_any(
&mut self,
domain: &str,
path: &str,
name: &str,
) -> Option<&mut Cookie<'static>> {
self.cookies.get_mut(domain).and_then(|domain_cookies| {
domain_cookies
.get_mut(path)
.and_then(|path_cookies| path_cookies.get_mut(name))
})
}
pub fn remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>> {
#[cfg(not(feature = "preserve_order"))]
fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
where
K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash,
Q: std::cmp::Eq + std::hash::Hash + ?Sized,
{
map.remove(key)
}
#[cfg(feature = "preserve_order")]
fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
where
K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash,
Q: std::cmp::Eq + std::hash::Hash + ?Sized,
{
map.shift_remove(key)
}
let (removed, remove_domain) = match self.cookies.get_mut(domain) {
None => (None, false),
Some(domain_cookies) => {
let (removed, remove_path) = match domain_cookies.get_mut(path) {
None => (None, false),
Some(path_cookies) => {
let removed = map_remove(path_cookies, name);
(removed, path_cookies.is_empty())
}
};
if remove_path {
map_remove(domain_cookies, path);
(removed, domain_cookies.is_empty())
} else {
(removed, false)
}
}
};
if remove_domain {
map_remove(&mut self.cookies, domain);
}
removed
}
pub fn matches(&self, request_url: &Url) -> Vec<&Cookie<'static>> {
let cookies = self
.cookies
.iter()
.filter(|&(d, _)| domain_match(d, request_url))
.flat_map(|(_, dcs)| {
dcs.iter()
.filter(|&(p, _)| path_match(p, request_url))
.flat_map(|(_, pcs)| {
pcs.values()
.filter(|c| !c.is_expired() && c.matches(request_url))
})
});
match (!is_http_scheme(request_url), !is_secure(request_url)) {
(true, true) => cookies
.filter(|c| !c.http_only().unwrap_or(false) && !c.secure().unwrap_or(false))
.collect(),
(true, false) => cookies
.filter(|c| !c.http_only().unwrap_or(false))
.collect(),
(false, true) => cookies.filter(|c| !c.secure().unwrap_or(false)).collect(),
(false, false) => cookies.collect(),
}
}
pub fn parse(&mut self, cookie_str: &str, request_url: &Url) -> InsertResult {
Cookie::parse(cookie_str, request_url)
.and_then(|cookie| self.insert(cookie.into_owned(), request_url))
}
pub fn insert_raw(&mut self, cookie: &RawCookie<'_>, request_url: &Url) -> InsertResult {
Cookie::try_from_raw_cookie(cookie, request_url)
.and_then(|cookie| self.insert(cookie.into_owned(), request_url))
}
pub fn insert(&mut self, cookie: Cookie<'static>, request_url: &Url) -> InsertResult {
if cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) {
return Err(CookieError::NonHttpScheme);
}
#[cfg(feature = "public_suffix")]
let mut cookie = cookie;
#[cfg(feature = "public_suffix")]
if let Some(ref psl) = self.public_suffix_list {
if cookie.domain.is_public_suffix(psl) {
if cookie.domain.host_is_identical(request_url) {
cookie.domain = crate::cookie_domain::CookieDomain::host_only(request_url)?;
} else {
return Err(CookieError::PublicSuffix);
}
}
}
if !cookie.domain.matches(request_url) {
return Err(CookieError::DomainMismatch);
}
{
let cookie_domain = cookie
.domain
.as_cow()
.ok_or_else(|| CookieError::UnspecifiedDomain)?;
if let Some(old_cookie) = self.get_mut(&cookie_domain, &cookie.path, cookie.name()) {
if old_cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) {
return Err(CookieError::NonHttpScheme);
} else if cookie.is_expired() {
old_cookie.expire();
return Ok(StoreAction::ExpiredExisting);
}
}
}
if !cookie.is_expired() {
Ok(
if self
.cookies
.entry(String::from(&cookie.domain))
.or_insert_with(Map::new)
.entry(String::from(&cookie.path))
.or_insert_with(Map::new)
.insert(cookie.name().to_owned(), cookie)
.is_none()
{
StoreAction::Inserted
} else {
StoreAction::UpdatedExisting
},
)
} else {
Err(CookieError::Expired)
}
}
pub fn clear(&mut self) {
self.cookies.clear()
}
pub fn iter_unexpired<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a {
self.cookies
.values()
.flat_map(|dcs| dcs.values())
.flat_map(|pcs| pcs.values())
.filter(|c| !c.is_expired())
}
pub fn iter_any<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a {
self.cookies
.values()
.flat_map(|dcs| dcs.values())
.flat_map(|pcs| pcs.values())
}
pub fn save<W, E, F>(&self, writer: &mut W, cookie_to_string: F) -> StoreResult<()>
where
W: Write,
F: Fn(&Cookie<'static>) -> Result<String, E>,
crate::Error: From<E>,
{
for cookie in self.iter_unexpired().filter_map(|c| {
if c.is_persistent() {
Some(cookie_to_string(c))
} else {
None
}
}) {
writeln!(writer, "{}", cookie?)?;
}
Ok(())
}
pub fn save_json<W: Write>(&self, writer: &mut W) -> StoreResult<()> {
self.save(writer, ::serde_json::to_string)
}
pub fn save_incl_expired_and_nonpersistent<W, E, F>(
&self,
writer: &mut W,
cookie_to_string: F,
) -> StoreResult<()>
where
W: Write,
F: Fn(&Cookie<'static>) -> Result<String, E>,
crate::Error: From<E>,
{
for cookie in self.iter_any() {
writeln!(writer, "{}", cookie_to_string(cookie)?)?;
}
Ok(())
}
pub fn save_incl_expired_and_nonpersistent_json<W: Write>(
&self,
writer: &mut W,
) -> StoreResult<()> {
self.save_incl_expired_and_nonpersistent(writer, ::serde_json::to_string)
}
pub fn load<R, E, F>(reader: R, cookie_from_str: F) -> StoreResult<CookieStore>
where
R: BufRead,
F: Fn(&str) -> Result<Cookie<'static>, E>,
crate::Error: From<E>,
{
CookieStore::load_from(reader, cookie_from_str, false)
}
pub fn load_all<R, E, F>(reader: R, cookie_from_str: F) -> StoreResult<CookieStore>
where
R: BufRead,
F: Fn(&str) -> Result<Cookie<'static>, E>,
crate::Error: From<E>,
{
CookieStore::load_from(reader, cookie_from_str, true)
}
fn load_from<R, E, F>(
reader: R,
cookie_from_str: F,
include_expired: bool,
) -> StoreResult<CookieStore>
where
R: BufRead,
F: Fn(&str) -> Result<Cookie<'static>, E>,
crate::Error: From<E>,
{
let cookies = reader.lines().map(|line_result| {
line_result
.map_err(Into::into)
.and_then(|line| cookie_from_str(&line).map_err(crate::Error::from))
});
Self::from_cookies(cookies, include_expired)
}
pub fn load_json<R: BufRead>(reader: R) -> StoreResult<CookieStore> {
CookieStore::load(reader, |cookie| ::serde_json::from_str(cookie))
}
pub fn load_json_all<R: BufRead>(reader: R) -> StoreResult<CookieStore> {
CookieStore::load_all(reader, |cookie| ::serde_json::from_str(cookie))
}
pub fn from_cookies<I, E>(iter: I, include_expired: bool) -> Result<Self, E>
where
I: IntoIterator<Item = Result<Cookie<'static>, E>>,
{
let mut cookies = Map::new();
for cookie in iter {
let cookie = cookie?;
if include_expired || !cookie.is_expired() {
cookies
.entry(String::from(&cookie.domain))
.or_insert_with(Map::new)
.entry(String::from(&cookie.path))
.or_insert_with(Map::new)
.insert(cookie.name().to_owned(), cookie);
}
}
Ok(Self {
cookies,
#[cfg(feature = "public_suffix")]
public_suffix_list: None,
})
}
pub fn new(
#[cfg(feature = "public_suffix")] public_suffix_list: Option<publicsuffix::List>,
) -> Self {
Self {
cookies: DomainMap::new(),
#[cfg(feature = "public_suffix")]
public_suffix_list,
}
}
}
impl Serialize for CookieStore {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(self.iter_unexpired().filter(|c| c.is_persistent()))
}
}
struct CookieStoreVisitor;
impl<'de> Visitor<'de> for CookieStoreVisitor {
type Value = CookieStore;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "a sequence of cookies")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
CookieStore::from_cookies(iter::from_fn(|| seq.next_element().transpose()), false)
}
}
impl<'de> Deserialize<'de> for CookieStore {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(CookieStoreVisitor)
}
}
#[cfg(test)]
mod tests {
use super::CookieStore;
use super::{InsertResult, StoreAction};
use crate::cookie::Cookie;
use crate::CookieError;
use ::cookie::Cookie as RawCookie;
use std::str::from_utf8;
use time::OffsetDateTime;
use crate::utils::test as test_utils;
macro_rules! has_str {
($e: expr, $i: ident) => {{
let val = from_utf8(&$i[..]).unwrap();
assert!(val.contains($e), "exp: {}\nval: {}", $e, val);
}};
}
macro_rules! not_has_str {
($e: expr, $i: ident) => {{
let val = from_utf8(&$i[..]).unwrap();
assert!(!val.contains($e), "exp: {}\nval: {}", $e, val);
}};
}
macro_rules! inserted {
($e: expr) => {
assert_eq!(Ok(StoreAction::Inserted), $e)
};
}
macro_rules! updated {
($e: expr) => {
assert_eq!(Ok(StoreAction::UpdatedExisting), $e)
};
}
macro_rules! expired_existing {
($e: expr) => {
assert_eq!(Ok(StoreAction::ExpiredExisting), $e)
};
}
macro_rules! domain_mismatch {
($e: expr) => {
assert_eq!(Err(CookieError::DomainMismatch), $e)
};
}
macro_rules! non_http_scheme {
($e: expr) => {
assert_eq!(Err(CookieError::NonHttpScheme), $e)
};
}
macro_rules! non_rel_scheme {
($e: expr) => {
assert_eq!(Err(CookieError::NonRelativeScheme), $e)
};
}
macro_rules! expired_err {
($e: expr) => {
assert_eq!(Err(CookieError::Expired), $e)
};
}
macro_rules! values_are {
($store: expr, $url: expr, $values: expr) => {{
let mut matched_values = $store
.matches(&test_utils::url($url))
.iter()
.map(|c| &c.value()[..])
.collect::<Vec<_>>();
matched_values.sort();
let mut values: Vec<&str> = $values;
values.sort();
assert!(
matched_values == values,
"\n{:?}\n!=\n{:?}\n",
matched_values,
values
);
}};
}
fn add_cookie(
store: &mut CookieStore,
cookie: &str,
url: &str,
expires: Option<OffsetDateTime>,
max_age: Option<u64>,
) -> InsertResult {
store.insert(
test_utils::make_cookie(cookie, url, expires, max_age),
&test_utils::url(url),
)
}
fn make_match_store() -> CookieStore {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie1=1",
"http://example.com/foo/bar",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie2=2; Secure",
"https://example.com/sec/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie3=3; HttpOnly",
"https://example.com/sec/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie4=4; Secure; HttpOnly",
"https://example.com/sec/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie5=5",
"http://example.com/foo/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie6=6",
"http://example.com/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie7=7",
"http://bar.example.com/foo/",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie8=8",
"http://example.org/foo/bar",
None,
Some(60 * 5),
));
inserted!(add_cookie(
&mut store,
"cookie9=9",
"http://bar.example.org/foo/bar",
None,
Some(60 * 5),
));
store
}
macro_rules! check_matches {
($store: expr) => {{
values_are!($store, "http://unknowndomain.org/foo/bar", vec![]);
values_are!($store, "http://example.org/foo/bar", vec!["8"]);
values_are!($store, "http://example.org/bus/bar", vec![]);
values_are!($store, "http://bar.example.org/foo/bar", vec!["9"]);
values_are!($store, "http://bar.example.org/bus/bar", vec![]);
values_are!(
$store,
"https://example.com/sec/foo",
vec!["6", "4", "3", "2"]
);
values_are!($store, "http://example.com/sec/foo", vec!["6", "3"]);
values_are!($store, "ftp://example.com/sec/foo", vec!["6"]);
values_are!($store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
values_are!(
$store,
"http://example.com/foo/bar/bus",
vec!["1", "5", "6"]
);
}};
}
#[test]
fn insert_raw() {
let mut store = CookieStore::default();
inserted!(store.insert_raw(
&RawCookie::parse("cookie1=value1").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
non_rel_scheme!(store.insert_raw(
&RawCookie::parse("cookie1=value1").unwrap(),
&test_utils::url("data:nonrelativescheme"),
));
non_http_scheme!(store.insert_raw(
&RawCookie::parse("cookie1=value1; HttpOnly").unwrap(),
&test_utils::url("ftp://example.com/"),
));
expired_existing!(store.insert_raw(
&RawCookie::parse("cookie1=value1; Max-Age=0").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
expired_err!(store.insert_raw(
&RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
updated!(store.insert_raw(
&RawCookie::parse("cookie1=value1").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
expired_existing!(store.insert_raw(
&RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
domain_mismatch!(store.insert_raw(
&RawCookie::parse("cookie1=value1; Domain=bar.example.com").unwrap(),
&test_utils::url("http://example.com/foo/bar"),
));
}
#[test]
fn parse() {
let mut store = CookieStore::default();
inserted!(store.parse(
"cookie1=value1",
&test_utils::url("http://example.com/foo/bar"),
));
non_rel_scheme!(store.parse("cookie1=value1", &test_utils::url("data:nonrelativescheme"),));
non_http_scheme!(store.parse(
"cookie1=value1; HttpOnly",
&test_utils::url("ftp://example.com/"),
));
expired_existing!(store.parse(
"cookie1=value1; Max-Age=0",
&test_utils::url("http://example.com/foo/bar"),
));
expired_err!(store.parse(
"cookie1=value1; Max-Age=-1",
&test_utils::url("http://example.com/foo/bar"),
));
updated!(store.parse(
"cookie1=value1",
&test_utils::url("http://example.com/foo/bar"),
));
expired_existing!(store.parse(
"cookie1=value1; Max-Age=-1",
&test_utils::url("http://example.com/foo/bar"),
));
domain_mismatch!(store.parse(
"cookie1=value1; Domain=bar.example.com",
&test_utils::url("http://example.com/foo/bar"),
));
}
#[test]
fn save() {
let mut output = vec![];
let mut store = CookieStore::default();
store.save_json(&mut output).unwrap();
assert_eq!("", from_utf8(&output[..]).unwrap());
inserted!(add_cookie(
&mut store,
"cookie0=value0",
"http://example.com/foo/bar",
None,
None,
));
store.save_json(&mut output).unwrap();
assert_eq!("", from_utf8(&output[..]).unwrap());
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
None,
Some(10),
));
store.save_json(&mut output).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
output.clear();
inserted!(add_cookie(
&mut store,
"cookie2=value2",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
store.save_json(&mut output).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
output.clear();
inserted!(add_cookie(
&mut store,
"cookie3=value3; Domain=example.com",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=value4; Path=/foo/",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie5=value5",
"http://127.0.0.1/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie6=value6",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie7=value7; Secure",
"https://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie8=value8; HttpOnly",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
store.save_json(&mut output).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
has_str!("cookie3=value3", output);
has_str!("cookie4=value4", output);
has_str!("cookie5=value5", output);
has_str!("cookie6=value6", output);
has_str!("cookie7=value7; Secure", output);
has_str!("cookie8=value8; HttpOnly", output);
output.clear();
}
#[test]
fn serialize() {
let mut output = vec![];
let mut store = CookieStore::default();
serde_json::to_writer(&mut output, &store).unwrap();
assert_eq!("[]", from_utf8(&output[..]).unwrap());
output.clear();
inserted!(add_cookie(
&mut store,
"cookie0=value0",
"http://example.com/foo/bar",
None,
None,
));
serde_json::to_writer(&mut output, &store).unwrap();
assert_eq!("[]", from_utf8(&output[..]).unwrap());
output.clear();
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
None,
Some(10),
));
serde_json::to_writer(&mut output, &store).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
output.clear();
inserted!(add_cookie(
&mut store,
"cookie2=value2",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
serde_json::to_writer(&mut output, &store).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
output.clear();
inserted!(add_cookie(
&mut store,
"cookie3=value3; Domain=example.com",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=value4; Path=/foo/",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie5=value5",
"http://127.0.0.1/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie6=value6",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie7=value7; Secure",
"https://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie8=value8; HttpOnly",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
serde_json::to_writer(&mut output, &store).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
has_str!("cookie3=value3", output);
has_str!("cookie4=value4", output);
has_str!("cookie5=value5", output);
has_str!("cookie6=value6", output);
has_str!("cookie7=value7; Secure", output);
has_str!("cookie8=value8; HttpOnly", output);
output.clear();
}
#[test]
fn domains() {
let mut store = CookieStore::default();
fn domain_cookie_from(domain: &str, request_url: &str) -> Cookie<'static> {
let cookie_str = format!("cookie1=value1; Domain={}", domain);
Cookie::parse(cookie_str, &test_utils::url(request_url)).unwrap()
}
{
let request_url = test_utils::url("http://foo.example.com");
inserted!(store.insert(
domain_cookie_from("example.com", "http://foo.example.com",),
&request_url,
));
updated!(store.insert(
domain_cookie_from(".example.com", "http://foo.example.com",),
&request_url,
));
inserted!(store.insert(
domain_cookie_from("foo.example.com", "http://foo.example.com",),
&request_url,
));
updated!(store.insert(
domain_cookie_from(".foo.example.com", "http://foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from("bar.example.com", "http://bar.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from(".bar.example.com", "http://bar.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from("bar.foo.example.com", "http://bar.foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from(".bar.foo.example.com", "http://bar.foo.example.com",),
&request_url,
));
}
{
let request_url = test_utils::url("http://bar.example.com");
updated!(store.insert(
domain_cookie_from("example.com", "http://foo.example.com",),
&request_url,
));
updated!(store.insert(
domain_cookie_from(".example.com", "http://foo.example.com",),
&request_url,
));
inserted!(store.insert(
domain_cookie_from("bar.example.com", "http://bar.example.com",),
&request_url,
));
updated!(store.insert(
domain_cookie_from(".bar.example.com", "http://bar.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from("foo.example.com", "http://foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from(".foo.example.com", "http://foo.example.com",),
&request_url,
));
}
{
let request_url = test_utils::url("http://example.com");
updated!(store.insert(
domain_cookie_from("example.com", "http://foo.example.com",),
&request_url,
));
updated!(store.insert(
domain_cookie_from(".example.com", "http://foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from("foo.example.com", "http://foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from(".foo.example.com", "http://foo.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from("bar.example.com", "http://bar.example.com",),
&request_url,
));
domain_mismatch!(store.insert(
domain_cookie_from(".bar.example.com", "http://bar.example.com",),
&request_url,
));
}
}
#[test]
fn http_only() {
let mut store = CookieStore::default();
let c = Cookie::parse(
"cookie1=value1; HttpOnly",
&test_utils::url("http://example.com/foo/bar"),
)
.unwrap();
non_http_scheme!(store.insert(c, &test_utils::url("ftp://example.com/foo/bar"),));
}
#[test]
fn load() {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie0=value0",
"http://example.com/foo/bar",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
None,
Some(10),
));
inserted!(add_cookie(
&mut store,
"cookie2=value2",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie3=value3; Domain=example.com",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=value4; Path=/foo/",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie5=value5",
"http://127.0.0.1/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie6=value6",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie7=value7; Secure",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie8=value8; HttpOnly",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
let mut output = vec![];
store.save_json(&mut output).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
has_str!("cookie3=value3", output);
has_str!("cookie4=value4", output);
has_str!("cookie5=value5", output);
has_str!("cookie6=value6", output);
has_str!("cookie7=value7; Secure", output);
has_str!("cookie8=value8; HttpOnly", output);
let store = CookieStore::load_json(&output[..]).unwrap();
assert!(store.get("example.com", "/foo", "cookie0").is_none());
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2");
assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3");
assert!(
store
.get("foo.example.com", "/foo/", "cookie4")
.unwrap()
.value()
== "value4"
);
assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5");
assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6");
assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7");
assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8");
output.clear();
let store = make_match_store();
store.save_json(&mut output).unwrap();
let store = CookieStore::load_json(&output[..]).unwrap();
check_matches!(&store);
}
#[test]
fn deserialize() {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie0=value0",
"http://example.com/foo/bar",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
None,
Some(10),
));
inserted!(add_cookie(
&mut store,
"cookie2=value2",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie3=value3; Domain=example.com",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=value4; Path=/foo/",
"http://foo.example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie5=value5",
"http://127.0.0.1/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie6=value6",
"http://[::1]/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie7=value7; Secure",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
inserted!(add_cookie(
&mut store,
"cookie8=value8; HttpOnly",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
let mut output = vec![];
serde_json::to_writer(&mut output, &store).unwrap();
not_has_str!("cookie0=value0", output);
has_str!("cookie1=value1", output);
has_str!("cookie2=value2", output);
has_str!("cookie3=value3", output);
has_str!("cookie4=value4", output);
has_str!("cookie5=value5", output);
has_str!("cookie6=value6", output);
has_str!("cookie7=value7; Secure", output);
has_str!("cookie8=value8; HttpOnly", output);
let store: CookieStore = serde_json::from_reader(&output[..]).unwrap();
assert!(store.get("example.com", "/foo", "cookie0").is_none());
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2");
assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3");
assert!(
store
.get("foo.example.com", "/foo/", "cookie4")
.unwrap()
.value()
== "value4"
);
assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5");
assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6");
assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7");
assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8");
output.clear();
let store = make_match_store();
serde_json::to_writer(&mut output, &store).unwrap();
let store: CookieStore = serde_json::from_reader(&output[..]).unwrap();
check_matches!(&store);
}
#[test]
fn clear() {
let mut output = vec![];
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
Some(test_utils::in_days(1)),
None,
));
store.save_json(&mut output).unwrap();
has_str!("cookie1=value1", output);
output.clear();
store.clear();
store.save_json(&mut output).unwrap();
assert_eq!("", from_utf8(&output[..]).unwrap());
}
#[test]
fn add_and_get() {
let mut store = CookieStore::default();
assert!(store.get("example.com", "/foo", "cookie1").is_none());
inserted!(add_cookie(
&mut store,
"cookie1=value1",
"http://example.com/foo/bar",
None,
None,
));
assert!(store.get("example.com", "/foo/bar", "cookie1").is_none());
assert!(store.get("example.com", "/foo", "cookie2").is_none());
assert!(store.get("example.org", "/foo", "cookie1").is_none());
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
updated!(add_cookie(
&mut store,
"cookie1=value2",
"http://example.com/foo/bar",
None,
None,
));
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
inserted!(add_cookie(
&mut store,
"cookie2=value3",
"http://example.com/foo/bar",
None,
None,
));
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
inserted!(add_cookie(
&mut store,
"cookie3=value4; HttpOnly",
"http://example.com/foo/bar",
None,
None,
));
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4");
non_http_scheme!(add_cookie(
&mut store,
"cookie3=value5",
"ftp://example.com/foo/bar",
None,
None,
));
assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4");
}
#[test]
fn matches() {
let store = make_match_store();
check_matches!(&store);
}
#[test]
fn expiry() {
let mut store = make_match_store();
let request_url = test_utils::url("http://foo.example.com");
let expired_cookie = Cookie::parse("cookie1=value1; Max-Age=-1", &request_url).unwrap();
expired_err!(store.insert(expired_cookie, &request_url));
check_matches!(&store);
match store.get_mut("example.com", "/", "cookie6") {
Some(cookie) => cookie.expire(),
None => unreachable!(),
}
values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
values_are!(store, "http://example.org/foo/bar", vec!["8"]);
values_are!(store, "http://example.org/bus/bar", vec![]);
values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
values_are!(store, "http://bar.example.org/bus/bar", vec![]);
values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
values_are!(store, "http://example.com/sec/foo", vec!["3"]);
values_are!(store, "ftp://example.com/sec/foo", vec![]);
values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
values_are!(store, "http://example.com/foo/bar/bus", vec!["1", "5"]);
match store.get_any("example.com", "/", "cookie6") {
Some(cookie) => assert!(cookie.is_expired()),
None => unreachable!(),
}
let request_url = test_utils::url("http://example.com/foo/");
let expired_cookie = Cookie::parse("cookie5=value5; Max-Age=-1", &request_url).unwrap();
expired_existing!(store.insert(expired_cookie, &request_url));
values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
values_are!(store, "http://example.org/foo/bar", vec!["8"]);
values_are!(store, "http://example.org/bus/bar", vec![]);
values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
values_are!(store, "http://bar.example.org/bus/bar", vec![]);
values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
values_are!(store, "http://example.com/sec/foo", vec!["3"]);
values_are!(store, "ftp://example.com/sec/foo", vec![]);
values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]);
match store.get_any("example.com", "/foo", "cookie5") {
Some(cookie) => assert!(cookie.is_expired()),
None => unreachable!(),
}
let mut output = vec![];
store.save_json(&mut output).unwrap();
store = CookieStore::load_json(&output[..]).unwrap();
values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
values_are!(store, "http://example.org/foo/bar", vec!["8"]);
values_are!(store, "http://example.org/bus/bar", vec![]);
values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
values_are!(store, "http://bar.example.org/bus/bar", vec![]);
values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
values_are!(store, "http://example.com/sec/foo", vec!["3"]);
values_are!(store, "ftp://example.com/sec/foo", vec![]);
values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]);
assert!(store.get_any("example.com", "/", "cookie6").is_none());
assert!(store.get_any("example.com", "/foo", "cookie5").is_none());
}
#[test]
fn non_persistent() {
let mut store = make_match_store();
check_matches!(&store);
let request_url = test_utils::url("http://example.com/tmp/");
let non_persistent = Cookie::parse("cookie10=value10", &request_url).unwrap();
inserted!(store.insert(non_persistent, &request_url));
match store.get("example.com", "/tmp", "cookie10") {
None => unreachable!(),
Some(cookie) => assert_eq!("value10", cookie.value()),
}
let mut output = vec![];
store.save_json(&mut output).unwrap();
store = CookieStore::load_json(&output[..]).unwrap();
check_matches!(&store);
assert!(store.get("example.com", "/tmp", "cookie10").is_none());
assert!(store.get_any("example.com", "/tmp", "cookie10").is_none());
}
macro_rules! dump {
($e: expr, $i: ident) => {{
use serde_json;
println!("");
println!(
"==== {}: {} ====",
$e,
time::OffsetDateTime::now_utc()
.format(crate::rfc3339_fmt::RFC3339_FORMAT)
.unwrap()
);
for c in $i.iter_any() {
println!(
"{} {}",
if c.is_expired() {
"XXXXX"
} else if c.is_persistent() {
"PPPPP"
} else {
" "
},
serde_json::to_string(c).unwrap()
);
println!("----------------");
}
println!("================");
}};
}
fn matches_are(store: &CookieStore, url: &str, exp: Vec<&str>) {
let matches = store
.matches(&test_utils::url(url))
.iter()
.map(|c| format!("{}={}", c.name(), c.value()))
.collect::<Vec<_>>();
for e in &exp {
assert!(
matches.iter().any(|m| &m[..] == *e),
"{}: matches missing '{}'\nmatches: {:?}\n exp: {:?}",
url,
e,
matches,
exp
);
}
assert!(
matches.len() == exp.len(),
"{}: matches={:?} != exp={:?}",
url,
matches,
exp
);
}
#[test]
fn some_non_https_uris_are_secure() {
let secure_uris = vec![
"http://localhost",
"http://localhost:1234",
"http://127.0.0.1",
"http://127.0.0.2",
"http://127.1.0.1",
"http://[::1]",
];
for secure_uri in secure_uris {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie1=1a; Secure",
secure_uri,
None,
None,
));
matches_are(&store, secure_uri, vec!["cookie1=1a"]);
}
}
#[test]
fn domain_collisions() {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie1=1a",
"http://foo.bus.example.com/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie1=1b",
"http://bus.example.com/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie2=2a; Domain=bus.example.com",
"http://foo.bus.example.com/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie2=2b; Domain=example.com",
"http://bus.example.com/",
None,
None,
));
dump!("domain_collisions", store);
matches_are(
&store,
"http://foo.bus.example.com/",
vec!["cookie1=1a", "cookie2=2a", "cookie2=2b"],
);
matches_are(
&store,
"http://bus.example.com/",
vec!["cookie1=1b", "cookie2=2a", "cookie2=2b"],
);
matches_are(&store, "http://example.com/", vec!["cookie2=2b"]);
matches_are(&store, "http://foo.example.com/", vec!["cookie2=2b"]);
}
#[test]
fn path_collisions() {
let mut store = CookieStore::default();
inserted!(add_cookie(
&mut store,
"cookie3=3a",
"http://bus.example.com/foo/bar/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie3=3b",
"http://bus.example.com/foo/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=4a; Path=/foo/bar/",
"http://bus.example.com/",
None,
None,
));
inserted!(add_cookie(
&mut store,
"cookie4=4b; Path=/foo/",
"http://bus.example.com/",
None,
None,
));
dump!("path_collisions", store);
matches_are(
&store,
"http://bus.example.com/foo/bar/",
vec!["cookie3=3a", "cookie3=3b", "cookie4=4a", "cookie4=4b"],
);
matches_are(
&store,
"http://bus.example.com/foo/bar",
vec!["cookie3=3a", "cookie3=3b", "cookie4=4b"],
);
matches_are(
&store,
"http://bus.example.com/foo/ba",
vec!["cookie3=3b", "cookie4=4b"],
);
matches_are(
&store,
"http://bus.example.com/foo/",
vec!["cookie3=3b", "cookie4=4b"],
);
matches_are(&store, "http://bus.example.com/foo", vec!["cookie3=3b"]);
matches_are(&store, "http://bus.example.com/fo", vec![]);
matches_are(&store, "http://bus.example.com/", vec![]);
matches_are(&store, "http://bus.example.com", vec![]);
}
}