cookie/
lib.rs

1//! HTTP cookie parsing and cookie jar management.
2//!
3//! This crates provides the [`Cookie`] type, representing an HTTP cookie, and
4//! the [`CookieJar`] type, which manages a collection of cookies for session
5//! management, recording changes as they are made, and optional automatic
6//! cookie encryption and signing.
7//!
8//! # Usage
9//!
10//! Add the following to the `[dependencies]` section of your `Cargo.toml`:
11//!
12//! ```toml
13//! cookie = "0.15"
14//! ```
15//!
16//! Then add the following line to your crate root:
17//!
18//! ```
19//! extern crate cookie;
20//! ```
21//!
22//! # Features
23//!
24//! This crate exposes several features, all of which are disabled by default:
25//!
26//! * **`percent-encode`**
27//!
28//!   Enables _percent encoding and decoding_ of names and values in cookies.
29//!
30//!   When this feature is enabled, the [`Cookie::encoded()`] and
31//!   [`Cookie::parse_encoded()`] methods are available. The `encoded` method
32//!   returns a wrapper around a `Cookie` whose `Display` implementation
33//!   percent-encodes the name and value of the cookie. The `parse_encoded`
34//!   method percent-decodes the name and value of a `Cookie` during parsing.
35//!
36//! * **`signed`**
37//!
38//!   Enables _signed_ cookies via [`CookieJar::signed()`].
39//!
40//!   When this feature is enabled, the [`CookieJar::signed()`] method,
41//!   [`SignedJar`] type, and [`Key`] type are available. The jar acts as "child
42//!   jar"; operations on the jar automatically sign and verify cookies as they
43//!   are added and retrieved from the parent jar.
44//!
45//! * **`private`**
46//!
47//!   Enables _private_ (authenticated, encrypted) cookies via
48//!   [`CookieJar::private()`].
49//!
50//!   When this feature is enabled, the [`CookieJar::private()`] method,
51//!   [`PrivateJar`] type, and [`Key`] type are available. The jar acts as "child
52//!   jar"; operations on the jar automatically encrypt and decrypt/authenticate
53//!   cookies as they are added and retrieved from the parent jar.
54//!
55//! * **`key-expansion`**
56//!
57//!   Enables _key expansion_ or _key derivation_ via [`Key::derive_from()`].
58//!
59//!   When this feature is enabled, and either `signed` or `private` are _also_
60//!   enabled, the [`Key::derive_from()`] method is available. The method can be
61//!   used to derive a `Key` structure appropriate for use with signed and
62//!   private jars from cryptographically valid key material that is shorter in
63//!   length than the full key.
64//!
65//! * **`secure`**
66//!
67//!   A meta-feature that simultaneously enables `signed`, `private`, and
68//!   `key-expansion`.
69//!
70//! You can enable features via `Cargo.toml`:
71//!
72//! ```toml
73//! [dependencies.cookie]
74//! features = ["secure", "percent-encode"]
75//! ```
76
77#![cfg_attr(all(nightly, doc), feature(doc_cfg))]
78
79#![doc(html_root_url = "https://docs.rs/cookie/0.15")]
80#![deny(missing_docs)]
81
82#[cfg(feature = "percent-encode")] extern crate percent_encoding;
83extern crate time;
84
85mod builder;
86mod parse;
87mod jar;
88mod delta;
89mod draft;
90mod expiration;
91
92#[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure;
93#[cfg(any(feature = "private", feature = "signed"))] pub use secure::*;
94
95use std::borrow::Cow;
96use std::fmt;
97use std::str::FromStr;
98
99#[allow(unused_imports, deprecated)]
100use std::ascii::AsciiExt;
101
102#[cfg(feature = "percent-encode")]
103use percent_encoding::{AsciiSet, percent_encode as encode};
104use time::{Duration, OffsetDateTime, UtcOffset};
105
106use crate::parse::parse_cookie;
107pub use crate::parse::ParseError;
108pub use crate::builder::CookieBuilder;
109pub use crate::jar::{CookieJar, Delta, Iter};
110pub use crate::draft::*;
111pub use crate::expiration::*;
112
113#[derive(Debug, Clone)]
114enum CookieStr<'c> {
115    /// An string derived from indexes (start, end).
116    Indexed(usize, usize),
117    /// A string derived from a concrete string.
118    Concrete(Cow<'c, str>),
119}
120
121impl<'c> CookieStr<'c> {
122    /// Retrieves the string `self` corresponds to. If `self` is derived from
123    /// indexes, the corresponding subslice of `string` is returned. Otherwise,
124    /// the concrete string is returned.
125    ///
126    /// # Panics
127    ///
128    /// Panics if `self` is an indexed string and `string` is None.
129    fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str {
130        match *self {
131            CookieStr::Indexed(i, j) => {
132                let s = string.expect("`Some` base string must exist when \
133                    converting indexed str to str! (This is a module invariant.)");
134                &s[i..j]
135            },
136            CookieStr::Concrete(ref cstr) => &*cstr,
137        }
138    }
139
140    #[allow(clippy::ptr_arg)]
141    fn to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str> {
142        match *self {
143            CookieStr::Indexed(i, j) => {
144                match *string {
145                    Cow::Borrowed(s) => Some(&s[i..j]),
146                    Cow::Owned(_) => None,
147                }
148            },
149            CookieStr::Concrete(_) => None,
150        }
151    }
152
153    fn into_owned(self) -> CookieStr<'static> {
154        use crate::CookieStr::*;
155
156        match self {
157            Indexed(a, b) => Indexed(a, b),
158            Concrete(Cow::Owned(c)) => Concrete(Cow::Owned(c)),
159            Concrete(Cow::Borrowed(c)) => Concrete(Cow::Owned(c.into())),
160        }
161    }
162}
163
164/// Representation of an HTTP cookie.
165///
166/// # Constructing a `Cookie`
167///
168/// To construct a cookie with only a name/value, use [`Cookie::new()`]:
169///
170/// ```rust
171/// use cookie::Cookie;
172///
173/// let cookie = Cookie::new("name", "value");
174/// assert_eq!(&cookie.to_string(), "name=value");
175/// ```
176///
177/// To construct more elaborate cookies, use [`Cookie::build()`] and
178/// [`CookieBuilder`] methods:
179///
180/// ```rust
181/// use cookie::Cookie;
182///
183/// let cookie = Cookie::build("name", "value")
184///     .domain("www.rust-lang.org")
185///     .path("/")
186///     .secure(true)
187///     .http_only(true)
188///     .finish();
189/// ```
190#[derive(Debug, Clone)]
191pub struct Cookie<'c> {
192    /// Storage for the cookie string. Only used if this structure was derived
193    /// from a string that was subsequently parsed.
194    cookie_string: Option<Cow<'c, str>>,
195    /// The cookie's name.
196    name: CookieStr<'c>,
197    /// The cookie's value.
198    value: CookieStr<'c>,
199    /// The cookie's expiration, if any.
200    expires: Option<Expiration>,
201    /// The cookie's maximum age, if any.
202    max_age: Option<Duration>,
203    /// The cookie's domain, if any.
204    domain: Option<CookieStr<'c>>,
205    /// The cookie's path domain, if any.
206    path: Option<CookieStr<'c>>,
207    /// Whether this cookie was marked Secure.
208    secure: Option<bool>,
209    /// Whether this cookie was marked HttpOnly.
210    http_only: Option<bool>,
211    /// The draft `SameSite` attribute.
212    same_site: Option<SameSite>,
213}
214
215impl<'c> Cookie<'c> {
216    /// Creates a new `Cookie` with the given name and value.
217    ///
218    /// # Example
219    ///
220    /// ```rust
221    /// use cookie::Cookie;
222    ///
223    /// let cookie = Cookie::new("name", "value");
224    /// assert_eq!(cookie.name_value(), ("name", "value"));
225    /// ```
226    pub fn new<N, V>(name: N, value: V) -> Self
227        where N: Into<Cow<'c, str>>,
228              V: Into<Cow<'c, str>>
229    {
230        Cookie {
231            cookie_string: None,
232            name: CookieStr::Concrete(name.into()),
233            value: CookieStr::Concrete(value.into()),
234            expires: None,
235            max_age: None,
236            domain: None,
237            path: None,
238            secure: None,
239            http_only: None,
240            same_site: None,
241        }
242    }
243
244    /// Creates a new `Cookie` with the given name and an empty value.
245    ///
246    /// # Example
247    ///
248    /// ```rust
249    /// use cookie::Cookie;
250    ///
251    /// let cookie = Cookie::named("name");
252    /// assert_eq!(cookie.name(), "name");
253    /// assert!(cookie.value().is_empty());
254    /// ```
255    pub fn named<N>(name: N) -> Cookie<'c>
256        where N: Into<Cow<'c, str>>
257    {
258        Cookie::new(name, "")
259    }
260
261    /// Creates a new `CookieBuilder` instance from the given key and value
262    /// strings.
263    ///
264    /// # Example
265    ///
266    /// ```
267    /// use cookie::Cookie;
268    ///
269    /// let c = Cookie::build("foo", "bar").finish();
270    /// assert_eq!(c.name_value(), ("foo", "bar"));
271    /// ```
272    pub fn build<N, V>(name: N, value: V) -> CookieBuilder<'c>
273        where N: Into<Cow<'c, str>>,
274              V: Into<Cow<'c, str>>
275    {
276        CookieBuilder::new(name, value)
277    }
278
279    /// Parses a `Cookie` from the given HTTP cookie header value string. Does
280    /// not perform any percent-decoding.
281    ///
282    /// # Example
283    ///
284    /// ```
285    /// use cookie::Cookie;
286    ///
287    /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap();
288    /// assert_eq!(c.name_value(), ("foo", "bar%20baz"));
289    /// assert_eq!(c.http_only(), Some(true));
290    /// ```
291    pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
292        where S: Into<Cow<'c, str>>
293    {
294        parse_cookie(s, false)
295    }
296
297    /// Parses a `Cookie` from the given HTTP cookie header value string where
298    /// the name and value fields are percent-encoded. Percent-decodes the
299    /// name/value fields.
300    ///
301    /// # Example
302    ///
303    /// ```
304    /// use cookie::Cookie;
305    ///
306    /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap();
307    /// assert_eq!(c.name_value(), ("foo", "bar baz"));
308    /// assert_eq!(c.http_only(), Some(true));
309    /// ```
310    #[cfg(feature = "percent-encode")]
311    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
312    pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
313        where S: Into<Cow<'c, str>>
314    {
315        parse_cookie(s, true)
316    }
317
318    /// Converts `self` into a `Cookie` with a static lifetime with as few
319    /// allocations as possible.
320    ///
321    /// # Example
322    ///
323    /// ```
324    /// use cookie::Cookie;
325    ///
326    /// let c = Cookie::new("a", "b");
327    /// let owned_cookie = c.into_owned();
328    /// assert_eq!(owned_cookie.name_value(), ("a", "b"));
329    /// ```
330    pub fn into_owned(self) -> Cookie<'static> {
331        Cookie {
332            cookie_string: self.cookie_string.map(|s| s.into_owned().into()),
333            name: self.name.into_owned(),
334            value: self.value.into_owned(),
335            expires: self.expires,
336            max_age: self.max_age,
337            domain: self.domain.map(|s| s.into_owned()),
338            path: self.path.map(|s| s.into_owned()),
339            secure: self.secure,
340            http_only: self.http_only,
341            same_site: self.same_site,
342        }
343    }
344
345    /// Returns the name of `self`.
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// use cookie::Cookie;
351    ///
352    /// let c = Cookie::new("name", "value");
353    /// assert_eq!(c.name(), "name");
354    /// ```
355    #[inline]
356    pub fn name(&self) -> &str {
357        self.name.to_str(self.cookie_string.as_ref())
358    }
359
360    /// Returns the value of `self`.
361    ///
362    /// # Example
363    ///
364    /// ```
365    /// use cookie::Cookie;
366    ///
367    /// let c = Cookie::new("name", "value");
368    /// assert_eq!(c.value(), "value");
369    /// ```
370    #[inline]
371    pub fn value(&self) -> &str {
372        self.value.to_str(self.cookie_string.as_ref())
373    }
374
375    /// Returns the name and value of `self` as a tuple of `(name, value)`.
376    ///
377    /// # Example
378    ///
379    /// ```
380    /// use cookie::Cookie;
381    ///
382    /// let c = Cookie::new("name", "value");
383    /// assert_eq!(c.name_value(), ("name", "value"));
384    /// ```
385    #[inline]
386    pub fn name_value(&self) -> (&str, &str) {
387        (self.name(), self.value())
388    }
389
390    /// Returns whether this cookie was marked `HttpOnly` or not. Returns
391    /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
392    /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`,
393    /// and `None` otherwise.
394    ///
395    /// # Example
396    ///
397    /// ```
398    /// use cookie::Cookie;
399    ///
400    /// let c = Cookie::parse("name=value; httponly").unwrap();
401    /// assert_eq!(c.http_only(), Some(true));
402    ///
403    /// let mut c = Cookie::new("name", "value");
404    /// assert_eq!(c.http_only(), None);
405    ///
406    /// let mut c = Cookie::new("name", "value");
407    /// assert_eq!(c.http_only(), None);
408    ///
409    /// // An explicitly set "false" value.
410    /// c.set_http_only(false);
411    /// assert_eq!(c.http_only(), Some(false));
412    ///
413    /// // An explicitly set "true" value.
414    /// c.set_http_only(true);
415    /// assert_eq!(c.http_only(), Some(true));
416    /// ```
417    #[inline]
418    pub fn http_only(&self) -> Option<bool> {
419        self.http_only
420    }
421
422    /// Returns whether this cookie was marked `Secure` or not. Returns
423    /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
424    /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and
425    /// `None` otherwise.
426    ///
427    /// # Example
428    ///
429    /// ```
430    /// use cookie::Cookie;
431    ///
432    /// let c = Cookie::parse("name=value; Secure").unwrap();
433    /// assert_eq!(c.secure(), Some(true));
434    ///
435    /// let mut c = Cookie::parse("name=value").unwrap();
436    /// assert_eq!(c.secure(), None);
437    ///
438    /// let mut c = Cookie::new("name", "value");
439    /// assert_eq!(c.secure(), None);
440    ///
441    /// // An explicitly set "false" value.
442    /// c.set_secure(false);
443    /// assert_eq!(c.secure(), Some(false));
444    ///
445    /// // An explicitly set "true" value.
446    /// c.set_secure(true);
447    /// assert_eq!(c.secure(), Some(true));
448    /// ```
449    #[inline]
450    pub fn secure(&self) -> Option<bool> {
451        self.secure
452    }
453
454    /// Returns the `SameSite` attribute of this cookie if one was specified.
455    ///
456    /// # Example
457    ///
458    /// ```
459    /// use cookie::{Cookie, SameSite};
460    ///
461    /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap();
462    /// assert_eq!(c.same_site(), Some(SameSite::Lax));
463    /// ```
464    #[inline]
465    pub fn same_site(&self) -> Option<SameSite> {
466        self.same_site
467    }
468
469    /// Returns the specified max-age of the cookie if one was specified.
470    ///
471    /// # Example
472    ///
473    /// ```
474    /// use cookie::Cookie;
475    ///
476    /// let c = Cookie::parse("name=value").unwrap();
477    /// assert_eq!(c.max_age(), None);
478    ///
479    /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
480    /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
481    /// ```
482    #[inline]
483    pub fn max_age(&self) -> Option<Duration> {
484        self.max_age
485    }
486
487    /// Returns the `Path` of the cookie if one was specified.
488    ///
489    /// # Example
490    ///
491    /// ```
492    /// use cookie::Cookie;
493    ///
494    /// let c = Cookie::parse("name=value").unwrap();
495    /// assert_eq!(c.path(), None);
496    ///
497    /// let c = Cookie::parse("name=value; Path=/").unwrap();
498    /// assert_eq!(c.path(), Some("/"));
499    ///
500    /// let c = Cookie::parse("name=value; path=/sub").unwrap();
501    /// assert_eq!(c.path(), Some("/sub"));
502    /// ```
503    #[inline]
504    pub fn path(&self) -> Option<&str> {
505        match self.path {
506            Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
507            None => None,
508        }
509    }
510
511    /// Returns the `Domain` of the cookie if one was specified.
512    ///
513    /// # Example
514    ///
515    /// ```
516    /// use cookie::Cookie;
517    ///
518    /// let c = Cookie::parse("name=value").unwrap();
519    /// assert_eq!(c.domain(), None);
520    ///
521    /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap();
522    /// assert_eq!(c.domain(), Some("crates.io"));
523    /// ```
524    #[inline]
525    pub fn domain(&self) -> Option<&str> {
526        match self.domain {
527            Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
528            None => None,
529        }
530    }
531
532    /// Returns the [`Expiration`] of the cookie if one was specified.
533    ///
534    /// # Example
535    ///
536    /// ```
537    /// use cookie::{Cookie, Expiration};
538    ///
539    /// let c = Cookie::parse("name=value").unwrap();
540    /// assert_eq!(c.expires(), None);
541    ///
542    /// // Here, `cookie.expires_datetime()` returns `None`.
543    /// let c = Cookie::build("name", "value").expires(None).finish();
544    /// assert_eq!(c.expires(), Some(Expiration::Session));
545    ///
546    /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
547    /// let cookie_str = format!("name=value; Expires={}", expire_time);
548    /// let c = Cookie::parse(cookie_str).unwrap();
549    /// assert_eq!(c.expires().and_then(|e| e.datetime()).map(|t| t.year()), Some(2017));
550    /// ```
551    #[inline]
552    pub fn expires(&self) -> Option<Expiration> {
553        self.expires
554    }
555
556    /// Returns the expiration date-time of the cookie if one was specified.
557    ///
558    /// # Example
559    ///
560    /// ```
561    /// use cookie::Cookie;
562    ///
563    /// let c = Cookie::parse("name=value").unwrap();
564    /// assert_eq!(c.expires_datetime(), None);
565    ///
566    /// // Here, `cookie.expires()` returns `Some`.
567    /// let c = Cookie::build("name", "value").expires(None).finish();
568    /// assert_eq!(c.expires_datetime(), None);
569    ///
570    /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
571    /// let cookie_str = format!("name=value; Expires={}", expire_time);
572    /// let c = Cookie::parse(cookie_str).unwrap();
573    /// assert_eq!(c.expires_datetime().map(|t| t.year()), Some(2017));
574    /// ```
575    #[inline]
576    pub fn expires_datetime(&self) -> Option<OffsetDateTime> {
577        self.expires.and_then(|e| e.datetime())
578    }
579
580    /// Sets the name of `self` to `name`.
581    ///
582    /// # Example
583    ///
584    /// ```
585    /// use cookie::Cookie;
586    ///
587    /// let mut c = Cookie::new("name", "value");
588    /// assert_eq!(c.name(), "name");
589    ///
590    /// c.set_name("foo");
591    /// assert_eq!(c.name(), "foo");
592    /// ```
593    pub fn set_name<N: Into<Cow<'c, str>>>(&mut self, name: N) {
594        self.name = CookieStr::Concrete(name.into())
595    }
596
597    /// Sets the value of `self` to `value`.
598    ///
599    /// # Example
600    ///
601    /// ```
602    /// use cookie::Cookie;
603    ///
604    /// let mut c = Cookie::new("name", "value");
605    /// assert_eq!(c.value(), "value");
606    ///
607    /// c.set_value("bar");
608    /// assert_eq!(c.value(), "bar");
609    /// ```
610    pub fn set_value<V: Into<Cow<'c, str>>>(&mut self, value: V) {
611        self.value = CookieStr::Concrete(value.into())
612    }
613
614    /// Sets the value of `http_only` in `self` to `value`.  If `value` is
615    /// `None`, the field is unset.
616    ///
617    /// # Example
618    ///
619    /// ```
620    /// use cookie::Cookie;
621    ///
622    /// let mut c = Cookie::new("name", "value");
623    /// assert_eq!(c.http_only(), None);
624    ///
625    /// c.set_http_only(true);
626    /// assert_eq!(c.http_only(), Some(true));
627    ///
628    /// c.set_http_only(false);
629    /// assert_eq!(c.http_only(), Some(false));
630    ///
631    /// c.set_http_only(None);
632    /// assert_eq!(c.http_only(), None);
633    /// ```
634    #[inline]
635    pub fn set_http_only<T: Into<Option<bool>>>(&mut self, value: T) {
636        self.http_only = value.into();
637    }
638
639    /// Sets the value of `secure` in `self` to `value`. If `value` is `None`,
640    /// the field is unset.
641    ///
642    /// # Example
643    ///
644    /// ```
645    /// use cookie::Cookie;
646    ///
647    /// let mut c = Cookie::new("name", "value");
648    /// assert_eq!(c.secure(), None);
649    ///
650    /// c.set_secure(true);
651    /// assert_eq!(c.secure(), Some(true));
652    ///
653    /// c.set_secure(false);
654    /// assert_eq!(c.secure(), Some(false));
655    ///
656    /// c.set_secure(None);
657    /// assert_eq!(c.secure(), None);
658    /// ```
659    #[inline]
660    pub fn set_secure<T: Into<Option<bool>>>(&mut self, value: T) {
661        self.secure = value.into();
662    }
663
664    /// Sets the value of `same_site` in `self` to `value`. If `value` is
665    /// `None`, the field is unset. If `value` is `SameSite::None`, the "Secure"
666    /// flag will be set when the cookie is written out unless `secure` is
667    /// explicitly set to `false` via [`Cookie::set_secure()`] or the equivalent
668    /// builder method.
669    ///
670    /// [HTTP draft]: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
671    ///
672    /// # Example
673    ///
674    /// ```
675    /// use cookie::{Cookie, SameSite};
676    ///
677    /// let mut c = Cookie::new("name", "value");
678    /// assert_eq!(c.same_site(), None);
679    ///
680    /// c.set_same_site(SameSite::None);
681    /// assert_eq!(c.same_site(), Some(SameSite::None));
682    /// assert_eq!(c.to_string(), "name=value; SameSite=None; Secure");
683    ///
684    /// c.set_secure(false);
685    /// assert_eq!(c.to_string(), "name=value; SameSite=None");
686    ///
687    /// let mut c = Cookie::new("name", "value");
688    /// assert_eq!(c.same_site(), None);
689    ///
690    /// c.set_same_site(SameSite::Strict);
691    /// assert_eq!(c.same_site(), Some(SameSite::Strict));
692    /// assert_eq!(c.to_string(), "name=value; SameSite=Strict");
693    ///
694    /// c.set_same_site(None);
695    /// assert_eq!(c.same_site(), None);
696    /// assert_eq!(c.to_string(), "name=value");
697    /// ```
698    #[inline]
699    pub fn set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T) {
700        self.same_site = value.into();
701    }
702
703    /// Sets the value of `max_age` in `self` to `value`. If `value` is `None`,
704    /// the field is unset.
705    ///
706    /// # Example
707    ///
708    /// ```rust
709    /// # extern crate cookie;
710    /// extern crate time;
711    ///
712    /// use cookie::Cookie;
713    /// use time::Duration;
714    ///
715    /// # fn main() {
716    /// let mut c = Cookie::new("name", "value");
717    /// assert_eq!(c.max_age(), None);
718    ///
719    /// c.set_max_age(Duration::hours(10));
720    /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
721    ///
722    /// c.set_max_age(None);
723    /// assert!(c.max_age().is_none());
724    /// # }
725    /// ```
726    #[inline]
727    pub fn set_max_age<D: Into<Option<Duration>>>(&mut self, value: D) {
728        self.max_age = value.into();
729    }
730
731    /// Sets the `path` of `self` to `path`.
732    ///
733    /// # Example
734    ///
735    /// ```rust
736    /// use cookie::Cookie;
737    ///
738    /// let mut c = Cookie::new("name", "value");
739    /// assert_eq!(c.path(), None);
740    ///
741    /// c.set_path("/");
742    /// assert_eq!(c.path(), Some("/"));
743    /// ```
744    pub fn set_path<P: Into<Cow<'c, str>>>(&mut self, path: P) {
745        self.path = Some(CookieStr::Concrete(path.into()));
746    }
747
748    /// Unsets the `path` of `self`.
749    ///
750    /// # Example
751    ///
752    /// ```
753    /// use cookie::Cookie;
754    ///
755    /// let mut c = Cookie::new("name", "value");
756    /// assert_eq!(c.path(), None);
757    ///
758    /// c.set_path("/");
759    /// assert_eq!(c.path(), Some("/"));
760    ///
761    /// c.unset_path();
762    /// assert_eq!(c.path(), None);
763    /// ```
764    pub fn unset_path(&mut self) {
765        self.path = None;
766    }
767
768    /// Sets the `domain` of `self` to `domain`.
769    ///
770    /// # Example
771    ///
772    /// ```
773    /// use cookie::Cookie;
774    ///
775    /// let mut c = Cookie::new("name", "value");
776    /// assert_eq!(c.domain(), None);
777    ///
778    /// c.set_domain("rust-lang.org");
779    /// assert_eq!(c.domain(), Some("rust-lang.org"));
780    /// ```
781    pub fn set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D) {
782        self.domain = Some(CookieStr::Concrete(domain.into()));
783    }
784
785    /// Unsets the `domain` of `self`.
786    ///
787    /// # Example
788    ///
789    /// ```
790    /// use cookie::Cookie;
791    ///
792    /// let mut c = Cookie::new("name", "value");
793    /// assert_eq!(c.domain(), None);
794    ///
795    /// c.set_domain("rust-lang.org");
796    /// assert_eq!(c.domain(), Some("rust-lang.org"));
797    ///
798    /// c.unset_domain();
799    /// assert_eq!(c.domain(), None);
800    /// ```
801    pub fn unset_domain(&mut self) {
802        self.domain = None;
803    }
804
805    /// Sets the expires field of `self` to `time`. If `time` is `None`, an
806    /// expiration of [`Session`](Expiration::Session) is set.
807    ///
808    /// # Example
809    ///
810    /// ```
811    /// # extern crate cookie;
812    /// extern crate time;
813    /// use cookie::{Cookie, Expiration};
814    /// use time::{Duration, OffsetDateTime};
815    ///
816    /// let mut c = Cookie::new("name", "value");
817    /// assert_eq!(c.expires(), None);
818    ///
819    /// let mut now = OffsetDateTime::now();
820    /// now += Duration::weeks(52);
821    ///
822    /// c.set_expires(now);
823    /// assert!(c.expires().is_some());
824    ///
825    /// c.set_expires(None);
826    /// assert_eq!(c.expires(), Some(Expiration::Session));
827    /// ```
828    pub fn set_expires<T: Into<Expiration>>(&mut self, time: T) {
829        use time::{date, time, offset};
830        static MAX_DATETIME: OffsetDateTime = date!(9999-12-31)
831            .with_time(time!(23:59:59.999_999))
832            .assume_utc()
833            .to_offset(offset!(UTC));
834
835        // RFC 6265 requires dates not to exceed 9999 years.
836        self.expires = Some(time.into()
837            .map(|time| std::cmp::min(time, MAX_DATETIME)));
838    }
839
840    /// Unsets the `expires` of `self`.
841    ///
842    /// # Example
843    ///
844    /// ```
845    /// use cookie::{Cookie, Expiration};
846    ///
847    /// let mut c = Cookie::new("name", "value");
848    /// assert_eq!(c.expires(), None);
849    ///
850    /// c.set_expires(None);
851    /// assert_eq!(c.expires(), Some(Expiration::Session));
852    ///
853    /// c.unset_expires();
854    /// assert_eq!(c.expires(), None);
855    /// ```
856    pub fn unset_expires(&mut self) {
857        self.expires = None;
858    }
859
860    /// Makes `self` a "permanent" cookie by extending its expiration and max
861    /// age 20 years into the future.
862    ///
863    /// # Example
864    ///
865    /// ```rust
866    /// # extern crate cookie;
867    /// extern crate time;
868    ///
869    /// use cookie::Cookie;
870    /// use time::Duration;
871    ///
872    /// # fn main() {
873    /// let mut c = Cookie::new("foo", "bar");
874    /// assert!(c.expires().is_none());
875    /// assert!(c.max_age().is_none());
876    ///
877    /// c.make_permanent();
878    /// assert!(c.expires().is_some());
879    /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
880    /// # }
881    /// ```
882    pub fn make_permanent(&mut self) {
883        let twenty_years = Duration::days(365 * 20);
884        self.set_max_age(twenty_years);
885        self.set_expires(OffsetDateTime::now_utc() + twenty_years);
886    }
887
888    /// Make `self` a "removal" cookie by clearing its value, setting a max-age
889    /// of `0`, and setting an expiration date far in the past.
890    ///
891    /// # Example
892    ///
893    /// ```rust
894    /// # extern crate cookie;
895    /// extern crate time;
896    ///
897    /// use cookie::Cookie;
898    /// use time::Duration;
899    ///
900    /// # fn main() {
901    /// let mut c = Cookie::new("foo", "bar");
902    /// c.make_permanent();
903    /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
904    /// assert_eq!(c.value(), "bar");
905    ///
906    /// c.make_removal();
907    /// assert_eq!(c.value(), "");
908    /// assert_eq!(c.max_age(), Some(Duration::zero()));
909    /// # }
910    /// ```
911    pub fn make_removal(&mut self) {
912        self.set_value("");
913        self.set_max_age(Duration::seconds(0));
914        self.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
915    }
916
917    fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
918        if let Some(true) = self.http_only() {
919            write!(f, "; HttpOnly")?;
920        }
921
922        if let Some(same_site) = self.same_site() {
923            write!(f, "; SameSite={}", same_site)?;
924
925            if same_site.is_none() && self.secure().is_none() {
926                write!(f, "; Secure")?;
927            }
928        }
929
930        if let Some(true) = self.secure() {
931            write!(f, "; Secure")?;
932        }
933
934        if let Some(path) = self.path() {
935            write!(f, "; Path={}", path)?;
936        }
937
938        if let Some(domain) = self.domain() {
939            write!(f, "; Domain={}", domain)?;
940        }
941
942        if let Some(max_age) = self.max_age() {
943            write!(f, "; Max-Age={}", max_age.whole_seconds())?;
944        }
945
946        if let Some(time) = self.expires_datetime() {
947            let time = time.to_offset(UtcOffset::UTC);
948            write!(f, "; Expires={}", time.format("%a, %d %b %Y %H:%M:%S GMT"))?;
949        }
950
951        Ok(())
952    }
953
954    /// Returns the name of `self` as a string slice of the raw string `self`
955    /// was originally parsed from. If `self` was not originally parsed from a
956    /// raw string, returns `None`.
957    ///
958    /// This method differs from [`Cookie::name()`] in that it returns a string
959    /// with the same lifetime as the originally parsed string. This lifetime
960    /// may outlive `self`. If a longer lifetime is not required, or you're
961    /// unsure if you need a longer lifetime, use [`Cookie::name()`].
962    ///
963    /// # Example
964    ///
965    /// ```
966    /// use cookie::Cookie;
967    ///
968    /// let cookie_string = format!("{}={}", "foo", "bar");
969    ///
970    /// // `c` will be dropped at the end of the scope, but `name` will live on
971    /// let name = {
972    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
973    ///     c.name_raw()
974    /// };
975    ///
976    /// assert_eq!(name, Some("foo"));
977    /// ```
978    #[inline]
979    pub fn name_raw(&self) -> Option<&'c str> {
980        self.cookie_string.as_ref()
981            .and_then(|s| self.name.to_raw_str(s))
982    }
983
984    /// Returns the value of `self` as a string slice of the raw string `self`
985    /// was originally parsed from. If `self` was not originally parsed from a
986    /// raw string, returns `None`.
987    ///
988    /// This method differs from [`Cookie::value()`] in that it returns a
989    /// string with the same lifetime as the originally parsed string. This
990    /// lifetime may outlive `self`. If a longer lifetime is not required, or
991    /// you're unsure if you need a longer lifetime, use [`Cookie::value()`].
992    ///
993    /// # Example
994    ///
995    /// ```
996    /// use cookie::Cookie;
997    ///
998    /// let cookie_string = format!("{}={}", "foo", "bar");
999    ///
1000    /// // `c` will be dropped at the end of the scope, but `value` will live on
1001    /// let value = {
1002    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
1003    ///     c.value_raw()
1004    /// };
1005    ///
1006    /// assert_eq!(value, Some("bar"));
1007    /// ```
1008    #[inline]
1009    pub fn value_raw(&self) -> Option<&'c str> {
1010        self.cookie_string.as_ref()
1011            .and_then(|s| self.value.to_raw_str(s))
1012    }
1013
1014    /// Returns the `Path` of `self` as a string slice of the raw string `self`
1015    /// was originally parsed from. If `self` was not originally parsed from a
1016    /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has
1017    /// changed since parsing, returns `None`.
1018    ///
1019    /// This method differs from [`Cookie::path()`] in that it returns a
1020    /// string with the same lifetime as the originally parsed string. This
1021    /// lifetime may outlive `self`. If a longer lifetime is not required, or
1022    /// you're unsure if you need a longer lifetime, use [`Cookie::path()`].
1023    ///
1024    /// # Example
1025    ///
1026    /// ```
1027    /// use cookie::Cookie;
1028    ///
1029    /// let cookie_string = format!("{}={}; Path=/", "foo", "bar");
1030    ///
1031    /// // `c` will be dropped at the end of the scope, but `path` will live on
1032    /// let path = {
1033    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
1034    ///     c.path_raw()
1035    /// };
1036    ///
1037    /// assert_eq!(path, Some("/"));
1038    /// ```
1039    #[inline]
1040    pub fn path_raw(&self) -> Option<&'c str> {
1041        match (self.path.as_ref(), self.cookie_string.as_ref()) {
1042            (Some(path), Some(string)) => path.to_raw_str(string),
1043            _ => None,
1044        }
1045    }
1046
1047    /// Returns the `Domain` of `self` as a string slice of the raw string
1048    /// `self` was originally parsed from. If `self` was not originally parsed
1049    /// from a raw string, or if `self` doesn't contain a `Domain`, or if the
1050    /// `Domain` has changed since parsing, returns `None`.
1051    ///
1052    /// This method differs from [`Cookie::domain()`] in that it returns a
1053    /// string with the same lifetime as the originally parsed string. This
1054    /// lifetime may outlive `self` struct. If a longer lifetime is not
1055    /// required, or you're unsure if you need a longer lifetime, use
1056    /// [`Cookie::domain()`].
1057    ///
1058    /// # Example
1059    ///
1060    /// ```
1061    /// use cookie::Cookie;
1062    ///
1063    /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar");
1064    ///
1065    /// //`c` will be dropped at the end of the scope, but `domain` will live on
1066    /// let domain = {
1067    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
1068    ///     c.domain_raw()
1069    /// };
1070    ///
1071    /// assert_eq!(domain, Some("crates.io"));
1072    /// ```
1073    #[inline]
1074    pub fn domain_raw(&self) -> Option<&'c str> {
1075        match (self.domain.as_ref(), self.cookie_string.as_ref()) {
1076            (Some(domain), Some(string)) => domain.to_raw_str(string),
1077            _ => None,
1078        }
1079    }
1080
1081    /// Wraps `self` in an encoded [`Display`]: a cost-free wrapper around
1082    /// `Cookie` whose [`fmt::Display`] implementation percent-encodes the name
1083    /// and value of the wrapped `Cookie`.
1084    ///
1085    /// The returned structure can be chained with [`Display::stripped()`] to
1086    /// display only the name and value.
1087    ///
1088    /// # Example
1089    ///
1090    /// ```rust
1091    /// use cookie::Cookie;
1092    ///
1093    /// let mut c = Cookie::build("my name", "this; value?").secure(true).finish();
1094    /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F; Secure");
1095    /// assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%3F");
1096    /// ```
1097    #[cfg(feature = "percent-encode")]
1098    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1099    #[inline(always)]
1100    pub fn encoded<'a>(&'a self) -> Display<'a, 'c> {
1101        Display::new_encoded(self)
1102    }
1103
1104    /// Wraps `self` in a stripped `Display`]: a cost-free wrapper around
1105    /// `Cookie` whose [`fmt::Display`] implementation prints only the `name`
1106    /// and `value` of the wrapped `Cookie`.
1107    ///
1108    /// The returned structure can be chained with [`Display::encoded()`] to
1109    /// encode the name and value.
1110    ///
1111    /// # Example
1112    ///
1113    /// ```rust
1114    /// use cookie::Cookie;
1115    ///
1116    /// let mut c = Cookie::build("key?", "value").secure(true).path("/").finish();
1117    /// assert_eq!(&c.stripped().to_string(), "key?=value");
1118    #[cfg_attr(feature = "percent-encode", doc = r##"
1119// Note: `encoded()` is only available when `percent-encode` is enabled.
1120assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
1121    #"##)]
1122    /// ```
1123    #[inline(always)]
1124    pub fn stripped<'a>(&'a self) -> Display<'a, 'c> {
1125        Display::new_stripped(self)
1126    }
1127}
1128
1129/// https://url.spec.whatwg.org/#fragment-percent-encode-set
1130#[cfg(feature = "percent-encode")]
1131const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
1132
1133/// https://url.spec.whatwg.org/#path-percent-encode-set
1134#[cfg(feature = "percent-encode")]
1135const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
1136
1137/// https://url.spec.whatwg.org/#userinfo-percent-encode-set
1138#[cfg(feature = "percent-encode")]
1139const USERINFO_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET
1140    .add(b'/')
1141    .add(b':')
1142    .add(b';')
1143    .add(b'=')
1144    .add(b'@')
1145    .add(b'[')
1146    .add(b'\\')
1147    .add(b']')
1148    .add(b'^')
1149    .add(b'|')
1150    .add(b'%');
1151
1152#[cfg(feature = "percent-encode")]
1153const COOKIE_ENCODE_SET: &AsciiSet = &USERINFO_ENCODE_SET
1154    .add(b'(')
1155    .add(b')')
1156    .add(b',');
1157
1158/// Wrapper around `Cookie` whose `Display` implementation either
1159/// percent-encodes the cookie's name and value, skips displaying the cookie's
1160/// parameters (only displaying it's name and value), or both.
1161///
1162/// A value of this type can be obtained via [`Cookie::encoded()`] and
1163/// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This
1164/// type should only be used for its `Display` implementation.
1165///
1166/// # Example
1167///
1168/// ```rust
1169/// use cookie::Cookie;
1170///
1171/// let c = Cookie::build("my name", "this; value%?").secure(true).finish();
1172/// assert_eq!(&c.stripped().to_string(), "my name=this; value%?");
1173#[cfg_attr(feature = "percent-encode", doc = r##"
1174// Note: `encoded()` is only available when `percent-encode` is enabled.
1175assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure");
1176assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F");
1177assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F");
1178"##)]
1179/// ```
1180pub struct Display<'a, 'c: 'a> {
1181    cookie: &'a Cookie<'c>,
1182    #[cfg(feature = "percent-encode")]
1183    encode: bool,
1184    strip: bool,
1185}
1186
1187impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> {
1188    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1189        #[cfg(feature = "percent-encode")] {
1190            if self.encode {
1191                let name = encode(self.cookie.name().as_bytes(), COOKIE_ENCODE_SET);
1192                let value = encode(self.cookie.value().as_bytes(), COOKIE_ENCODE_SET);
1193                write!(f, "{}={}", name, value)?;
1194            } else {
1195                write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1196            }
1197        }
1198
1199        #[cfg(not(feature = "percent-encode"))] {
1200            write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1201        }
1202
1203        match self.strip {
1204            true => Ok(()),
1205            false => self.cookie.fmt_parameters(f)
1206        }
1207    }
1208}
1209
1210impl<'a, 'c> Display<'a, 'c> {
1211    #[cfg(feature = "percent-encode")]
1212    fn new_encoded(cookie: &'a Cookie<'c>) -> Self {
1213        Display { cookie, strip: false, encode: true }
1214    }
1215
1216    fn new_stripped(cookie: &'a Cookie<'c>) -> Self {
1217        Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false }
1218    }
1219
1220    /// Percent-encode the name and value pair.
1221    #[inline]
1222    #[cfg(feature = "percent-encode")]
1223    #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1224    pub fn encoded(mut self) -> Self {
1225        self.encode = true;
1226        self
1227    }
1228
1229    /// Only display the name and value.
1230    #[inline]
1231    pub fn stripped(mut self) -> Self {
1232        self.strip = true;
1233        self
1234    }
1235}
1236
1237impl<'c> fmt::Display for Cookie<'c> {
1238    /// Formats the cookie `self` as a `Set-Cookie` header value.
1239    ///
1240    /// Does _not_ percent-encode any values. To percent-encode, use
1241    /// [`Cookie::encoded()`].
1242    ///
1243    /// # Example
1244    ///
1245    /// ```rust
1246    /// use cookie::Cookie;
1247    ///
1248    /// let mut cookie = Cookie::build("foo", "bar")
1249    ///     .path("/")
1250    ///     .finish();
1251    ///
1252    /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1253    /// ```
1254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1255        write!(f, "{}={}", self.name(), self.value())?;
1256        self.fmt_parameters(f)
1257    }
1258}
1259
1260impl FromStr for Cookie<'static> {
1261    type Err = ParseError;
1262
1263    fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
1264        Cookie::parse(s).map(|c| c.into_owned())
1265    }
1266}
1267
1268impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
1269    fn eq(&self, other: &Cookie<'b>) -> bool {
1270        let so_far_so_good = self.name() == other.name()
1271            && self.value() == other.value()
1272            && self.http_only() == other.http_only()
1273            && self.secure() == other.secure()
1274            && self.max_age() == other.max_age()
1275            && self.expires() == other.expires();
1276
1277        if !so_far_so_good {
1278            return false;
1279        }
1280
1281        match (self.path(), other.path()) {
1282            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1283            (None, None) => {}
1284            _ => return false,
1285        };
1286
1287        match (self.domain(), other.domain()) {
1288            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1289            (None, None) => {}
1290            _ => return false,
1291        };
1292
1293        true
1294    }
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299    use crate::{Cookie, SameSite};
1300    use crate::parse::parse_gmt_date;
1301    use crate::{time::Duration, OffsetDateTime};
1302
1303    #[test]
1304    fn format() {
1305        let cookie = Cookie::new("foo", "bar");
1306        assert_eq!(&cookie.to_string(), "foo=bar");
1307
1308        let cookie = Cookie::build("foo", "bar")
1309            .http_only(true).finish();
1310        assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
1311
1312        let cookie = Cookie::build("foo", "bar")
1313            .max_age(Duration::seconds(10)).finish();
1314        assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
1315
1316        let cookie = Cookie::build("foo", "bar")
1317            .secure(true).finish();
1318        assert_eq!(&cookie.to_string(), "foo=bar; Secure");
1319
1320        let cookie = Cookie::build("foo", "bar")
1321            .path("/").finish();
1322        assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1323
1324        let cookie = Cookie::build("foo", "bar")
1325            .domain("www.rust-lang.org").finish();
1326        assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
1327
1328        let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
1329        let expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%M:%S GMT").unwrap();
1330        let cookie = Cookie::build("foo", "bar")
1331            .expires(expires).finish();
1332        assert_eq!(&cookie.to_string(),
1333                   "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT");
1334
1335        let cookie = Cookie::build("foo", "bar")
1336            .same_site(SameSite::Strict).finish();
1337        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
1338
1339        let cookie = Cookie::build("foo", "bar")
1340            .same_site(SameSite::Lax).finish();
1341        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
1342
1343        let mut cookie = Cookie::build("foo", "bar")
1344            .same_site(SameSite::None).finish();
1345        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1346
1347        cookie.set_same_site(None);
1348        assert_eq!(&cookie.to_string(), "foo=bar");
1349
1350        let mut cookie = Cookie::build("foo", "bar")
1351            .same_site(SameSite::None)
1352            .secure(false)
1353            .finish();
1354        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None");
1355        cookie.set_secure(true);
1356        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1357    }
1358
1359    #[test]
1360    #[ignore]
1361    fn format_date_wraps() {
1362        let expires = OffsetDateTime::unix_epoch() + Duration::max_value();
1363        let cookie = Cookie::build("foo", "bar")
1364            .expires(expires).finish();
1365        assert_eq!(&cookie.to_string(),
1366                   "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
1367    }
1368
1369    #[test]
1370    fn cookie_string_long_lifetimes() {
1371        let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1372        let (name, value, path, domain) = {
1373            // Create a cookie passing a slice
1374            let c = Cookie::parse(cookie_string.as_str()).unwrap();
1375            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1376        };
1377
1378        assert_eq!(name, Some("bar"));
1379        assert_eq!(value, Some("baz"));
1380        assert_eq!(path, Some("/subdir"));
1381        assert_eq!(domain, Some("crates.io"));
1382    }
1383
1384    #[test]
1385    fn owned_cookie_string() {
1386        let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1387        let (name, value, path, domain) = {
1388            // Create a cookie passing an owned string
1389            let c = Cookie::parse(cookie_string).unwrap();
1390            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1391        };
1392
1393        assert_eq!(name, None);
1394        assert_eq!(value, None);
1395        assert_eq!(path, None);
1396        assert_eq!(domain, None);
1397    }
1398
1399    #[test]
1400    fn owned_cookie_struct() {
1401        let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
1402        let (name, value, path, domain) = {
1403            // Create an owned cookie
1404            let c = Cookie::parse(cookie_string).unwrap().into_owned();
1405
1406            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1407        };
1408
1409        assert_eq!(name, None);
1410        assert_eq!(value, None);
1411        assert_eq!(path, None);
1412        assert_eq!(domain, None);
1413    }
1414
1415    #[test]
1416    #[cfg(feature = "percent-encode")]
1417    fn format_encoded() {
1418        let cookie = Cookie::build("foo !%?=", "bar;;, a").finish();
1419        let cookie_str = cookie.encoded().to_string();
1420        assert_eq!(&cookie_str, "foo%20!%25%3F%3D=bar%3B%3B%2C%20a");
1421
1422        let cookie = Cookie::parse_encoded(cookie_str).unwrap();
1423        assert_eq!(cookie.name_value(), ("foo !%?=", "bar;;, a"));
1424    }
1425}