cookie/
jar.rs

1use std::collections::HashSet;
2
3#[cfg(feature = "signed")] use crate::secure::SignedJar;
4#[cfg(feature = "private")] use crate::secure::PrivateJar;
5#[cfg(any(feature = "signed", feature = "private"))] use crate::secure::Key;
6
7use crate::delta::DeltaCookie;
8use crate::prefix::{Prefix, PrefixedJar};
9use crate::Cookie;
10
11/// A collection of cookies that tracks its modifications.
12///
13/// A `CookieJar` provides storage for any number of cookies. Any changes made
14/// to the jar are tracked; the changes can be retrieved via the
15/// [`delta`](#method.delta) method which returns an iterator over the changes.
16///
17/// # Usage
18///
19/// A jar's life begins via [`CookieJar::new()`] and calls to
20/// [`add_original()`](#method.add_original):
21///
22/// ```rust
23/// use cookie::{Cookie, CookieJar};
24///
25/// let mut jar = CookieJar::new();
26/// jar.add_original(("name", "value"));
27/// jar.add_original(("second", "another"));
28/// jar.add_original(Cookie::build(("third", "again")).path("/"));
29/// ```
30///
31/// Cookies can be added via [`CookieJar::add()`] and removed via
32/// [`CookieJar::remove()`]. Note that any `T: Into<Cookie>` can be passed into
33/// these methods; see [`Cookie::build()`] for a table of implementing types.
34///
35/// Finally, cookies can be retrieved with [`CookieJar::get()`].
36///
37/// ```rust
38/// # use cookie::{Cookie, CookieJar};
39/// let mut jar = CookieJar::new();
40/// jar.add(("a", "one"));
41/// jar.add(("b", "two"));
42///
43/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one"));
44/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two"));
45///
46/// jar.remove("b");
47/// assert!(jar.get("b").is_none());
48/// ```
49///
50/// # Deltas
51///
52/// A jar keeps track of any modifications made to it over time. The
53/// modifications are recorded as cookies. The modifications can be retrieved
54/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add`
55/// results in the same `Cookie` appearing in the `delta`; cookies added via
56/// `add_original` do not count towards the delta. Any _original_ cookie that is
57/// removed from a jar results in a "removal" cookie appearing in the delta. A
58/// "removal" cookie is a cookie that a server sends so that the cookie is
59/// removed from the client's machine.
60///
61/// Deltas are typically used to create `Set-Cookie` headers corresponding to
62/// the changes made to a cookie jar over a period of time.
63///
64/// ```rust
65/// # use cookie::{Cookie, CookieJar};
66/// let mut jar = CookieJar::new();
67///
68/// // original cookies don't affect the delta
69/// jar.add_original(("original", "value"));
70/// assert_eq!(jar.delta().count(), 0);
71///
72/// // new cookies result in an equivalent `Cookie` in the delta
73/// jar.add(("a", "one"));
74/// jar.add(("b", "two"));
75/// assert_eq!(jar.delta().count(), 2);
76///
77/// // removing an original cookie adds a "removal" cookie to the delta
78/// jar.remove("original");
79/// assert_eq!(jar.delta().count(), 3);
80///
81/// // removing a new cookie that was added removes that `Cookie` from the delta
82/// jar.remove("a");
83/// assert_eq!(jar.delta().count(), 2);
84/// ```
85#[derive(Default, Debug, Clone)]
86pub struct CookieJar {
87    original_cookies: HashSet<DeltaCookie>,
88    delta_cookies: HashSet<DeltaCookie>,
89}
90
91impl CookieJar {
92    /// Creates an empty cookie jar.
93    ///
94    /// # Example
95    ///
96    /// ```rust
97    /// use cookie::CookieJar;
98    ///
99    /// let jar = CookieJar::new();
100    /// assert_eq!(jar.iter().count(), 0);
101    /// ```
102    pub fn new() -> CookieJar {
103        CookieJar::default()
104    }
105
106    /// Returns a reference to the `Cookie` inside this jar with the name
107    /// `name`. If no such cookie exists, returns `None`.
108    ///
109    /// # Example
110    ///
111    /// ```rust
112    /// use cookie::{CookieJar, Cookie};
113    ///
114    /// let mut jar = CookieJar::new();
115    /// assert!(jar.get("name").is_none());
116    ///
117    /// jar.add(("name", "value"));
118    /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
119    /// ```
120    pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
121        self.delta_cookies
122            .get(name)
123            .or_else(|| self.original_cookies.get(name))
124            .and_then(|c| if c.removed { None } else { Some(&c.cookie) })
125    }
126
127    /// Adds an "original" `cookie` to this jar. If an original cookie with the
128    /// same name already exists, it is replaced with `cookie`. Cookies added
129    /// with `add` take precedence and are not replaced by this method.
130    ///
131    /// Adding an original cookie does not affect the [delta](#method.delta)
132    /// computation. This method is intended to be used to seed the cookie jar
133    /// with cookies received from a client's HTTP message.
134    ///
135    /// For accurate `delta` computations, this method should not be called
136    /// after calling `remove`.
137    ///
138    /// # Example
139    ///
140    /// ```rust
141    /// use cookie::{CookieJar, Cookie};
142    ///
143    /// let mut jar = CookieJar::new();
144    /// jar.add_original(("name", "value"));
145    /// jar.add_original(("second", "two"));
146    ///
147    /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
148    /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
149    /// assert_eq!(jar.iter().count(), 2);
150    /// assert_eq!(jar.delta().count(), 0);
151    /// ```
152    pub fn add_original<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
153        self.original_cookies.replace(DeltaCookie::added(cookie.into()));
154    }
155
156    /// Adds `cookie` to this jar. If a cookie with the same name already
157    /// exists, it is replaced with `cookie`.
158    ///
159    /// # Example
160    ///
161    /// ```rust
162    /// use cookie::{CookieJar, Cookie};
163    ///
164    /// let mut jar = CookieJar::new();
165    /// jar.add(("name", "value"));
166    /// jar.add(("second", "two"));
167    ///
168    /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
169    /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
170    /// assert_eq!(jar.iter().count(), 2);
171    /// assert_eq!(jar.delta().count(), 2);
172    /// ```
173    pub fn add<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
174        self.delta_cookies.replace(DeltaCookie::added(cookie.into()));
175    }
176
177    /// Removes `cookie` from this jar. If an _original_ cookie with the same
178    /// name as `cookie` is present in the jar, a _removal_ cookie will be
179    /// present in the `delta` computation. **To properly generate the removal
180    /// cookie, `cookie` must contain the same `path` and `domain` as the cookie
181    /// that was initially set.**
182    ///
183    /// A "removal" cookie is a cookie that has the same name as the original
184    /// cookie but has an empty value, a max-age of 0, and an expiration date
185    /// far in the past. See also [`Cookie::make_removal()`].
186    ///
187    /// # Example
188    ///
189    /// Removing an _original_ cookie results in a _removal_ cookie:
190    ///
191    /// ```rust
192    /// use cookie::{CookieJar, Cookie};
193    /// use cookie::time::Duration;
194    ///
195    /// let mut jar = CookieJar::new();
196    ///
197    /// // Assume this cookie originally had a path of "/" and domain of "a.b".
198    /// jar.add_original(("name", "value"));
199    ///
200    /// // If the path and domain were set, they must be provided to `remove`.
201    /// jar.remove(Cookie::build("name").path("/").domain("a.b"));
202    ///
203    /// // The delta will contain the removal cookie.
204    /// let delta: Vec<_> = jar.delta().collect();
205    /// assert_eq!(delta.len(), 1);
206    /// assert_eq!(delta[0].name(), "name");
207    /// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
208    /// ```
209    ///
210    /// Removing a new cookie does not result in a _removal_ cookie unless
211    /// there's an original cookie with the same name:
212    ///
213    /// ```rust
214    /// use cookie::{CookieJar, Cookie};
215    ///
216    /// let mut jar = CookieJar::new();
217    /// jar.add(("name", "value"));
218    /// assert_eq!(jar.delta().count(), 1);
219    ///
220    /// jar.remove("name");
221    /// assert_eq!(jar.delta().count(), 0);
222    ///
223    /// jar.add_original(("name", "value"));
224    /// jar.add(("name", "value"));
225    /// assert_eq!(jar.delta().count(), 1);
226    ///
227    /// jar.remove("name");
228    /// assert_eq!(jar.delta().count(), 1);
229    /// ```
230    pub fn remove<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
231        let mut cookie = cookie.into();
232        if self.original_cookies.contains(cookie.name()) {
233            cookie.make_removal();
234            self.delta_cookies.replace(DeltaCookie::removed(cookie));
235        } else {
236            self.delta_cookies.remove(cookie.name());
237        }
238    }
239
240    /// Removes `cookie` from this jar completely.
241    ///
242    /// This method differs from `remove` in that no delta cookie is created
243    /// under any condition. Thus, no path or domain are needed: only the
244    /// cookie's name. Neither the `delta` nor `iter` methods will return a
245    /// cookie that is removed using this method.
246    ///
247    /// # Example
248    ///
249    /// Removing an _original_ cookie; no _removal_ cookie is generated:
250    ///
251    /// ```rust
252    /// # extern crate cookie;
253    /// use cookie::{CookieJar, Cookie};
254    /// use cookie::time::Duration;
255    ///
256    /// # fn main() {
257    /// let mut jar = CookieJar::new();
258    ///
259    /// // Add an original cookie and a new cookie.
260    /// jar.add_original(("name", "value"));
261    /// jar.add(("key", "value"));
262    /// assert_eq!(jar.delta().count(), 1);
263    /// assert_eq!(jar.iter().count(), 2);
264    ///
265    /// // Now force remove the original cookie.
266    /// jar.force_remove("name");
267    /// assert_eq!(jar.delta().count(), 1);
268    /// assert_eq!(jar.iter().count(), 1);
269    ///
270    /// // Now force remove the new cookie. `to_string()` for illustration only.
271    /// jar.force_remove("key".to_string());
272    /// assert_eq!(jar.delta().count(), 0);
273    /// assert_eq!(jar.iter().count(), 0);
274    /// # }
275    /// ```
276    pub fn force_remove<N: AsRef<str>>(&mut self, name: N) {
277        self.original_cookies.remove(name.as_ref());
278        self.delta_cookies.remove(name.as_ref());
279    }
280
281    /// Removes all delta cookies, i.e. all cookies not added via
282    /// [`CookieJar::add_original()`], from this `CookieJar`. This undoes any
283    /// changes from [`CookieJar::add()`] and [`CookieJar::remove()`]
284    /// operations.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// use cookie::{CookieJar, Cookie};
290    ///
291    /// let mut jar = CookieJar::new();
292    ///
293    /// // Only original cookies will remain after calling `reset_delta`.
294    /// jar.add_original(("name", "value"));
295    /// jar.add_original(("language", "Rust"));
296    ///
297    /// // These operations, represented by delta cookies, will be reset.
298    /// jar.add(("language", "C++"));
299    /// jar.remove("name");
300    ///
301    /// // All is normal.
302    /// assert_eq!(jar.get("name"), None);
303    /// assert_eq!(jar.get("language").map(Cookie::value), Some("C++"));
304    /// assert_eq!(jar.iter().count(), 1);
305    /// assert_eq!(jar.delta().count(), 2);
306    ///
307    /// // Resetting undoes delta operations.
308    /// jar.reset_delta();
309    /// assert_eq!(jar.get("name").map(Cookie::value), Some("value"));
310    /// assert_eq!(jar.get("language").map(Cookie::value), Some("Rust"));
311    /// assert_eq!(jar.iter().count(), 2);
312    /// assert_eq!(jar.delta().count(), 0);
313    /// ```
314    pub fn reset_delta(&mut self) {
315        self.delta_cookies = HashSet::new();
316    }
317
318    /// Returns an iterator over cookies that represent the changes to this jar
319    /// over time. These cookies can be rendered directly as `Set-Cookie` header
320    /// values to affect the changes made to this jar on the client.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use cookie::{CookieJar, Cookie};
326    ///
327    /// let mut jar = CookieJar::new();
328    /// jar.add_original(("name", "value"));
329    /// jar.add_original(("second", "two"));
330    ///
331    /// // Add new cookies.
332    /// jar.add(("new", "third"));
333    /// jar.add(("another", "fourth"));
334    /// jar.add(("yac", "fifth"));
335    ///
336    /// // Remove some cookies.
337    /// jar.remove(("name"));
338    /// jar.remove(("another"));
339    ///
340    /// // Delta contains two new cookies ("new", "yac") and a removal ("name").
341    /// assert_eq!(jar.delta().count(), 3);
342    /// ```
343    pub fn delta(&self) -> Delta {
344        Delta { iter: self.delta_cookies.iter() }
345    }
346
347    /// Returns an iterator over all of the cookies present in this jar.
348    ///
349    /// # Example
350    ///
351    /// ```rust
352    /// use cookie::{CookieJar, Cookie};
353    ///
354    /// let mut jar = CookieJar::new();
355    ///
356    /// jar.add_original(("name", "value"));
357    /// jar.add_original(("second", "two"));
358    ///
359    /// jar.add(("new", "third"));
360    /// jar.add(("another", "fourth"));
361    /// jar.add(("yac", "fifth"));
362    ///
363    /// jar.remove("name");
364    /// jar.remove("another");
365    ///
366    /// // There are three cookies in the jar: "second", "new", and "yac".
367    /// # assert_eq!(jar.iter().count(), 3);
368    /// for cookie in jar.iter() {
369    ///     match cookie.name() {
370    ///         "second" => assert_eq!(cookie.value(), "two"),
371    ///         "new" => assert_eq!(cookie.value(), "third"),
372    ///         "yac" => assert_eq!(cookie.value(), "fifth"),
373    ///         _ => unreachable!("there are only three cookies in the jar")
374    ///     }
375    /// }
376    /// ```
377    pub fn iter(&self) -> Iter {
378        Iter {
379            delta_cookies: self.delta_cookies.iter()
380                .chain(self.original_cookies.difference(&self.delta_cookies)),
381        }
382    }
383
384    /// Returns a read-only `PrivateJar` with `self` as its parent jar using the
385    /// key `key` to verify/decrypt cookies retrieved from the child jar. Any
386    /// retrievals from the child jar will be made from the parent jar.
387    ///
388    /// # Example
389    ///
390    /// ```rust
391    /// use cookie::{Cookie, CookieJar, Key};
392    ///
393    /// // Generate a secure key.
394    /// let key = Key::generate();
395    ///
396    /// // Add a private (signed + encrypted) cookie.
397    /// let mut jar = CookieJar::new();
398    /// jar.private_mut(&key).add(("private", "text"));
399    ///
400    /// // The cookie's contents are encrypted.
401    /// assert_ne!(jar.get("private").unwrap().value(), "text");
402    ///
403    /// // They can be decrypted and verified through the child jar.
404    /// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text");
405    ///
406    /// // A tampered with cookie does not validate but still exists.
407    /// let mut cookie = jar.get("private").unwrap().clone();
408    /// jar.add(("private", cookie.value().to_string() + "!"));
409    /// assert!(jar.private(&key).get("private").is_none());
410    /// assert!(jar.get("private").is_some());
411    /// ```
412    #[cfg(feature = "private")]
413    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
414    pub fn private<'a>(&'a self, key: &Key) -> PrivateJar<&'a Self> {
415        PrivateJar::new(self, key)
416    }
417
418    /// Returns a read/write `PrivateJar` with `self` as its parent jar using
419    /// the key `key` to sign/encrypt and verify/decrypt cookies added/retrieved
420    /// from the child jar.
421    ///
422    /// Any modifications to the child jar will be reflected on the parent jar,
423    /// and any retrievals from the child jar will be made from the parent jar.
424    ///
425    /// # Example
426    ///
427    /// ```rust
428    /// use cookie::{Cookie, CookieJar, Key};
429    ///
430    /// // Generate a secure key.
431    /// let key = Key::generate();
432    ///
433    /// // Add a private (signed + encrypted) cookie.
434    /// let mut jar = CookieJar::new();
435    /// jar.private_mut(&key).add(("private", "text"));
436    ///
437    /// // Remove a cookie using the child jar.
438    /// jar.private_mut(&key).remove("private");
439    /// ```
440    #[cfg(feature = "private")]
441    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
442    pub fn private_mut<'a>(&'a mut self, key: &Key) -> PrivateJar<&'a mut Self> {
443        PrivateJar::new(self, key)
444    }
445
446    /// Returns a read-only `SignedJar` with `self` as its parent jar using the
447    /// key `key` to verify cookies retrieved from the child jar. Any retrievals
448    /// from the child jar will be made from the parent jar.
449    ///
450    /// # Example
451    ///
452    /// ```rust
453    /// use cookie::{Cookie, CookieJar, Key};
454    ///
455    /// // Generate a secure key.
456    /// let key = Key::generate();
457    ///
458    /// // Add a signed cookie.
459    /// let mut jar = CookieJar::new();
460    /// jar.signed_mut(&key).add(("signed", "text"));
461    ///
462    /// // The cookie's contents are signed but still in plaintext.
463    /// assert_ne!(jar.get("signed").unwrap().value(), "text");
464    /// assert!(jar.get("signed").unwrap().value().contains("text"));
465    ///
466    /// // They can be verified through the child jar.
467    /// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text");
468    ///
469    /// // A tampered with cookie does not validate but still exists.
470    /// let mut cookie = jar.get("signed").unwrap().clone();
471    /// jar.add(("signed", cookie.value().to_string() + "!"));
472    /// assert!(jar.signed(&key).get("signed").is_none());
473    /// assert!(jar.get("signed").is_some());
474    /// ```
475    #[cfg(feature = "signed")]
476    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
477    pub fn signed<'a>(&'a self, key: &Key) -> SignedJar<&'a Self> {
478        SignedJar::new(self, key)
479    }
480
481    /// Returns a read/write `SignedJar` with `self` as its parent jar using the
482    /// key `key` to sign/verify cookies added/retrieved from the child jar.
483    ///
484    /// Any modifications to the child jar will be reflected on the parent jar,
485    /// and any retrievals from the child jar will be made from the parent jar.
486    ///
487    /// # Example
488    ///
489    /// ```rust
490    /// use cookie::{CookieJar, Key};
491    ///
492    /// // Generate a secure key.
493    /// let key = Key::generate();
494    ///
495    /// // Add a signed cookie.
496    /// let mut jar = CookieJar::new();
497    /// jar.signed_mut(&key).add(("signed", "text"));
498    ///
499    /// // Remove a cookie.
500    /// jar.signed_mut(&key).remove("signed");
501    /// ```
502    #[cfg(feature = "signed")]
503    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
504    pub fn signed_mut<'a>(&'a mut self, key: &Key) -> SignedJar<&'a mut Self> {
505        SignedJar::new(self, key)
506    }
507
508    /// Returns a read-only `PrefixedJar` with `self` as its parent jar that
509    /// prefixes the name of cookies with `prefix`. Any retrievals from the
510    /// child jar will be made from the parent jar.
511    ///
512    /// **Note:** Cookie prefixes are specified in an HTTP draft! Their meaning
513    /// and definition are subject to change.
514    ///
515    /// # Example
516    ///
517    /// ```rust
518    /// use cookie::CookieJar;
519    /// use cookie::prefix::{Host, Secure};
520    ///
521    /// // Add a `Host` prefixed cookie.
522    /// let mut jar = CookieJar::new();
523    /// jar.prefixed_mut(Host).add(("h0st", "value"));
524    /// jar.prefixed_mut(Secure).add(("secur3", "value"));
525    ///
526    /// // The cookie's name is prefixed in the parent jar.
527    /// assert!(matches!(jar.get("h0st"), None));
528    /// assert!(matches!(jar.get("__Host-h0st"), Some(_)));
529    /// assert!(matches!(jar.get("secur3"), None));
530    /// assert!(matches!(jar.get("__Secure-secur3"), Some(_)));
531    ///
532    /// // The prefixed jar automatically removes the prefix.
533    /// assert_eq!(jar.prefixed(Host).get("h0st").unwrap().name(), "h0st");
534    /// assert_eq!(jar.prefixed(Host).get("h0st").unwrap().value(), "value");
535    /// assert_eq!(jar.prefixed(Secure).get("secur3").unwrap().name(), "secur3");
536    /// assert_eq!(jar.prefixed(Secure).get("secur3").unwrap().value(), "value");
537    ///
538    /// // Only the correct prefixed jar retrieves the cookie.
539    /// assert!(matches!(jar.prefixed(Host).get("secur3"), None));
540    /// assert!(matches!(jar.prefixed(Secure).get("h0st"), None));
541    /// ```
542    #[inline(always)]
543    pub fn prefixed<'a, P: Prefix>(&'a self, prefix: P) -> PrefixedJar<P, &'a Self> {
544        let _ = prefix;
545        PrefixedJar::new(self)
546    }
547
548    /// Returns a read/write `PrefixedJar` with `self` as its parent jar that
549    /// prefixes the name of cookies with `prefix` and makes the cookie conform
550    /// to the prefix's requirements. This means that added cookies:
551    ///
552    ///   1. Have the [`Prefix::PREFIX`] prepended to their name.
553    ///   2. Modify the cookie via [`Prefix::conform()`] so that it conforms to
554    ///      the prefix's requirements.
555    ///
556    /// Any modifications to the child jar will be reflected on the parent jar,
557    /// and any retrievals from the child jar will be made from the parent jar.
558    ///
559    /// **Note:** Cookie prefixes are specified in an HTTP draft! Their meaning
560    /// and definition are subject to change.
561    ///
562    /// # Example
563    ///
564    /// ```rust
565    /// use cookie::CookieJar;
566    /// use cookie::prefix::{Host, Secure};
567    ///
568    /// // Add some prefixed cookies.
569    /// let mut jar = CookieJar::new();
570    /// jar.prefixed_mut(Host).add(("one", "1"));
571    /// jar.prefixed_mut(Secure).add((2.to_string(), "2"));
572    /// jar.prefixed_mut(Host).add((format!("{:0b}", 3), "0b11"));
573    ///
574    /// // Fetch cookies with either `prefixed()` or `prefixed_mut()`.
575    /// assert_eq!(jar.prefixed(Host).get("one").unwrap().value(), "1");
576    /// assert_eq!(jar.prefixed(Secure).get("2").unwrap().value(), "2");
577    /// assert_eq!(jar.prefixed_mut(Host).get("11").unwrap().value(), "0b11");
578    ///
579    /// // Remove cookies.
580    /// jar.prefixed_mut(Host).remove("one");
581    /// assert!(jar.prefixed(Host).get("one").is_none());
582    /// ```
583    pub fn prefixed_mut<'a, P: Prefix>(&'a mut self, prefix: P) -> PrefixedJar<P, &'a mut Self> {
584        let _ = prefix;
585        PrefixedJar::new(self)
586    }
587}
588
589use std::collections::hash_set::Iter as HashSetIter;
590
591/// Iterator over the changes to a cookie jar.
592pub struct Delta<'a> {
593    iter: HashSetIter<'a, DeltaCookie>,
594}
595
596impl<'a> Iterator for Delta<'a> {
597    type Item = &'a Cookie<'static>;
598
599    fn next(&mut self) -> Option<&'a Cookie<'static>> {
600        self.iter.next().map(|c| &c.cookie)
601    }
602}
603
604use std::collections::hash_set::Difference;
605use std::collections::hash_map::RandomState;
606use std::iter::Chain;
607
608/// Iterator over all of the cookies in a jar.
609pub struct Iter<'a> {
610    delta_cookies: Chain<HashSetIter<'a, DeltaCookie>, Difference<'a, DeltaCookie, RandomState>>,
611}
612
613impl<'a> Iterator for Iter<'a> {
614    type Item = &'a Cookie<'static>;
615
616    fn next(&mut self) -> Option<&'a Cookie<'static>> {
617        for cookie in self.delta_cookies.by_ref() {
618            if !cookie.removed {
619                return Some(&*cookie);
620            }
621        }
622
623        None
624    }
625}
626
627#[cfg(test)]
628mod test {
629    use super::CookieJar;
630    use crate::Cookie;
631
632    #[test]
633    #[allow(deprecated)]
634    fn simple() {
635        let mut c = CookieJar::new();
636
637        c.add(("test", ""));
638        c.add(("test2", ""));
639        c.remove("test");
640
641        assert!(c.get("test").is_none());
642        assert!(c.get("test2").is_some());
643
644        c.add(("test3", ""));
645        c.remove("test2");
646        c.remove("test3");
647
648        assert!(c.get("test").is_none());
649        assert!(c.get("test2").is_none());
650        assert!(c.get("test3").is_none());
651    }
652
653    #[test]
654    fn jar_is_send() {
655        fn is_send<T: Send>(_: T) -> bool {
656            true
657        }
658
659        assert!(is_send(CookieJar::new()))
660    }
661
662    #[test]
663    #[cfg(all(feature = "signed", feature = "private"))]
664    fn iter() {
665        let key = crate::Key::generate();
666        let mut c = CookieJar::new();
667
668        c.add_original(Cookie::new("original", "original"));
669
670        c.add(Cookie::new("test", "test"));
671        c.add(Cookie::new("test2", "test2"));
672        c.add(Cookie::new("test3", "test3"));
673        assert_eq!(c.iter().count(), 4);
674
675        c.signed_mut(&key).add(Cookie::new("signed", "signed"));
676        c.private_mut(&key).add(Cookie::new("encrypted", "encrypted"));
677        assert_eq!(c.iter().count(), 6);
678
679        c.remove("test");
680        assert_eq!(c.iter().count(), 5);
681
682        c.remove("signed");
683        c.remove("test2");
684        assert_eq!(c.iter().count(), 3);
685
686        c.add(("test2", "test2"));
687        assert_eq!(c.iter().count(), 4);
688
689        c.remove("test2");
690        assert_eq!(c.iter().count(), 3);
691    }
692
693    #[test]
694    fn delta() {
695        use std::collections::HashMap;
696        use time::Duration;
697
698        let mut c = CookieJar::new();
699
700        c.add_original(Cookie::new("original", "original"));
701        c.add_original(Cookie::new("original1", "original1"));
702
703        c.add(Cookie::new("test", "test"));
704        c.add(Cookie::new("test2", "test2"));
705        c.add(Cookie::new("test3", "test3"));
706        c.add(Cookie::new("test4", "test4"));
707
708        c.remove("test");
709        c.remove("original");
710
711        assert_eq!(c.delta().count(), 4);
712
713        let names: HashMap<_, _> = c.delta()
714            .map(|c| (c.name(), c.max_age()))
715            .collect();
716
717        assert!(names.get("test2").unwrap().is_none());
718        assert!(names.get("test3").unwrap().is_none());
719        assert!(names.get("test4").unwrap().is_none());
720        assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0)));
721    }
722
723    #[test]
724    fn replace_original() {
725        let mut jar = CookieJar::new();
726        jar.add_original(Cookie::new("original_a", "a"));
727        jar.add_original(Cookie::new("original_b", "b"));
728        assert_eq!(jar.get("original_a").unwrap().value(), "a");
729
730        jar.add(Cookie::new("original_a", "av2"));
731        assert_eq!(jar.get("original_a").unwrap().value(), "av2");
732    }
733
734    #[test]
735    fn empty_delta() {
736        let mut jar = CookieJar::new();
737        jar.add(Cookie::new("name", "val"));
738        assert_eq!(jar.delta().count(), 1);
739
740        jar.remove("name");
741        assert_eq!(jar.delta().count(), 0);
742
743        jar.add_original(Cookie::new("name", "val"));
744        assert_eq!(jar.delta().count(), 0);
745
746        jar.remove("name");
747        assert_eq!(jar.delta().count(), 1);
748
749        jar.add(Cookie::new("name", "val"));
750        assert_eq!(jar.delta().count(), 1);
751
752        jar.remove("name");
753        assert_eq!(jar.delta().count(), 1);
754    }
755
756    #[test]
757    fn add_remove_add() {
758        let mut jar = CookieJar::new();
759        jar.add_original(Cookie::new("name", "val"));
760        assert_eq!(jar.delta().count(), 0);
761
762        jar.remove("name");
763        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
764        assert_eq!(jar.delta().count(), 1);
765
766        // The cookie's been deleted. Another original doesn't change that.
767        jar.add_original(Cookie::new("name", "val"));
768        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
769        assert_eq!(jar.delta().count(), 1);
770
771        jar.remove("name");
772        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
773        assert_eq!(jar.delta().count(), 1);
774
775        jar.add(Cookie::new("name", "val"));
776        assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
777        assert_eq!(jar.delta().count(), 1);
778
779        jar.remove("name");
780        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
781        assert_eq!(jar.delta().count(), 1);
782    }
783
784    #[test]
785    fn replace_remove() {
786        let mut jar = CookieJar::new();
787        jar.add_original(Cookie::new("name", "val"));
788        assert_eq!(jar.delta().count(), 0);
789
790        jar.add(Cookie::new("name", "val"));
791        assert_eq!(jar.delta().count(), 1);
792        assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
793
794        jar.remove("name");
795        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
796    }
797
798    #[test]
799    fn remove_with_path() {
800        let mut jar = CookieJar::new();
801        jar.add_original(("name", "val"));
802        assert_eq!(jar.iter().count(), 1);
803        assert_eq!(jar.delta().count(), 0);
804        assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1);
805
806        jar.remove(Cookie::build("name").path("/"));
807        assert_eq!(jar.iter().count(), 0);
808        assert_eq!(jar.delta().count(), 1);
809        assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
810        assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1);
811    }
812}