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;
83 extern crate time;
84 
85 mod builder;
86 mod parse;
87 mod jar;
88 mod delta;
89 mod draft;
90 mod expiration;
91 
92 #[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure;
93 #[cfg(any(feature = "private", feature = "signed"))] pub use secure::*;
94 
95 use std::borrow::Cow;
96 use std::fmt;
97 use std::str::FromStr;
98 
99 #[allow(unused_imports, deprecated)]
100 use std::ascii::AsciiExt;
101 
102 #[cfg(feature = "percent-encode")]
103 use percent_encoding::{AsciiSet, percent_encode as encode};
104 use time::{Duration, OffsetDateTime, UtcOffset};
105 
106 use crate::parse::parse_cookie;
107 pub use crate::parse::ParseError;
108 pub use crate::builder::CookieBuilder;
109 pub use crate::jar::{CookieJar, Delta, Iter};
110 pub use crate::draft::*;
111 pub use crate::expiration::*;
112 
113 #[derive(Debug, Clone)]
114 enum 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 
121 impl<'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.
to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str129     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)]
to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str>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 
into_owned(self) -> CookieStr<'static>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)]
191 pub 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 
215 impl<'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     /// ```
new<N, V>(name: N, value: V) -> Self where N: Into<Cow<'c, str>>, V: Into<Cow<'c, str>>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     /// ```
named<N>(name: N) -> Cookie<'c> where N: Into<Cow<'c, str>>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     /// ```
build<N, V>(name: N, value: V) -> CookieBuilder<'c> where N: Into<Cow<'c, str>>, V: Into<Cow<'c, str>>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     /// ```
parse<S>(s: S) -> Result<Cookie<'c>, ParseError> where S: Into<Cow<'c, str>>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")))]
parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError> where S: Into<Cow<'c, str>>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     /// ```
into_owned(self) -> Cookie<'static>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]
name(&self) -> &str356     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]
value(&self) -> &str371     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]
name_value(&self) -> (&str, &str)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]
http_only(&self) -> Option<bool>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]
secure(&self) -> Option<bool>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]
same_site(&self) -> Option<SameSite>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]
max_age(&self) -> Option<Duration>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]
path(&self) -> Option<&str>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]
domain(&self) -> Option<&str>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]
expires(&self) -> Option<Expiration>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]
expires_datetime(&self) -> Option<OffsetDateTime>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     /// ```
set_name<N: Into<Cow<'c, str>>>(&mut self, name: N)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     /// ```
set_value<V: Into<Cow<'c, str>>>(&mut self, value: V)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]
set_http_only<T: Into<Option<bool>>>(&mut self, value: T)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]
set_secure<T: Into<Option<bool>>>(&mut self, value: T)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]
set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T)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]
set_max_age<D: Into<Option<Duration>>>(&mut self, value: D)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     /// ```
set_path<P: Into<Cow<'c, str>>>(&mut self, path: P)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     /// ```
unset_path(&mut self)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     /// ```
set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D)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     /// ```
unset_domain(&mut self)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     /// ```
set_expires<T: Into<Expiration>>(&mut self, time: T)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     /// ```
unset_expires(&mut self)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     /// ```
make_permanent(&mut self)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     /// ```
make_removal(&mut self)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 
fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result917     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]
name_raw(&self) -> Option<&'c str>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]
value_raw(&self) -> Option<&'c str>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]
path_raw(&self) -> Option<&'c str>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]
domain_raw(&self) -> Option<&'c str>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)]
encoded<'a>(&'a self) -> Display<'a, 'c>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.
1120 assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
1121     #"##)]
1122     /// ```
1123     #[inline(always)]
stripped<'a>(&'a self) -> Display<'a, 'c>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")]
1131 const 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")]
1135 const 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")]
1139 const 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 /// Wrapper around `Cookie` whose `Display` implementation either
1153 /// percent-encodes the cookie's name and value, skips displaying the cookie's
1154 /// parameters (only displaying it's name and value), or both.
1155 ///
1156 /// A value of this type can be obtained via [`Cookie::encoded()`] and
1157 /// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This
1158 /// type should only be used for its `Display` implementation.
1159 ///
1160 /// # Example
1161 ///
1162 /// ```rust
1163 /// use cookie::Cookie;
1164 ///
1165 /// let c = Cookie::build("my name", "this; value%?").secure(true).finish();
1166 /// assert_eq!(&c.stripped().to_string(), "my name=this; value%?");
1167 #[cfg_attr(feature = "percent-encode", doc = r##"
1168 // Note: `encoded()` is only available when `percent-encode` is enabled.
1169 assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure");
1170 assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F");
1171 assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F");
1172 "##)]
1173 /// ```
1174 pub struct Display<'a, 'c: 'a> {
1175     cookie: &'a Cookie<'c>,
1176     #[cfg(feature = "percent-encode")]
1177     encode: bool,
1178     strip: bool,
1179 }
1180 
1181 impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1182     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1183         #[cfg(feature = "percent-encode")] {
1184             if self.encode {
1185                 let name = encode(self.cookie.name().as_bytes(), USERINFO_ENCODE_SET);
1186                 let value = encode(self.cookie.value().as_bytes(), USERINFO_ENCODE_SET);
1187                 write!(f, "{}={}", name, value)?;
1188             } else {
1189                 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1190             }
1191         }
1192 
1193         #[cfg(not(feature = "percent-encode"))] {
1194             write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1195         }
1196 
1197         match self.strip {
1198             true => Ok(()),
1199             false => self.cookie.fmt_parameters(f)
1200         }
1201     }
1202 }
1203 
1204 impl<'a, 'c> Display<'a, 'c> {
1205     #[cfg(feature = "percent-encode")]
new_encoded(cookie: &'a Cookie<'c>) -> Self1206     fn new_encoded(cookie: &'a Cookie<'c>) -> Self {
1207         Display { cookie, strip: false, encode: true }
1208     }
1209 
new_stripped(cookie: &'a Cookie<'c>) -> Self1210     fn new_stripped(cookie: &'a Cookie<'c>) -> Self {
1211         Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false }
1212     }
1213 
1214     /// Percent-encode the name and value pair.
1215     #[inline]
1216     #[cfg(feature = "percent-encode")]
1217     #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
encoded(mut self) -> Self1218     pub fn encoded(mut self) -> Self {
1219         self.encode = true;
1220         self
1221     }
1222 
1223     /// Only display the name and value.
1224     #[inline]
stripped(mut self) -> Self1225     pub fn stripped(mut self) -> Self {
1226         self.strip = true;
1227         self
1228     }
1229 }
1230 
1231 impl<'c> fmt::Display for Cookie<'c> {
1232     /// Formats the cookie `self` as a `Set-Cookie` header value.
1233     ///
1234     /// Does _not_ percent-encode any values. To percent-encode, use
1235     /// [`Cookie::encoded()`].
1236     ///
1237     /// # Example
1238     ///
1239     /// ```rust
1240     /// use cookie::Cookie;
1241     ///
1242     /// let mut cookie = Cookie::build("foo", "bar")
1243     ///     .path("/")
1244     ///     .finish();
1245     ///
1246     /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1247     /// ```
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1248     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1249         write!(f, "{}={}", self.name(), self.value())?;
1250         self.fmt_parameters(f)
1251     }
1252 }
1253 
1254 impl FromStr for Cookie<'static> {
1255     type Err = ParseError;
1256 
from_str(s: &str) -> Result<Cookie<'static>, ParseError>1257     fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
1258         Cookie::parse(s).map(|c| c.into_owned())
1259     }
1260 }
1261 
1262 impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
eq(&self, other: &Cookie<'b>) -> bool1263     fn eq(&self, other: &Cookie<'b>) -> bool {
1264         let so_far_so_good = self.name() == other.name()
1265             && self.value() == other.value()
1266             && self.http_only() == other.http_only()
1267             && self.secure() == other.secure()
1268             && self.max_age() == other.max_age()
1269             && self.expires() == other.expires();
1270 
1271         if !so_far_so_good {
1272             return false;
1273         }
1274 
1275         match (self.path(), other.path()) {
1276             (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1277             (None, None) => {}
1278             _ => return false,
1279         };
1280 
1281         match (self.domain(), other.domain()) {
1282             (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1283             (None, None) => {}
1284             _ => return false,
1285         };
1286 
1287         true
1288     }
1289 }
1290 
1291 #[cfg(test)]
1292 mod tests {
1293     use crate::{Cookie, SameSite};
1294     use crate::parse::parse_gmt_date;
1295     use crate::{time::Duration, OffsetDateTime};
1296 
1297     #[test]
format()1298     fn format() {
1299         let cookie = Cookie::new("foo", "bar");
1300         assert_eq!(&cookie.to_string(), "foo=bar");
1301 
1302         let cookie = Cookie::build("foo", "bar")
1303             .http_only(true).finish();
1304         assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
1305 
1306         let cookie = Cookie::build("foo", "bar")
1307             .max_age(Duration::seconds(10)).finish();
1308         assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
1309 
1310         let cookie = Cookie::build("foo", "bar")
1311             .secure(true).finish();
1312         assert_eq!(&cookie.to_string(), "foo=bar; Secure");
1313 
1314         let cookie = Cookie::build("foo", "bar")
1315             .path("/").finish();
1316         assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1317 
1318         let cookie = Cookie::build("foo", "bar")
1319             .domain("www.rust-lang.org").finish();
1320         assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
1321 
1322         let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
1323         let expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%M:%S GMT").unwrap();
1324         let cookie = Cookie::build("foo", "bar")
1325             .expires(expires).finish();
1326         assert_eq!(&cookie.to_string(),
1327                    "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT");
1328 
1329         let cookie = Cookie::build("foo", "bar")
1330             .same_site(SameSite::Strict).finish();
1331         assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
1332 
1333         let cookie = Cookie::build("foo", "bar")
1334             .same_site(SameSite::Lax).finish();
1335         assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
1336 
1337         let mut cookie = Cookie::build("foo", "bar")
1338             .same_site(SameSite::None).finish();
1339         assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1340 
1341         cookie.set_same_site(None);
1342         assert_eq!(&cookie.to_string(), "foo=bar");
1343 
1344         let mut cookie = Cookie::build("foo", "bar")
1345             .same_site(SameSite::None)
1346             .secure(false)
1347             .finish();
1348         assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None");
1349         cookie.set_secure(true);
1350         assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1351     }
1352 
1353     #[test]
1354     #[ignore]
format_date_wraps()1355     fn format_date_wraps() {
1356         let expires = OffsetDateTime::unix_epoch() + Duration::max_value();
1357         let cookie = Cookie::build("foo", "bar")
1358             .expires(expires).finish();
1359         assert_eq!(&cookie.to_string(),
1360                    "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
1361     }
1362 
1363     #[test]
cookie_string_long_lifetimes()1364     fn cookie_string_long_lifetimes() {
1365         let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1366         let (name, value, path, domain) = {
1367             // Create a cookie passing a slice
1368             let c = Cookie::parse(cookie_string.as_str()).unwrap();
1369             (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1370         };
1371 
1372         assert_eq!(name, Some("bar"));
1373         assert_eq!(value, Some("baz"));
1374         assert_eq!(path, Some("/subdir"));
1375         assert_eq!(domain, Some("crates.io"));
1376     }
1377 
1378     #[test]
owned_cookie_string()1379     fn owned_cookie_string() {
1380         let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1381         let (name, value, path, domain) = {
1382             // Create a cookie passing an owned string
1383             let c = Cookie::parse(cookie_string).unwrap();
1384             (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1385         };
1386 
1387         assert_eq!(name, None);
1388         assert_eq!(value, None);
1389         assert_eq!(path, None);
1390         assert_eq!(domain, None);
1391     }
1392 
1393     #[test]
owned_cookie_struct()1394     fn owned_cookie_struct() {
1395         let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
1396         let (name, value, path, domain) = {
1397             // Create an owned cookie
1398             let c = Cookie::parse(cookie_string).unwrap().into_owned();
1399 
1400             (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1401         };
1402 
1403         assert_eq!(name, None);
1404         assert_eq!(value, None);
1405         assert_eq!(path, None);
1406         assert_eq!(domain, None);
1407     }
1408 
1409     #[test]
1410     #[cfg(feature = "percent-encode")]
format_encoded()1411     fn format_encoded() {
1412         let cookie = Cookie::build("foo !?=", "bar;; a").finish();
1413         let cookie_str = cookie.encoded().to_string();
1414         assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a");
1415 
1416         let cookie = Cookie::parse_encoded(cookie_str).unwrap();
1417         assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a"));
1418     }
1419 }
1420