cookie_store/
cookie_store.rs

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