Skip to main content

saphir_cookie/
jar.rs

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