1 use std::fmt::{self, Formatter};
2 use std::io::{BufRead, Write};
3 use std::iter;
4 use std::ops::Deref;
5 
6 use ::cookie::Cookie as RawCookie;
7 use log::debug;
8 use publicsuffix;
9 use serde::de::{SeqAccess, Visitor};
10 use serde::{Deserialize, Deserializer, Serialize, Serializer};
11 use url::Url;
12 
13 use crate::cookie::Cookie;
14 use crate::cookie_domain::{is_match as domain_match, CookieDomain};
15 use crate::cookie_path::is_match as path_match;
16 use crate::utils::{is_http_scheme, is_secure};
17 use crate::CookieError;
18 
19 #[cfg(feature = "preserve_order")]
20 use indexmap::IndexMap;
21 #[cfg(not(feature = "preserve_order"))]
22 use std::collections::HashMap;
23 #[cfg(feature = "preserve_order")]
24 type Map<K, V> = IndexMap<K, V>;
25 #[cfg(not(feature = "preserve_order"))]
26 type Map<K, V> = HashMap<K, V>;
27 
28 type NameMap = Map<String, Cookie<'static>>;
29 type PathMap = Map<String, NameMap>;
30 type DomainMap = Map<String, PathMap>;
31 
32 #[derive(PartialEq, Clone, Debug, Eq)]
33 pub enum StoreAction {
34     /// The `Cookie` was successfully added to the store
35     Inserted,
36     /// The `Cookie` successfully expired a `Cookie` already in the store
37     ExpiredExisting,
38     /// The `Cookie` was added to the store, replacing an existing entry
39     UpdatedExisting,
40 }
41 
42 pub type StoreResult<T> = Result<T, crate::Error>;
43 pub type InsertResult = Result<StoreAction, CookieError>;
44 #[derive(Debug, Default)]
45 pub struct CookieStore {
46     /// Cookies stored by domain, path, then name
47     cookies: DomainMap,
48     /// If set, enables [public suffix](https://tools.ietf.org/html/rfc6265#section-5.3) rejection based on the provided `publicsuffix::List`
49     public_suffix_list: Option<publicsuffix::List>,
50 }
51 
52 impl CookieStore {
53     /// Return an `Iterator` of the cookies for `url` in the store
get_request_cookies(&self, url: &Url) -> impl Iterator<Item = &RawCookie<'static>>54     pub fn get_request_cookies(&self, url: &Url) -> impl Iterator<Item = &RawCookie<'static>> {
55         self.matches(url).into_iter().map(|c| c.deref())
56     }
57 
58     /// Store the `cookies` received from `url`
store_response_cookies<I: Iterator<Item = RawCookie<'static>>>( &mut self, cookies: I, url: &Url, )59     pub fn store_response_cookies<I: Iterator<Item = RawCookie<'static>>>(
60         &mut self,
61         cookies: I,
62         url: &Url,
63     ) {
64         for cookie in cookies {
65             debug!("inserting Set-Cookie '{:?}'", cookie);
66             if let Err(e) = self.insert_raw(&cookie, url) {
67                 debug!("unable to store Set-Cookie: {:?}", e);
68             }
69         }
70     }
71 
72     /// Specify a `publicsuffix::List` for the `CookieStore` to allow [public suffix
73     /// matching](https://tools.ietf.org/html/rfc6265#section-5.3)
with_suffix_list(self, psl: publicsuffix::List) -> CookieStore74     pub fn with_suffix_list(self, psl: publicsuffix::List) -> CookieStore {
75         CookieStore {
76             cookies: self.cookies,
77             public_suffix_list: Some(psl),
78         }
79     }
80 
81     /// Returns true if the `CookieStore` contains an __unexpired__ `Cookie` corresponding to the
82     /// specified `domain`, `path`, and `name`.
contains(&self, domain: &str, path: &str, name: &str) -> bool83     pub fn contains(&self, domain: &str, path: &str, name: &str) -> bool {
84         self.get(domain, path, name).is_some()
85     }
86 
87     /// Returns true if the `CookieStore` contains any (even an __expired__) `Cookie` corresponding
88     /// to the specified `domain`, `path`, and `name`.
contains_any(&self, domain: &str, path: &str, name: &str) -> bool89     pub fn contains_any(&self, domain: &str, path: &str, name: &str) -> bool {
90         self.get_any(domain, path, name).is_some()
91     }
92 
93     /// Returns a reference to the __unexpired__ `Cookie` corresponding to the specified `domain`,
94     /// `path`, and `name`.
get(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'_>>95     pub fn get(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'_>> {
96         self.get_any(domain, path, name).and_then(|cookie| {
97             if cookie.is_expired() {
98                 None
99             } else {
100                 Some(cookie)
101             }
102         })
103     }
104 
105     /// Returns a mutable reference to the __unexpired__ `Cookie` corresponding to the specified
106     /// `domain`, `path`, and `name`.
get_mut(&mut self, domain: &str, path: &str, name: &str) -> Option<&mut Cookie<'static>>107     fn get_mut(&mut self, domain: &str, path: &str, name: &str) -> Option<&mut Cookie<'static>> {
108         self.get_mut_any(domain, path, name).and_then(|cookie| {
109             if cookie.is_expired() {
110                 None
111             } else {
112                 Some(cookie)
113             }
114         })
115     }
116 
117     /// Returns a reference to the (possibly __expired__) `Cookie` corresponding to the specified
118     /// `domain`, `path`, and `name`.
get_any(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'static>>119     pub fn get_any(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'static>> {
120         self.cookies.get(domain).and_then(|domain_cookies| {
121             domain_cookies
122                 .get(path)
123                 .and_then(|path_cookies| path_cookies.get(name))
124         })
125     }
126 
127     /// Returns a mutable reference to the (possibly __expired__) `Cookie` corresponding to the
128     /// specified `domain`, `path`, and `name`.
get_mut_any( &mut self, domain: &str, path: &str, name: &str, ) -> Option<&mut Cookie<'static>>129     fn get_mut_any(
130         &mut self,
131         domain: &str,
132         path: &str,
133         name: &str,
134     ) -> Option<&mut Cookie<'static>> {
135         self.cookies.get_mut(domain).and_then(|domain_cookies| {
136             domain_cookies
137                 .get_mut(path)
138                 .and_then(|path_cookies| path_cookies.get_mut(name))
139         })
140     }
141 
142     /// Removes a `Cookie` from the store, returning the `Cookie` if it was in the store
remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>>143     pub fn remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>> {
144         #[cfg(not(feature = "preserve_order"))]
145         fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
146         where
147             K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash,
148             Q: std::cmp::Eq + std::hash::Hash + ?Sized,
149         {
150             map.remove(key)
151         }
152         #[cfg(feature = "preserve_order")]
153         fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
154         where
155             K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash,
156             Q: std::cmp::Eq + std::hash::Hash + ?Sized,
157         {
158             map.shift_remove(key)
159         }
160 
161         let (removed, remove_domain) = match self.cookies.get_mut(domain) {
162             None => (None, false),
163             Some(domain_cookies) => {
164                 let (removed, remove_path) = match domain_cookies.get_mut(path) {
165                     None => (None, false),
166                     Some(path_cookies) => {
167                         let removed = map_remove(path_cookies, name);
168                         (removed, path_cookies.is_empty())
169                     }
170                 };
171 
172                 if remove_path {
173                     map_remove(domain_cookies, path);
174                     (removed, domain_cookies.is_empty())
175                 } else {
176                     (removed, false)
177                 }
178             }
179         };
180 
181         if remove_domain {
182             map_remove(&mut self.cookies, domain);
183         }
184 
185         removed
186     }
187 
188     /// Returns a collection of references to __unexpired__ cookies that path- and domain-match
189     /// `request_url`, as well as having HttpOnly and Secure attributes compatible with the
190     /// `request_url`.
matches(&self, request_url: &Url) -> Vec<&Cookie<'static>>191     pub fn matches(&self, request_url: &Url) -> Vec<&Cookie<'static>> {
192         // although we domain_match and path_match as we descend through the tree, we
193         // still need to
194         // do a full Cookie::matches() check in the last filter. Otherwise, we cannot
195         // properly deal
196         // with HostOnly Cookies.
197         let cookies = self
198             .cookies
199             .iter()
200             .filter(|&(d, _)| domain_match(d, request_url))
201             .flat_map(|(_, dcs)| {
202                 dcs.iter()
203                     .filter(|&(p, _)| path_match(p, request_url))
204                     .flat_map(|(_, pcs)| {
205                         pcs.values()
206                             .filter(|c| !c.is_expired() && c.matches(request_url))
207                     })
208             });
209         match (!is_http_scheme(request_url), !is_secure(request_url)) {
210             (true, true) => cookies
211                 .filter(|c| !c.http_only().unwrap_or(false) && !c.secure().unwrap_or(false))
212                 .collect(),
213             (true, false) => cookies
214                 .filter(|c| !c.http_only().unwrap_or(false))
215                 .collect(),
216             (false, true) => cookies.filter(|c| !c.secure().unwrap_or(false)).collect(),
217             (false, false) => cookies.collect(),
218         }
219     }
220 
221     /// Parses a new `Cookie` from `cookie_str` and inserts it into the store.
parse(&mut self, cookie_str: &str, request_url: &Url) -> InsertResult222     pub fn parse(&mut self, cookie_str: &str, request_url: &Url) -> InsertResult {
223         Cookie::parse(cookie_str, request_url)
224             .and_then(|cookie| self.insert(cookie.into_owned(), request_url))
225     }
226 
227     /// Converts a `cookie::Cookie` (from the `cookie` crate) into a `cookie_store::Cookie` and
228     /// inserts it into the store.
insert_raw(&mut self, cookie: &RawCookie<'_>, request_url: &Url) -> InsertResult229     pub fn insert_raw(&mut self, cookie: &RawCookie<'_>, request_url: &Url) -> InsertResult {
230         Cookie::try_from_raw_cookie(cookie, request_url)
231             .and_then(|cookie| self.insert(cookie.into_owned(), request_url))
232     }
233 
234     /// Inserts `cookie`, received from `request_url`, into the store, following the rules of the
235     /// [IETF RFC6265 Storage Model](http://tools.ietf.org/html/rfc6265#section-5.3). If the
236     /// `Cookie` is __unexpired__ and is successfully inserted, returns
237     /// `Ok(StoreAction::Inserted)`. If the `Cookie` is __expired__ *and* matches an existing
238     /// `Cookie` in the store, the existing `Cookie` wil be `expired()` and
239     /// `Ok(StoreAction::ExpiredExisting)` will be returned.
insert(&mut self, mut cookie: Cookie<'static>, request_url: &Url) -> InsertResult240     pub fn insert(&mut self, mut cookie: Cookie<'static>, request_url: &Url) -> InsertResult {
241         if cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) {
242             // If the cookie was received from a "non-HTTP" API and the
243             // cookie's http-only-flag is set, abort these steps and ignore the
244             // cookie entirely.
245             return Err(CookieError::NonHttpScheme);
246         } else if let Some(ref psl) = self.public_suffix_list {
247             // If the user agent is configured to reject "public suffixes"
248             if cookie.domain.is_public_suffix(psl) {
249                 // and the domain-attribute is a public suffix:
250                 if cookie.domain.host_is_identical(request_url) {
251                     //   If the domain-attribute is identical to the canonicalized
252                     //   request-host:
253                     //     Let the domain-attribute be the empty string.
254                     // (NB: at this point, an empty domain-attribute should be represented
255                     // as the HostOnly variant of CookieDomain)
256                     cookie.domain = CookieDomain::host_only(request_url)?;
257                 } else {
258                     //   Otherwise:
259                     //     Ignore the cookie entirely and abort these steps.
260                     return Err(CookieError::PublicSuffix);
261                 }
262             }
263         } else if !cookie.domain.matches(request_url) {
264             // If the canonicalized request-host does not domain-match the
265             // domain-attribute:
266             //    Ignore the cookie entirely and abort these steps.
267             return Err(CookieError::DomainMismatch);
268         }
269         // NB: we do not bail out above on is_expired(), as servers can remove a cookie
270         // by sending
271         // an expired one, so we need to do the old_cookie check below before checking
272         // is_expired() on an incoming cookie
273 
274         {
275             // At this point in parsing, any non-present Domain attribute should have been
276             // converted into a HostOnly variant
277             let cookie_domain = cookie
278                 .domain
279                 .as_cow()
280                 .ok_or_else(|| CookieError::UnspecifiedDomain)?;
281             if let Some(old_cookie) = self.get_mut(&cookie_domain, &cookie.path, cookie.name()) {
282                 if old_cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) {
283                     // 2.  If the newly created cookie was received from a "non-HTTP"
284                     //    API and the old-cookie's http-only-flag is set, abort these
285                     //    steps and ignore the newly created cookie entirely.
286                     return Err(CookieError::NonHttpScheme);
287                 } else if cookie.is_expired() {
288                     old_cookie.expire();
289                     return Ok(StoreAction::ExpiredExisting);
290                 }
291             }
292         }
293 
294         if !cookie.is_expired() {
295             Ok(
296                 if self
297                     .cookies
298                     .entry(String::from(&cookie.domain))
299                     .or_insert_with(Map::new)
300                     .entry(String::from(&cookie.path))
301                     .or_insert_with(Map::new)
302                     .insert(cookie.name().to_owned(), cookie)
303                     .is_none()
304                 {
305                     StoreAction::Inserted
306                 } else {
307                     StoreAction::UpdatedExisting
308                 },
309             )
310         } else {
311             Err(CookieError::Expired)
312         }
313     }
314 
315     /// Clear the contents of the store
clear(&mut self)316     pub fn clear(&mut self) {
317         self.cookies.clear()
318     }
319 
320     /// An iterator visiting all the __unexpired__ cookies in the store
iter_unexpired<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a321     pub fn iter_unexpired<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a {
322         self.cookies
323             .values()
324             .flat_map(|dcs| dcs.values())
325             .flat_map(|pcs| pcs.values())
326             .filter(|c| !c.is_expired())
327     }
328 
329     /// An iterator visiting all (including __expired__) cookies in the store
iter_any<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a330     pub fn iter_any<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a {
331         self.cookies
332             .values()
333             .flat_map(|dcs| dcs.values())
334             .flat_map(|pcs| pcs.values())
335     }
336 
337     /// Serialize any __unexpired__ and __persistent__ cookies in the store with `cookie_to_string`
338     /// and write them to `writer`
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>,339     pub fn save<W, E, F>(&self, writer: &mut W, cookie_to_string: F) -> StoreResult<()>
340     where
341         W: Write,
342         F: Fn(&Cookie<'static>) -> Result<String, E>,
343         crate::Error: From<E>,
344     {
345         for cookie in self.iter_unexpired().filter_map(|c| {
346             if c.is_persistent() {
347                 Some(cookie_to_string(c))
348             } else {
349                 None
350             }
351         }) {
352             writeln!(writer, "{}", cookie?)?;
353         }
354         Ok(())
355     }
356 
357     /// Serialize any __unexpired__ and __persistent__ cookies in the store to JSON format and
358     /// write them to `writer`
save_json<W: Write>(&self, writer: &mut W) -> StoreResult<()>359     pub fn save_json<W: Write>(&self, writer: &mut W) -> StoreResult<()> {
360         self.save(writer, ::serde_json::to_string)
361     }
362 
363     /// Load cookies from `reader`, deserializing with `cookie_from_str`, skipping any __expired__
364     /// cookies
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>,365     pub fn load<R, E, F>(reader: R, cookie_from_str: F) -> StoreResult<CookieStore>
366     where
367         R: BufRead,
368         F: Fn(&str) -> Result<Cookie<'static>, E>,
369         crate::Error: From<E>,
370     {
371         let cookies = reader.lines().filter_map(|line_result| {
372             let cookie = line_result
373                 .map_err(Into::into)
374                 .and_then(|line| cookie_from_str(&line).map_err(crate::Error::from));
375             match cookie {
376                 Ok(c) if c.is_expired() => None,
377                 _ => Some(cookie),
378             }
379         });
380         Self::from_cookies(cookies)
381     }
382 
383     /// Load JSON-formatted cookies from `reader`, skipping any __expired__ cookies
load_json<R: BufRead>(reader: R) -> StoreResult<CookieStore>384     pub fn load_json<R: BufRead>(reader: R) -> StoreResult<CookieStore> {
385         CookieStore::load(reader, |cookie| ::serde_json::from_str(cookie))
386     }
387 
from_cookies<I, E>(iter: I) -> Result<Self, E> where I: IntoIterator<Item = Result<Cookie<'static>, E>>,388     fn from_cookies<I, E>(iter: I) -> Result<Self, E>
389     where
390         I: IntoIterator<Item = Result<Cookie<'static>, E>>,
391     {
392         let mut cookies = Map::new();
393         for cookie in iter {
394             let cookie = cookie?;
395             if !cookie.is_expired() {
396                 cookies
397                     .entry(String::from(&cookie.domain))
398                     .or_insert_with(Map::new)
399                     .entry(String::from(&cookie.path))
400                     .or_insert_with(Map::new)
401                     .insert(cookie.name().to_owned(), cookie);
402             }
403         }
404         Ok(Self {
405             cookies,
406             public_suffix_list: None,
407         })
408     }
409 }
410 
411 impl Serialize for CookieStore {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,412     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
413     where
414         S: Serializer,
415     {
416         serializer.collect_seq(self.iter_unexpired().filter(|c| c.is_persistent()))
417     }
418 }
419 
420 struct CookieStoreVisitor;
421 
422 impl<'de> Visitor<'de> for CookieStoreVisitor {
423     type Value = CookieStore;
424 
expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result425     fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
426         write!(formatter, "a sequence of cookies")
427     }
428 
visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: SeqAccess<'de>,429     fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
430     where
431         A: SeqAccess<'de>,
432     {
433         CookieStore::from_cookies(iter::from_fn(|| seq.next_element().transpose()))
434     }
435 }
436 
437 impl<'de> Deserialize<'de> for CookieStore {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,438     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
439     where
440         D: Deserializer<'de>,
441     {
442         deserializer.deserialize_seq(CookieStoreVisitor)
443     }
444 }
445 
446 #[cfg(test)]
447 mod tests {
448     use super::CookieStore;
449     use super::{InsertResult, StoreAction};
450     use crate::cookie::Cookie;
451     use crate::CookieError;
452     use ::cookie::Cookie as RawCookie;
453     use std::str::from_utf8;
454     use time::OffsetDateTime;
455 
456     use crate::utils::test as test_utils;
457 
458     macro_rules! has_str {
459         ($e: expr, $i: ident) => {{
460             let val = from_utf8(&$i[..]).unwrap();
461             assert!(val.contains($e), "exp: {}\nval: {}", $e, val);
462         }};
463     }
464     macro_rules! not_has_str {
465         ($e: expr, $i: ident) => {{
466             let val = from_utf8(&$i[..]).unwrap();
467             assert!(!val.contains($e), "exp: {}\nval: {}", $e, val);
468         }};
469     }
470     macro_rules! inserted {
471         ($e: expr) => {
472             assert_eq!(Ok(StoreAction::Inserted), $e)
473         };
474     }
475     macro_rules! updated {
476         ($e: expr) => {
477             assert_eq!(Ok(StoreAction::UpdatedExisting), $e)
478         };
479     }
480     macro_rules! expired_existing {
481         ($e: expr) => {
482             assert_eq!(Ok(StoreAction::ExpiredExisting), $e)
483         };
484     }
485     macro_rules! domain_mismatch {
486         ($e: expr) => {
487             assert_eq!(Err(CookieError::DomainMismatch), $e)
488         };
489     }
490     macro_rules! non_http_scheme {
491         ($e: expr) => {
492             assert_eq!(Err(CookieError::NonHttpScheme), $e)
493         };
494     }
495     macro_rules! non_rel_scheme {
496         ($e: expr) => {
497             assert_eq!(Err(CookieError::NonRelativeScheme), $e)
498         };
499     }
500     macro_rules! expired_err {
501         ($e: expr) => {
502             assert_eq!(Err(CookieError::Expired), $e)
503         };
504     }
505     macro_rules! values_are {
506         ($store: expr, $url: expr, $values: expr) => {{
507             let mut matched_values = $store
508                 .matches(&test_utils::url($url))
509                 .iter()
510                 .map(|c| &c.value()[..])
511                 .collect::<Vec<_>>();
512             matched_values.sort();
513 
514             let mut values: Vec<&str> = $values;
515             values.sort();
516 
517             assert!(
518                 matched_values == values,
519                 "\n{:?}\n!=\n{:?}\n",
520                 matched_values,
521                 values
522             );
523         }};
524     }
525 
add_cookie( store: &mut CookieStore, cookie: &str, url: &str, expires: Option<OffsetDateTime>, max_age: Option<u64>, ) -> InsertResult526     fn add_cookie(
527         store: &mut CookieStore,
528         cookie: &str,
529         url: &str,
530         expires: Option<OffsetDateTime>,
531         max_age: Option<u64>,
532     ) -> InsertResult {
533         store.insert(
534             test_utils::make_cookie(cookie, url, expires, max_age),
535             &test_utils::url(url),
536         )
537     }
538 
make_match_store() -> CookieStore539     fn make_match_store() -> CookieStore {
540         let mut store = CookieStore::default();
541         inserted!(add_cookie(
542             &mut store,
543             "cookie1=1",
544             "http://example.com/foo/bar",
545             None,
546             Some(60 * 5),
547         ));
548         inserted!(add_cookie(
549             &mut store,
550             "cookie2=2; Secure",
551             "https://example.com/sec/",
552             None,
553             Some(60 * 5),
554         ));
555         inserted!(add_cookie(
556             &mut store,
557             "cookie3=3; HttpOnly",
558             "https://example.com/sec/",
559             None,
560             Some(60 * 5),
561         ));
562         inserted!(add_cookie(
563             &mut store,
564             "cookie4=4; Secure; HttpOnly",
565             "https://example.com/sec/",
566             None,
567             Some(60 * 5),
568         ));
569         inserted!(add_cookie(
570             &mut store,
571             "cookie5=5",
572             "http://example.com/foo/",
573             None,
574             Some(60 * 5),
575         ));
576         inserted!(add_cookie(
577             &mut store,
578             "cookie6=6",
579             "http://example.com/",
580             None,
581             Some(60 * 5),
582         ));
583         inserted!(add_cookie(
584             &mut store,
585             "cookie7=7",
586             "http://bar.example.com/foo/",
587             None,
588             Some(60 * 5),
589         ));
590 
591         inserted!(add_cookie(
592             &mut store,
593             "cookie8=8",
594             "http://example.org/foo/bar",
595             None,
596             Some(60 * 5),
597         ));
598         inserted!(add_cookie(
599             &mut store,
600             "cookie9=9",
601             "http://bar.example.org/foo/bar",
602             None,
603             Some(60 * 5),
604         ));
605         store
606     }
607 
608     macro_rules! check_matches {
609         ($store: expr) => {{
610             values_are!($store, "http://unknowndomain.org/foo/bar", vec![]);
611             values_are!($store, "http://example.org/foo/bar", vec!["8"]);
612             values_are!($store, "http://example.org/bus/bar", vec![]);
613             values_are!($store, "http://bar.example.org/foo/bar", vec!["9"]);
614             values_are!($store, "http://bar.example.org/bus/bar", vec![]);
615             values_are!(
616                 $store,
617                 "https://example.com/sec/foo",
618                 vec!["6", "4", "3", "2"]
619             );
620             values_are!($store, "http://example.com/sec/foo", vec!["6", "3"]);
621             values_are!($store, "ftp://example.com/sec/foo", vec!["6"]);
622             values_are!($store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
623             values_are!(
624                 $store,
625                 "http://example.com/foo/bar/bus",
626                 vec!["1", "5", "6"]
627             );
628         }};
629     }
630 
631     #[test]
insert_raw()632     fn insert_raw() {
633         let mut store = CookieStore::default();
634         inserted!(store.insert_raw(
635             &RawCookie::parse("cookie1=value1").unwrap(),
636             &test_utils::url("http://example.com/foo/bar"),
637         ));
638         non_rel_scheme!(store.insert_raw(
639             &RawCookie::parse("cookie1=value1").unwrap(),
640             &test_utils::url("data:nonrelativescheme"),
641         ));
642         non_http_scheme!(store.insert_raw(
643             &RawCookie::parse("cookie1=value1; HttpOnly").unwrap(),
644             &test_utils::url("ftp://example.com/"),
645         ));
646         expired_existing!(store.insert_raw(
647             &RawCookie::parse("cookie1=value1; Max-Age=0").unwrap(),
648             &test_utils::url("http://example.com/foo/bar"),
649         ));
650         expired_err!(store.insert_raw(
651             &RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(),
652             &test_utils::url("http://example.com/foo/bar"),
653         ));
654         updated!(store.insert_raw(
655             &RawCookie::parse("cookie1=value1").unwrap(),
656             &test_utils::url("http://example.com/foo/bar"),
657         ));
658         expired_existing!(store.insert_raw(
659             &RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(),
660             &test_utils::url("http://example.com/foo/bar"),
661         ));
662         domain_mismatch!(store.insert_raw(
663             &RawCookie::parse("cookie1=value1; Domain=bar.example.com").unwrap(),
664             &test_utils::url("http://example.com/foo/bar"),
665         ));
666     }
667 
668     #[test]
parse()669     fn parse() {
670         let mut store = CookieStore::default();
671         inserted!(store.parse(
672             "cookie1=value1",
673             &test_utils::url("http://example.com/foo/bar"),
674         ));
675         non_rel_scheme!(store.parse("cookie1=value1", &test_utils::url("data:nonrelativescheme"),));
676         non_http_scheme!(store.parse(
677             "cookie1=value1; HttpOnly",
678             &test_utils::url("ftp://example.com/"),
679         ));
680         expired_existing!(store.parse(
681             "cookie1=value1; Max-Age=0",
682             &test_utils::url("http://example.com/foo/bar"),
683         ));
684         expired_err!(store.parse(
685             "cookie1=value1; Max-Age=-1",
686             &test_utils::url("http://example.com/foo/bar"),
687         ));
688         updated!(store.parse(
689             "cookie1=value1",
690             &test_utils::url("http://example.com/foo/bar"),
691         ));
692         expired_existing!(store.parse(
693             "cookie1=value1; Max-Age=-1",
694             &test_utils::url("http://example.com/foo/bar"),
695         ));
696         domain_mismatch!(store.parse(
697             "cookie1=value1; Domain=bar.example.com",
698             &test_utils::url("http://example.com/foo/bar"),
699         ));
700     }
701 
702     #[test]
save()703     fn save() {
704         let mut output = vec![];
705         let mut store = CookieStore::default();
706         store.save_json(&mut output).unwrap();
707         assert_eq!("", from_utf8(&output[..]).unwrap());
708         // non-persistent cookie, should not be saved
709         inserted!(add_cookie(
710             &mut store,
711             "cookie0=value0",
712             "http://example.com/foo/bar",
713             None,
714             None,
715         ));
716         store.save_json(&mut output).unwrap();
717         assert_eq!("", from_utf8(&output[..]).unwrap());
718 
719         // persistent cookie, Max-Age
720         inserted!(add_cookie(
721             &mut store,
722             "cookie1=value1",
723             "http://example.com/foo/bar",
724             None,
725             Some(10),
726         ));
727         store.save_json(&mut output).unwrap();
728         not_has_str!("cookie0=value0", output);
729         has_str!("cookie1=value1", output);
730         output.clear();
731 
732         // persistent cookie, Expires
733         inserted!(add_cookie(
734             &mut store,
735             "cookie2=value2",
736             "http://example.com/foo/bar",
737             Some(test_utils::in_days(1)),
738             None,
739         ));
740         store.save_json(&mut output).unwrap();
741         not_has_str!("cookie0=value0", output);
742         has_str!("cookie1=value1", output);
743         has_str!("cookie2=value2", output);
744         output.clear();
745 
746         inserted!(add_cookie(
747             &mut store,
748             "cookie3=value3; Domain=example.com",
749             "http://foo.example.com/foo/bar",
750             Some(test_utils::in_days(1)),
751             None,
752         ));
753         inserted!(add_cookie(
754             &mut store,
755             "cookie4=value4; Path=/foo/",
756             "http://foo.example.com/foo/bar",
757             Some(test_utils::in_days(1)),
758             None,
759         ));
760         inserted!(add_cookie(
761             &mut store,
762             "cookie5=value5",
763             "http://127.0.0.1/foo/bar",
764             Some(test_utils::in_days(1)),
765             None,
766         ));
767         inserted!(add_cookie(
768             &mut store,
769             "cookie6=value6",
770             "http://[::1]/foo/bar",
771             Some(test_utils::in_days(1)),
772             None,
773         ));
774         inserted!(add_cookie(
775             &mut store,
776             "cookie7=value7; Secure",
777             "https://[::1]/foo/bar",
778             Some(test_utils::in_days(1)),
779             None,
780         ));
781         inserted!(add_cookie(
782             &mut store,
783             "cookie8=value8; HttpOnly",
784             "http://[::1]/foo/bar",
785             Some(test_utils::in_days(1)),
786             None,
787         ));
788         store.save_json(&mut output).unwrap();
789         not_has_str!("cookie0=value0", output);
790         has_str!("cookie1=value1", output);
791         has_str!("cookie2=value2", output);
792         has_str!("cookie3=value3", output);
793         has_str!("cookie4=value4", output);
794         has_str!("cookie5=value5", output);
795         has_str!("cookie6=value6", output);
796         has_str!("cookie7=value7; Secure", output);
797         has_str!("cookie8=value8; HttpOnly", output);
798         output.clear();
799     }
800 
801     #[test]
serialize()802     fn serialize() {
803         let mut output = vec![];
804         let mut store = CookieStore::default();
805         serde_json::to_writer(&mut output, &store).unwrap();
806         assert_eq!("[]", from_utf8(&output[..]).unwrap());
807         output.clear();
808 
809         // non-persistent cookie, should not be saved
810         inserted!(add_cookie(
811             &mut store,
812             "cookie0=value0",
813             "http://example.com/foo/bar",
814             None,
815             None,
816         ));
817         serde_json::to_writer(&mut output, &store).unwrap();
818         assert_eq!("[]", from_utf8(&output[..]).unwrap());
819         output.clear();
820 
821         // persistent cookie, Max-Age
822         inserted!(add_cookie(
823             &mut store,
824             "cookie1=value1",
825             "http://example.com/foo/bar",
826             None,
827             Some(10),
828         ));
829         serde_json::to_writer(&mut output, &store).unwrap();
830         not_has_str!("cookie0=value0", output);
831         has_str!("cookie1=value1", output);
832         output.clear();
833 
834         // persistent cookie, Expires
835         inserted!(add_cookie(
836             &mut store,
837             "cookie2=value2",
838             "http://example.com/foo/bar",
839             Some(test_utils::in_days(1)),
840             None,
841         ));
842         serde_json::to_writer(&mut output, &store).unwrap();
843         not_has_str!("cookie0=value0", output);
844         has_str!("cookie1=value1", output);
845         has_str!("cookie2=value2", output);
846         output.clear();
847 
848         inserted!(add_cookie(
849             &mut store,
850             "cookie3=value3; Domain=example.com",
851             "http://foo.example.com/foo/bar",
852             Some(test_utils::in_days(1)),
853             None,
854         ));
855         inserted!(add_cookie(
856             &mut store,
857             "cookie4=value4; Path=/foo/",
858             "http://foo.example.com/foo/bar",
859             Some(test_utils::in_days(1)),
860             None,
861         ));
862         inserted!(add_cookie(
863             &mut store,
864             "cookie5=value5",
865             "http://127.0.0.1/foo/bar",
866             Some(test_utils::in_days(1)),
867             None,
868         ));
869         inserted!(add_cookie(
870             &mut store,
871             "cookie6=value6",
872             "http://[::1]/foo/bar",
873             Some(test_utils::in_days(1)),
874             None,
875         ));
876         inserted!(add_cookie(
877             &mut store,
878             "cookie7=value7; Secure",
879             "https://[::1]/foo/bar",
880             Some(test_utils::in_days(1)),
881             None,
882         ));
883         inserted!(add_cookie(
884             &mut store,
885             "cookie8=value8; HttpOnly",
886             "http://[::1]/foo/bar",
887             Some(test_utils::in_days(1)),
888             None,
889         ));
890         serde_json::to_writer(&mut output, &store).unwrap();
891         not_has_str!("cookie0=value0", output);
892         has_str!("cookie1=value1", output);
893         has_str!("cookie2=value2", output);
894         has_str!("cookie3=value3", output);
895         has_str!("cookie4=value4", output);
896         has_str!("cookie5=value5", output);
897         has_str!("cookie6=value6", output);
898         has_str!("cookie7=value7; Secure", output);
899         has_str!("cookie8=value8; HttpOnly", output);
900         output.clear();
901     }
902 
903     #[test]
domains()904     fn domains() {
905         let mut store = CookieStore::default();
906         //        The user agent will reject cookies unless the Domain attribute
907         // specifies a scope for the cookie that would include the origin
908         // server.  For example, the user agent will accept a cookie with a
909         // Domain attribute of "example.com" or of "foo.example.com" from
910         // foo.example.com, but the user agent will not accept a cookie with a
911         // Domain attribute of "bar.example.com" or of "baz.foo.example.com".
912         fn domain_cookie_from(domain: &str, request_url: &str) -> Cookie<'static> {
913             let cookie_str = format!("cookie1=value1; Domain={}", domain);
914             Cookie::parse(cookie_str, &test_utils::url(request_url)).unwrap()
915         }
916 
917         {
918             let request_url = test_utils::url("http://foo.example.com");
919             // foo.example.com can submit cookies for example.com and foo.example.com
920             inserted!(store.insert(
921                 domain_cookie_from("example.com", "http://foo.example.com",),
922                 &request_url,
923             ));
924             updated!(store.insert(
925                 domain_cookie_from(".example.com", "http://foo.example.com",),
926                 &request_url,
927             ));
928             inserted!(store.insert(
929                 domain_cookie_from("foo.example.com", "http://foo.example.com",),
930                 &request_url,
931             ));
932             updated!(store.insert(
933                 domain_cookie_from(".foo.example.com", "http://foo.example.com",),
934                 &request_url,
935             ));
936             // not for bar.example.com
937             domain_mismatch!(store.insert(
938                 domain_cookie_from("bar.example.com", "http://bar.example.com",),
939                 &request_url,
940             ));
941             domain_mismatch!(store.insert(
942                 domain_cookie_from(".bar.example.com", "http://bar.example.com",),
943                 &request_url,
944             ));
945             // not for bar.foo.example.com
946             domain_mismatch!(store.insert(
947                 domain_cookie_from("bar.foo.example.com", "http://bar.foo.example.com",),
948                 &request_url,
949             ));
950             domain_mismatch!(store.insert(
951                 domain_cookie_from(".bar.foo.example.com", "http://bar.foo.example.com",),
952                 &request_url,
953             ));
954         }
955 
956         {
957             let request_url = test_utils::url("http://bar.example.com");
958             // bar.example.com can submit for example.com and bar.example.com
959             updated!(store.insert(
960                 domain_cookie_from("example.com", "http://foo.example.com",),
961                 &request_url,
962             ));
963             updated!(store.insert(
964                 domain_cookie_from(".example.com", "http://foo.example.com",),
965                 &request_url,
966             ));
967             inserted!(store.insert(
968                 domain_cookie_from("bar.example.com", "http://bar.example.com",),
969                 &request_url,
970             ));
971             updated!(store.insert(
972                 domain_cookie_from(".bar.example.com", "http://bar.example.com",),
973                 &request_url,
974             ));
975             // bar.example.com cannot submit for foo.example.com
976             domain_mismatch!(store.insert(
977                 domain_cookie_from("foo.example.com", "http://foo.example.com",),
978                 &request_url,
979             ));
980             domain_mismatch!(store.insert(
981                 domain_cookie_from(".foo.example.com", "http://foo.example.com",),
982                 &request_url,
983             ));
984         }
985         {
986             let request_url = test_utils::url("http://example.com");
987             // example.com can submit for example.com
988             updated!(store.insert(
989                 domain_cookie_from("example.com", "http://foo.example.com",),
990                 &request_url,
991             ));
992             updated!(store.insert(
993                 domain_cookie_from(".example.com", "http://foo.example.com",),
994                 &request_url,
995             ));
996             // example.com cannot submit for foo.example.com or bar.example.com
997             domain_mismatch!(store.insert(
998                 domain_cookie_from("foo.example.com", "http://foo.example.com",),
999                 &request_url,
1000             ));
1001             domain_mismatch!(store.insert(
1002                 domain_cookie_from(".foo.example.com", "http://foo.example.com",),
1003                 &request_url,
1004             ));
1005             domain_mismatch!(store.insert(
1006                 domain_cookie_from("bar.example.com", "http://bar.example.com",),
1007                 &request_url,
1008             ));
1009             domain_mismatch!(store.insert(
1010                 domain_cookie_from(".bar.example.com", "http://bar.example.com",),
1011                 &request_url,
1012             ));
1013         }
1014     }
1015 
1016     #[test]
http_only()1017     fn http_only() {
1018         let mut store = CookieStore::default();
1019         let c = Cookie::parse(
1020             "cookie1=value1; HttpOnly",
1021             &test_utils::url("http://example.com/foo/bar"),
1022         )
1023         .unwrap();
1024         // cannot add a HttpOnly cookies from a non-http source
1025         non_http_scheme!(store.insert(c, &test_utils::url("ftp://example.com/foo/bar"),));
1026     }
1027 
1028     #[test]
load()1029     fn load() {
1030         let mut store = CookieStore::default();
1031         // non-persistent cookie, should not be saved
1032         inserted!(add_cookie(
1033             &mut store,
1034             "cookie0=value0",
1035             "http://example.com/foo/bar",
1036             None,
1037             None,
1038         ));
1039         // persistent cookie, Max-Age
1040         inserted!(add_cookie(
1041             &mut store,
1042             "cookie1=value1",
1043             "http://example.com/foo/bar",
1044             None,
1045             Some(10),
1046         ));
1047         // persistent cookie, Expires
1048         inserted!(add_cookie(
1049             &mut store,
1050             "cookie2=value2",
1051             "http://example.com/foo/bar",
1052             Some(test_utils::in_days(1)),
1053             None,
1054         ));
1055         inserted!(add_cookie(
1056             &mut store,
1057             "cookie3=value3; Domain=example.com",
1058             "http://foo.example.com/foo/bar",
1059             Some(test_utils::in_days(1)),
1060             None,
1061         ));
1062         inserted!(add_cookie(
1063             &mut store,
1064             "cookie4=value4; Path=/foo/",
1065             "http://foo.example.com/foo/bar",
1066             Some(test_utils::in_days(1)),
1067             None,
1068         ));
1069         inserted!(add_cookie(
1070             &mut store,
1071             "cookie5=value5",
1072             "http://127.0.0.1/foo/bar",
1073             Some(test_utils::in_days(1)),
1074             None,
1075         ));
1076         inserted!(add_cookie(
1077             &mut store,
1078             "cookie6=value6",
1079             "http://[::1]/foo/bar",
1080             Some(test_utils::in_days(1)),
1081             None,
1082         ));
1083         inserted!(add_cookie(
1084             &mut store,
1085             "cookie7=value7; Secure",
1086             "http://example.com/foo/bar",
1087             Some(test_utils::in_days(1)),
1088             None,
1089         ));
1090         inserted!(add_cookie(
1091             &mut store,
1092             "cookie8=value8; HttpOnly",
1093             "http://example.com/foo/bar",
1094             Some(test_utils::in_days(1)),
1095             None,
1096         ));
1097         let mut output = vec![];
1098         store.save_json(&mut output).unwrap();
1099         not_has_str!("cookie0=value0", output);
1100         has_str!("cookie1=value1", output);
1101         has_str!("cookie2=value2", output);
1102         has_str!("cookie3=value3", output);
1103         has_str!("cookie4=value4", output);
1104         has_str!("cookie5=value5", output);
1105         has_str!("cookie6=value6", output);
1106         has_str!("cookie7=value7; Secure", output);
1107         has_str!("cookie8=value8; HttpOnly", output);
1108         let store = CookieStore::load_json(&output[..]).unwrap();
1109         assert!(store.get("example.com", "/foo", "cookie0").is_none());
1110         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
1111         assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2");
1112         assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3");
1113         assert!(
1114             store
1115                 .get("foo.example.com", "/foo/", "cookie4")
1116                 .unwrap()
1117                 .value()
1118                 == "value4"
1119         );
1120         assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5");
1121         assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6");
1122         assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7");
1123         assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8");
1124 
1125         output.clear();
1126         let store = make_match_store();
1127         store.save_json(&mut output).unwrap();
1128         let store = CookieStore::load_json(&output[..]).unwrap();
1129         check_matches!(&store);
1130     }
1131 
1132     #[test]
deserialize()1133     fn deserialize() {
1134         let mut store = CookieStore::default();
1135         // non-persistent cookie, should not be saved
1136         inserted!(add_cookie(
1137             &mut store,
1138             "cookie0=value0",
1139             "http://example.com/foo/bar",
1140             None,
1141             None,
1142         ));
1143         // persistent cookie, Max-Age
1144         inserted!(add_cookie(
1145             &mut store,
1146             "cookie1=value1",
1147             "http://example.com/foo/bar",
1148             None,
1149             Some(10),
1150         ));
1151         // persistent cookie, Expires
1152         inserted!(add_cookie(
1153             &mut store,
1154             "cookie2=value2",
1155             "http://example.com/foo/bar",
1156             Some(test_utils::in_days(1)),
1157             None,
1158         ));
1159         inserted!(add_cookie(
1160             &mut store,
1161             "cookie3=value3; Domain=example.com",
1162             "http://foo.example.com/foo/bar",
1163             Some(test_utils::in_days(1)),
1164             None,
1165         ));
1166         inserted!(add_cookie(
1167             &mut store,
1168             "cookie4=value4; Path=/foo/",
1169             "http://foo.example.com/foo/bar",
1170             Some(test_utils::in_days(1)),
1171             None,
1172         ));
1173         inserted!(add_cookie(
1174             &mut store,
1175             "cookie5=value5",
1176             "http://127.0.0.1/foo/bar",
1177             Some(test_utils::in_days(1)),
1178             None,
1179         ));
1180         inserted!(add_cookie(
1181             &mut store,
1182             "cookie6=value6",
1183             "http://[::1]/foo/bar",
1184             Some(test_utils::in_days(1)),
1185             None,
1186         ));
1187         inserted!(add_cookie(
1188             &mut store,
1189             "cookie7=value7; Secure",
1190             "http://example.com/foo/bar",
1191             Some(test_utils::in_days(1)),
1192             None,
1193         ));
1194         inserted!(add_cookie(
1195             &mut store,
1196             "cookie8=value8; HttpOnly",
1197             "http://example.com/foo/bar",
1198             Some(test_utils::in_days(1)),
1199             None,
1200         ));
1201         let mut output = vec![];
1202         serde_json::to_writer(&mut output, &store).unwrap();
1203         not_has_str!("cookie0=value0", output);
1204         has_str!("cookie1=value1", output);
1205         has_str!("cookie2=value2", output);
1206         has_str!("cookie3=value3", output);
1207         has_str!("cookie4=value4", output);
1208         has_str!("cookie5=value5", output);
1209         has_str!("cookie6=value6", output);
1210         has_str!("cookie7=value7; Secure", output);
1211         has_str!("cookie8=value8; HttpOnly", output);
1212         let store: CookieStore = serde_json::from_reader(&output[..]).unwrap();
1213         assert!(store.get("example.com", "/foo", "cookie0").is_none());
1214         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
1215         assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2");
1216         assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3");
1217         assert!(
1218             store
1219                 .get("foo.example.com", "/foo/", "cookie4")
1220                 .unwrap()
1221                 .value()
1222                 == "value4"
1223         );
1224         assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5");
1225         assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6");
1226         assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7");
1227         assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8");
1228 
1229         output.clear();
1230         let store = make_match_store();
1231         serde_json::to_writer(&mut output, &store).unwrap();
1232         let store: CookieStore = serde_json::from_reader(&output[..]).unwrap();
1233         check_matches!(&store);
1234     }
1235 
1236     #[test]
clear()1237     fn clear() {
1238         let mut output = vec![];
1239         let mut store = CookieStore::default();
1240         inserted!(add_cookie(
1241             &mut store,
1242             "cookie1=value1",
1243             "http://example.com/foo/bar",
1244             Some(test_utils::in_days(1)),
1245             None,
1246         ));
1247         store.save_json(&mut output).unwrap();
1248         has_str!("cookie1=value1", output);
1249         output.clear();
1250         store.clear();
1251         store.save_json(&mut output).unwrap();
1252         assert_eq!("", from_utf8(&output[..]).unwrap());
1253     }
1254 
1255     #[test]
add_and_get()1256     fn add_and_get() {
1257         let mut store = CookieStore::default();
1258         assert!(store.get("example.com", "/foo", "cookie1").is_none());
1259 
1260         inserted!(add_cookie(
1261             &mut store,
1262             "cookie1=value1",
1263             "http://example.com/foo/bar",
1264             None,
1265             None,
1266         ));
1267         assert!(store.get("example.com", "/foo/bar", "cookie1").is_none());
1268         assert!(store.get("example.com", "/foo", "cookie2").is_none());
1269         assert!(store.get("example.org", "/foo", "cookie1").is_none());
1270         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1");
1271 
1272         updated!(add_cookie(
1273             &mut store,
1274             "cookie1=value2",
1275             "http://example.com/foo/bar",
1276             None,
1277             None,
1278         ));
1279         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
1280 
1281         inserted!(add_cookie(
1282             &mut store,
1283             "cookie2=value3",
1284             "http://example.com/foo/bar",
1285             None,
1286             None,
1287         ));
1288         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
1289         assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
1290 
1291         inserted!(add_cookie(
1292             &mut store,
1293             "cookie3=value4; HttpOnly",
1294             "http://example.com/foo/bar",
1295             None,
1296             None,
1297         ));
1298         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
1299         assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
1300         assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4");
1301 
1302         non_http_scheme!(add_cookie(
1303             &mut store,
1304             "cookie3=value5",
1305             "ftp://example.com/foo/bar",
1306             None,
1307             None,
1308         ));
1309         assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2");
1310         assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3");
1311         assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4");
1312     }
1313 
1314     #[test]
matches()1315     fn matches() {
1316         let store = make_match_store();
1317         check_matches!(&store);
1318     }
1319 
1320     #[test]
expiry()1321     fn expiry() {
1322         let mut store = make_match_store();
1323         let request_url = test_utils::url("http://foo.example.com");
1324         let expired_cookie = Cookie::parse("cookie1=value1; Max-Age=-1", &request_url).unwrap();
1325         expired_err!(store.insert(expired_cookie, &request_url));
1326         check_matches!(&store);
1327         match store.get_mut("example.com", "/", "cookie6") {
1328             Some(cookie) => cookie.expire(),
1329             None => unreachable!(),
1330         }
1331         values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
1332         values_are!(store, "http://example.org/foo/bar", vec!["8"]);
1333         values_are!(store, "http://example.org/bus/bar", vec![]);
1334         values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
1335         values_are!(store, "http://bar.example.org/bus/bar", vec![]);
1336         values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
1337         values_are!(store, "http://example.com/sec/foo", vec!["3"]);
1338         values_are!(store, "ftp://example.com/sec/foo", vec![]);
1339         values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
1340         values_are!(store, "http://example.com/foo/bar/bus", vec!["1", "5"]);
1341         match store.get_any("example.com", "/", "cookie6") {
1342             Some(cookie) => assert!(cookie.is_expired()),
1343             None => unreachable!(),
1344         }
1345         // inserting an expired cookie that matches an existing cookie should expire
1346         // the existing
1347         let request_url = test_utils::url("http://example.com/foo/");
1348         let expired_cookie = Cookie::parse("cookie5=value5; Max-Age=-1", &request_url).unwrap();
1349         expired_existing!(store.insert(expired_cookie, &request_url));
1350         values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
1351         values_are!(store, "http://example.org/foo/bar", vec!["8"]);
1352         values_are!(store, "http://example.org/bus/bar", vec![]);
1353         values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
1354         values_are!(store, "http://bar.example.org/bus/bar", vec![]);
1355         values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
1356         values_are!(store, "http://example.com/sec/foo", vec!["3"]);
1357         values_are!(store, "ftp://example.com/sec/foo", vec![]);
1358         values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
1359         values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]);
1360         match store.get_any("example.com", "/foo", "cookie5") {
1361             Some(cookie) => assert!(cookie.is_expired()),
1362             None => unreachable!(),
1363         }
1364         // save and loading the store should drop any expired cookies
1365         let mut output = vec![];
1366         store.save_json(&mut output).unwrap();
1367         store = CookieStore::load_json(&output[..]).unwrap();
1368         values_are!(store, "http://unknowndomain.org/foo/bar", vec![]);
1369         values_are!(store, "http://example.org/foo/bar", vec!["8"]);
1370         values_are!(store, "http://example.org/bus/bar", vec![]);
1371         values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]);
1372         values_are!(store, "http://bar.example.org/bus/bar", vec![]);
1373         values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]);
1374         values_are!(store, "http://example.com/sec/foo", vec!["3"]);
1375         values_are!(store, "ftp://example.com/sec/foo", vec![]);
1376         values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]);
1377         values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]);
1378         assert!(store.get_any("example.com", "/", "cookie6").is_none());
1379         assert!(store.get_any("example.com", "/foo", "cookie5").is_none());
1380     }
1381 
1382     #[test]
non_persistent()1383     fn non_persistent() {
1384         let mut store = make_match_store();
1385         check_matches!(&store);
1386         let request_url = test_utils::url("http://example.com/tmp/");
1387         let non_persistent = Cookie::parse("cookie10=value10", &request_url).unwrap();
1388         inserted!(store.insert(non_persistent, &request_url));
1389         match store.get("example.com", "/tmp", "cookie10") {
1390             None => unreachable!(),
1391             Some(cookie) => assert_eq!("value10", cookie.value()),
1392         }
1393         // save and loading the store should drop any non-persistent cookies
1394         let mut output = vec![];
1395         store.save_json(&mut output).unwrap();
1396         store = CookieStore::load_json(&output[..]).unwrap();
1397         check_matches!(&store);
1398         assert!(store.get("example.com", "/tmp", "cookie10").is_none());
1399         assert!(store.get_any("example.com", "/tmp", "cookie10").is_none());
1400     }
1401 
1402     macro_rules! dump {
1403         ($e: expr, $i: ident) => {{
1404             use serde_json;
1405             println!("");
1406             println!(
1407                 "==== {}: {} ====",
1408                 $e,
1409                 time::OffsetDateTime::now_utc().format(crate::rfc3339_fmt::RFC3339_FORMAT)
1410             );
1411             for c in $i.iter_any() {
1412                 println!(
1413                     "{} {}",
1414                     if c.is_expired() {
1415                         "XXXXX"
1416                     } else if c.is_persistent() {
1417                         "PPPPP"
1418                     } else {
1419                         "     "
1420                     },
1421                     serde_json::to_string(c).unwrap()
1422                 );
1423                 println!("----------------");
1424             }
1425             println!("================");
1426         }};
1427     }
1428 
matches_are(store: &CookieStore, url: &str, exp: Vec<&str>)1429     fn matches_are(store: &CookieStore, url: &str, exp: Vec<&str>) {
1430         let matches = store
1431             .matches(&test_utils::url(url))
1432             .iter()
1433             .map(|c| format!("{}={}", c.name(), c.value()))
1434             .collect::<Vec<_>>();
1435         for e in &exp {
1436             assert!(
1437                 matches.iter().any(|m| &m[..] == *e),
1438                 "{}: matches missing '{}'\nmatches: {:?}\n    exp: {:?}",
1439                 url,
1440                 e,
1441                 matches,
1442                 exp
1443             );
1444         }
1445         assert!(
1446             matches.len() == exp.len(),
1447             "{}: matches={:?} != exp={:?}",
1448             url,
1449             matches,
1450             exp
1451         );
1452     }
1453 
1454     #[test]
domain_collisions()1455     fn domain_collisions() {
1456         let mut store = CookieStore::default();
1457         // - HostOnly, so no collisions here
1458         inserted!(add_cookie(
1459             &mut store,
1460             "cookie1=1a",
1461             "http://foo.bus.example.com/",
1462             None,
1463             None,
1464         ));
1465         inserted!(add_cookie(
1466             &mut store,
1467             "cookie1=1b",
1468             "http://bus.example.com/",
1469             None,
1470             None,
1471         ));
1472         // - Suffix
1473         // both cookie2's domain-match bus.example.com
1474         inserted!(add_cookie(
1475             &mut store,
1476             "cookie2=2a; Domain=bus.example.com",
1477             "http://foo.bus.example.com/",
1478             None,
1479             None,
1480         ));
1481         inserted!(add_cookie(
1482             &mut store,
1483             "cookie2=2b; Domain=example.com",
1484             "http://bus.example.com/",
1485             None,
1486             None,
1487         ));
1488         dump!("domain_collisions", store);
1489         matches_are(
1490             &store,
1491             "http://foo.bus.example.com/",
1492             vec!["cookie1=1a", "cookie2=2a", "cookie2=2b"],
1493         );
1494         matches_are(
1495             &store,
1496             "http://bus.example.com/",
1497             vec!["cookie1=1b", "cookie2=2a", "cookie2=2b"],
1498         );
1499         matches_are(&store, "http://example.com/", vec!["cookie2=2b"]);
1500         matches_are(&store, "http://foo.example.com/", vec!["cookie2=2b"]);
1501     }
1502 
1503     #[test]
path_collisions()1504     fn path_collisions() {
1505         let mut store = CookieStore::default();
1506         // will be default-path: /foo/bar, and /foo, resp.
1507         // both should match /foo/bar/
1508         inserted!(add_cookie(
1509             &mut store,
1510             "cookie3=3a",
1511             "http://bus.example.com/foo/bar/",
1512             None,
1513             None,
1514         ));
1515         inserted!(add_cookie(
1516             &mut store,
1517             "cookie3=3b",
1518             "http://bus.example.com/foo/",
1519             None,
1520             None,
1521         ));
1522         // - Path set explicitly
1523         inserted!(add_cookie(
1524             &mut store,
1525             "cookie4=4a; Path=/foo/bar/",
1526             "http://bus.example.com/",
1527             None,
1528             None,
1529         ));
1530         inserted!(add_cookie(
1531             &mut store,
1532             "cookie4=4b; Path=/foo/",
1533             "http://bus.example.com/",
1534             None,
1535             None,
1536         ));
1537         dump!("path_collisions", store);
1538         matches_are(
1539             &store,
1540             "http://bus.example.com/foo/bar/",
1541             vec!["cookie3=3a", "cookie3=3b", "cookie4=4a", "cookie4=4b"],
1542         );
1543         // Agrees w/ chrome, but not FF... FF also sends cookie4=4a, but this should be
1544         // a path-match
1545         // fail since request-uri /foo/bar is a *prefix* of the cookie path /foo/bar/
1546         matches_are(
1547             &store,
1548             "http://bus.example.com/foo/bar",
1549             vec!["cookie3=3a", "cookie3=3b", "cookie4=4b"],
1550         );
1551         matches_are(
1552             &store,
1553             "http://bus.example.com/foo/ba",
1554             vec!["cookie3=3b", "cookie4=4b"],
1555         );
1556         matches_are(
1557             &store,
1558             "http://bus.example.com/foo/",
1559             vec!["cookie3=3b", "cookie4=4b"],
1560         );
1561         // Agrees w/ chrome, but not FF... FF also sends cookie4=4b, but this should be
1562         // a path-match
1563         // fail since request-uri /foo is a *prefix* of the cookie path /foo/
1564         matches_are(&store, "http://bus.example.com/foo", vec!["cookie3=3b"]);
1565         matches_are(&store, "http://bus.example.com/fo", vec![]);
1566         matches_are(&store, "http://bus.example.com/", vec![]);
1567         matches_are(&store, "http://bus.example.com", vec![]);
1568     }
1569 }
1570