actori_http/cookie/
mod.rs

1//! https://github.com/alexcrichton/cookie-rs fork
2//!
3//! HTTP cookie parsing and cookie jar management.
4//!
5//! This crates provides the [`Cookie`](struct.Cookie.html) type, which directly
6//! maps to an HTTP cookie, and the [`CookieJar`](struct.CookieJar.html) type,
7//! which allows for simple management of many cookies as well as encryption and
8//! signing of cookies for session management.
9//!
10//! # Features
11//!
12//! This crates can be configured at compile-time through the following Cargo
13//! features:
14//!
15//!
16//! * **secure** (disabled by default)
17//!
18//!   Enables signed and private (signed + encrypted) cookie jars.
19//!
20//!   When this feature is enabled, the
21//!   [signed](struct.CookieJar.html#method.signed) and
22//!   [private](struct.CookieJar.html#method.private) method of `CookieJar` and
23//!   [`SignedJar`](struct.SignedJar.html) and
24//!   [`PrivateJar`](struct.PrivateJar.html) structures are available. The jars
25//!   act as "children jars", allowing for easy retrieval and addition of signed
26//!   and/or encrypted cookies to a cookie jar. When this feature is disabled,
27//!   none of the types are available.
28//!
29//! * **percent-encode** (disabled by default)
30//!
31//!   Enables percent encoding and decoding of names and values in cookies.
32//!
33//!   When this feature is enabled, the
34//!   [encoded](struct.Cookie.html#method.encoded) and
35//!   [`parse_encoded`](struct.Cookie.html#method.parse_encoded) methods of
36//!   `Cookie` become available. The `encoded` method returns a wrapper around a
37//!   `Cookie` whose `Display` implementation percent-encodes the name and value
38//!   of the cookie. The `parse_encoded` method percent-decodes the name and
39//!   value of a `Cookie` during parsing. When this feature is disabled, the
40//!   `encoded` and `parse_encoded` methods are not available.
41//!
42//! You can enable features via the `Cargo.toml` file:
43//!
44//! ```ignore
45//! [dependencies.cookie]
46//! features = ["secure", "percent-encode"]
47//! ```
48
49#![doc(html_root_url = "https://docs.rs/cookie/0.11")]
50#![deny(missing_docs)]
51
52mod builder;
53mod delta;
54mod draft;
55mod jar;
56mod parse;
57
58#[cfg(feature = "secure-cookies")]
59#[macro_use]
60mod secure;
61#[cfg(feature = "secure-cookies")]
62pub use self::secure::*;
63
64use std::borrow::Cow;
65use std::fmt;
66use std::str::FromStr;
67
68use chrono::Duration;
69use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
70use time::Tm;
71
72pub use self::builder::CookieBuilder;
73pub use self::draft::*;
74pub use self::jar::{CookieJar, Delta, Iter};
75use self::parse::parse_cookie;
76pub use self::parse::ParseError;
77
78/// https://url.spec.whatwg.org/#fragment-percent-encode-set
79const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
80
81/// https://url.spec.whatwg.org/#path-percent-encode-set
82const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
83
84/// https://url.spec.whatwg.org/#userinfo-percent-encode-set
85pub const USERINFO: &AsciiSet = &PATH
86    .add(b'/')
87    .add(b':')
88    .add(b';')
89    .add(b'=')
90    .add(b'@')
91    .add(b'[')
92    .add(b'\\')
93    .add(b']')
94    .add(b'^')
95    .add(b'|');
96
97#[derive(Debug, Clone)]
98enum CookieStr {
99    /// An string derived from indexes (start, end).
100    Indexed(usize, usize),
101    /// A string derived from a concrete string.
102    Concrete(Cow<'static, str>),
103}
104
105impl CookieStr {
106    /// Retrieves the string `self` corresponds to. If `self` is derived from
107    /// indexes, the corresponding subslice of `string` is returned. Otherwise,
108    /// the concrete string is returned.
109    ///
110    /// # Panics
111    ///
112    /// Panics if `self` is an indexed string and `string` is None.
113    fn to_str<'s>(&'s self, string: Option<&'s Cow<'_, str>>) -> &'s str {
114        match *self {
115            CookieStr::Indexed(i, j) => {
116                let s = string.expect(
117                    "`Some` base string must exist when \
118                     converting indexed str to str! (This is a module invariant.)",
119                );
120                &s[i..j]
121            }
122            CookieStr::Concrete(ref cstr) => &*cstr,
123        }
124    }
125
126    #[allow(clippy::ptr_arg)]
127    fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> {
128        match *self {
129            CookieStr::Indexed(i, j) => match *string {
130                Cow::Borrowed(s) => Some(&s[i..j]),
131                Cow::Owned(_) => None,
132            },
133            CookieStr::Concrete(_) => None,
134        }
135    }
136}
137
138/// Representation of an HTTP cookie.
139///
140/// # Constructing a `Cookie`
141///
142/// To construct a cookie with only a name/value, use the [new](#method.new)
143/// method:
144///
145/// ```rust
146/// use actori_http::cookie::Cookie;
147///
148/// let cookie = Cookie::new("name", "value");
149/// assert_eq!(&cookie.to_string(), "name=value");
150/// ```
151///
152/// To construct more elaborate cookies, use the [build](#method.build) method
153/// and [`CookieBuilder`](struct.CookieBuilder.html) methods:
154///
155/// ```rust
156/// use actori_http::cookie::Cookie;
157///
158/// let cookie = Cookie::build("name", "value")
159///     .domain("www.rust-lang.org")
160///     .path("/")
161///     .secure(true)
162///     .http_only(true)
163///     .finish();
164/// ```
165#[derive(Debug, Clone)]
166pub struct Cookie<'c> {
167    /// Storage for the cookie string. Only used if this structure was derived
168    /// from a string that was subsequently parsed.
169    cookie_string: Option<Cow<'c, str>>,
170    /// The cookie's name.
171    name: CookieStr,
172    /// The cookie's value.
173    value: CookieStr,
174    /// The cookie's expiration, if any.
175    expires: Option<Tm>,
176    /// The cookie's maximum age, if any.
177    max_age: Option<Duration>,
178    /// The cookie's domain, if any.
179    domain: Option<CookieStr>,
180    /// The cookie's path domain, if any.
181    path: Option<CookieStr>,
182    /// Whether this cookie was marked Secure.
183    secure: Option<bool>,
184    /// Whether this cookie was marked HttpOnly.
185    http_only: Option<bool>,
186    /// The draft `SameSite` attribute.
187    same_site: Option<SameSite>,
188}
189
190impl Cookie<'static> {
191    /// Creates a new `Cookie` with the given name and value.
192    ///
193    /// # Example
194    ///
195    /// ```rust
196    /// use actori_http::cookie::Cookie;
197    ///
198    /// let cookie = Cookie::new("name", "value");
199    /// assert_eq!(cookie.name_value(), ("name", "value"));
200    /// ```
201    pub fn new<N, V>(name: N, value: V) -> Cookie<'static>
202    where
203        N: Into<Cow<'static, str>>,
204        V: Into<Cow<'static, str>>,
205    {
206        Cookie {
207            cookie_string: None,
208            name: CookieStr::Concrete(name.into()),
209            value: CookieStr::Concrete(value.into()),
210            expires: None,
211            max_age: None,
212            domain: None,
213            path: None,
214            secure: None,
215            http_only: None,
216            same_site: None,
217        }
218    }
219
220    /// Creates a new `Cookie` with the given name and an empty value.
221    ///
222    /// # Example
223    ///
224    /// ```rust
225    /// use actori_http::cookie::Cookie;
226    ///
227    /// let cookie = Cookie::named("name");
228    /// assert_eq!(cookie.name(), "name");
229    /// assert!(cookie.value().is_empty());
230    /// ```
231    pub fn named<N>(name: N) -> Cookie<'static>
232    where
233        N: Into<Cow<'static, str>>,
234    {
235        Cookie::new(name, "")
236    }
237
238    /// Creates a new `CookieBuilder` instance from the given key and value
239    /// strings.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// use actori_http::cookie::Cookie;
245    ///
246    /// let c = Cookie::build("foo", "bar").finish();
247    /// assert_eq!(c.name_value(), ("foo", "bar"));
248    /// ```
249    pub fn build<N, V>(name: N, value: V) -> CookieBuilder
250    where
251        N: Into<Cow<'static, str>>,
252        V: Into<Cow<'static, str>>,
253    {
254        CookieBuilder::new(name, value)
255    }
256}
257
258impl<'c> Cookie<'c> {
259    /// Parses a `Cookie` from the given HTTP cookie header value string. Does
260    /// not perform any percent-decoding.
261    ///
262    /// # Example
263    ///
264    /// ```rust
265    /// use actori_http::cookie::Cookie;
266    ///
267    /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap();
268    /// assert_eq!(c.name_value(), ("foo", "bar%20baz"));
269    /// assert_eq!(c.http_only(), Some(true));
270    /// ```
271    pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
272    where
273        S: Into<Cow<'c, str>>,
274    {
275        parse_cookie(s, false)
276    }
277
278    /// Parses a `Cookie` from the given HTTP cookie header value string where
279    /// the name and value fields are percent-encoded. Percent-decodes the
280    /// name/value fields.
281    ///
282    /// This API requires the `percent-encode` feature to be enabled on this
283    /// crate.
284    ///
285    /// # Example
286    ///
287    /// ```rust
288    /// use actori_http::cookie::Cookie;
289    ///
290    /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap();
291    /// assert_eq!(c.name_value(), ("foo", "bar baz"));
292    /// assert_eq!(c.http_only(), Some(true));
293    /// ```
294    pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
295    where
296        S: Into<Cow<'c, str>>,
297    {
298        parse_cookie(s, true)
299    }
300
301    /// Wraps `self` in an `EncodedCookie`: a cost-free wrapper around `Cookie`
302    /// whose `Display` implementation percent-encodes the name and value of the
303    /// wrapped `Cookie`.
304    ///
305    /// This method is only available when the `percent-encode` feature is
306    /// enabled.
307    ///
308    /// # Example
309    ///
310    /// ```rust
311    /// use actori_http::cookie::Cookie;
312    ///
313    /// let mut c = Cookie::new("my name", "this; value?");
314    /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F");
315    /// ```
316    pub fn encoded<'a>(&'a self) -> EncodedCookie<'a, 'c> {
317        EncodedCookie(self)
318    }
319
320    /// Converts `self` into a `Cookie` with a static lifetime. This method
321    /// results in at most one allocation.
322    ///
323    /// # Example
324    ///
325    /// ```rust
326    /// use actori_http::cookie::Cookie;
327    ///
328    /// let c = Cookie::new("a", "b");
329    /// let owned_cookie = c.into_owned();
330    /// assert_eq!(owned_cookie.name_value(), ("a", "b"));
331    /// ```
332    pub fn into_owned(self) -> Cookie<'static> {
333        Cookie {
334            cookie_string: self.cookie_string.map(|s| s.into_owned().into()),
335            name: self.name,
336            value: self.value,
337            expires: self.expires,
338            max_age: self.max_age,
339            domain: self.domain,
340            path: self.path,
341            secure: self.secure,
342            http_only: self.http_only,
343            same_site: self.same_site,
344        }
345    }
346
347    /// Returns the name of `self`.
348    ///
349    /// # Example
350    ///
351    /// ```rust
352    /// use actori_http::cookie::Cookie;
353    ///
354    /// let c = Cookie::new("name", "value");
355    /// assert_eq!(c.name(), "name");
356    /// ```
357    #[inline]
358    pub fn name(&self) -> &str {
359        self.name.to_str(self.cookie_string.as_ref())
360    }
361
362    /// Returns the value of `self`.
363    ///
364    /// # Example
365    ///
366    /// ```rust
367    /// use actori_http::cookie::Cookie;
368    ///
369    /// let c = Cookie::new("name", "value");
370    /// assert_eq!(c.value(), "value");
371    /// ```
372    #[inline]
373    pub fn value(&self) -> &str {
374        self.value.to_str(self.cookie_string.as_ref())
375    }
376
377    /// Returns the name and value of `self` as a tuple of `(name, value)`.
378    ///
379    /// # Example
380    ///
381    /// ```rust
382    /// use actori_http::cookie::Cookie;
383    ///
384    /// let c = Cookie::new("name", "value");
385    /// assert_eq!(c.name_value(), ("name", "value"));
386    /// ```
387    #[inline]
388    pub fn name_value(&self) -> (&str, &str) {
389        (self.name(), self.value())
390    }
391
392    /// Returns whether this cookie was marked `HttpOnly` or not. Returns
393    /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
394    /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`,
395    /// and `None` otherwise.
396    ///
397    /// # Example
398    ///
399    /// ```rust
400    /// use actori_http::cookie::Cookie;
401    ///
402    /// let c = Cookie::parse("name=value; httponly").unwrap();
403    /// assert_eq!(c.http_only(), Some(true));
404    ///
405    /// let mut c = Cookie::new("name", "value");
406    /// assert_eq!(c.http_only(), None);
407    ///
408    /// let mut c = Cookie::new("name", "value");
409    /// assert_eq!(c.http_only(), None);
410    ///
411    /// // An explicitly set "false" value.
412    /// c.set_http_only(false);
413    /// assert_eq!(c.http_only(), Some(false));
414    ///
415    /// // An explicitly set "true" value.
416    /// c.set_http_only(true);
417    /// assert_eq!(c.http_only(), Some(true));
418    /// ```
419    #[inline]
420    pub fn http_only(&self) -> Option<bool> {
421        self.http_only
422    }
423
424    /// Returns whether this cookie was marked `Secure` or not. Returns
425    /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
426    /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and
427    /// `None` otherwise.
428    ///
429    /// # Example
430    ///
431    /// ```rust
432    /// use actori_http::cookie::Cookie;
433    ///
434    /// let c = Cookie::parse("name=value; Secure").unwrap();
435    /// assert_eq!(c.secure(), Some(true));
436    ///
437    /// let mut c = Cookie::parse("name=value").unwrap();
438    /// assert_eq!(c.secure(), None);
439    ///
440    /// let mut c = Cookie::new("name", "value");
441    /// assert_eq!(c.secure(), None);
442    ///
443    /// // An explicitly set "false" value.
444    /// c.set_secure(false);
445    /// assert_eq!(c.secure(), Some(false));
446    ///
447    /// // An explicitly set "true" value.
448    /// c.set_secure(true);
449    /// assert_eq!(c.secure(), Some(true));
450    /// ```
451    #[inline]
452    pub fn secure(&self) -> Option<bool> {
453        self.secure
454    }
455
456    /// Returns the `SameSite` attribute of this cookie if one was specified.
457    ///
458    /// # Example
459    ///
460    /// ```rust
461    /// use actori_http::cookie::{Cookie, SameSite};
462    ///
463    /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap();
464    /// assert_eq!(c.same_site(), Some(SameSite::Lax));
465    /// ```
466    #[inline]
467    pub fn same_site(&self) -> Option<SameSite> {
468        self.same_site
469    }
470
471    /// Returns the specified max-age of the cookie if one was specified.
472    ///
473    /// # Example
474    ///
475    /// ```rust
476    /// use actori_http::cookie::Cookie;
477    ///
478    /// let c = Cookie::parse("name=value").unwrap();
479    /// assert_eq!(c.max_age(), None);
480    ///
481    /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
482    /// assert_eq!(c.max_age().map(|age| age.num_hours()), Some(1));
483    /// ```
484    #[inline]
485    pub fn max_age(&self) -> Option<Duration> {
486        self.max_age
487    }
488
489    /// Returns the `Path` of the cookie if one was specified.
490    ///
491    /// # Example
492    ///
493    /// ```rust
494    /// use actori_http::cookie::Cookie;
495    ///
496    /// let c = Cookie::parse("name=value").unwrap();
497    /// assert_eq!(c.path(), None);
498    ///
499    /// let c = Cookie::parse("name=value; Path=/").unwrap();
500    /// assert_eq!(c.path(), Some("/"));
501    ///
502    /// let c = Cookie::parse("name=value; path=/sub").unwrap();
503    /// assert_eq!(c.path(), Some("/sub"));
504    /// ```
505    #[inline]
506    pub fn path(&self) -> Option<&str> {
507        match self.path {
508            Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
509            None => None,
510        }
511    }
512
513    /// Returns the `Domain` of the cookie if one was specified.
514    ///
515    /// # Example
516    ///
517    /// ```rust
518    /// use actori_http::cookie::Cookie;
519    ///
520    /// let c = Cookie::parse("name=value").unwrap();
521    /// assert_eq!(c.domain(), None);
522    ///
523    /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap();
524    /// assert_eq!(c.domain(), Some("crates.io"));
525    /// ```
526    #[inline]
527    pub fn domain(&self) -> Option<&str> {
528        match self.domain {
529            Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
530            None => None,
531        }
532    }
533
534    /// Returns the `Expires` time of the cookie if one was specified.
535    ///
536    /// # Example
537    ///
538    /// ```rust
539    /// use actori_http::cookie::Cookie;
540    ///
541    /// let c = Cookie::parse("name=value").unwrap();
542    /// assert_eq!(c.expires(), None);
543    ///
544    /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
545    /// let cookie_str = format!("name=value; Expires={}", expire_time);
546    /// let c = Cookie::parse(cookie_str).unwrap();
547    /// assert_eq!(c.expires().map(|t| t.tm_year), Some(117));
548    /// ```
549    #[inline]
550    pub fn expires(&self) -> Option<Tm> {
551        self.expires
552    }
553
554    /// Sets the name of `self` to `name`.
555    ///
556    /// # Example
557    ///
558    /// ```rust
559    /// use actori_http::cookie::Cookie;
560    ///
561    /// let mut c = Cookie::new("name", "value");
562    /// assert_eq!(c.name(), "name");
563    ///
564    /// c.set_name("foo");
565    /// assert_eq!(c.name(), "foo");
566    /// ```
567    pub fn set_name<N: Into<Cow<'static, str>>>(&mut self, name: N) {
568        self.name = CookieStr::Concrete(name.into())
569    }
570
571    /// Sets the value of `self` to `value`.
572    ///
573    /// # Example
574    ///
575    /// ```rust
576    /// use actori_http::cookie::Cookie;
577    ///
578    /// let mut c = Cookie::new("name", "value");
579    /// assert_eq!(c.value(), "value");
580    ///
581    /// c.set_value("bar");
582    /// assert_eq!(c.value(), "bar");
583    /// ```
584    pub fn set_value<V: Into<Cow<'static, str>>>(&mut self, value: V) {
585        self.value = CookieStr::Concrete(value.into())
586    }
587
588    /// Sets the value of `http_only` in `self` to `value`.
589    ///
590    /// # Example
591    ///
592    /// ```rust
593    /// use actori_http::cookie::Cookie;
594    ///
595    /// let mut c = Cookie::new("name", "value");
596    /// assert_eq!(c.http_only(), None);
597    ///
598    /// c.set_http_only(true);
599    /// assert_eq!(c.http_only(), Some(true));
600    /// ```
601    #[inline]
602    pub fn set_http_only(&mut self, value: bool) {
603        self.http_only = Some(value);
604    }
605
606    /// Sets the value of `secure` in `self` to `value`.
607    ///
608    /// # Example
609    ///
610    /// ```rust
611    /// use actori_http::cookie::Cookie;
612    ///
613    /// let mut c = Cookie::new("name", "value");
614    /// assert_eq!(c.secure(), None);
615    ///
616    /// c.set_secure(true);
617    /// assert_eq!(c.secure(), Some(true));
618    /// ```
619    #[inline]
620    pub fn set_secure(&mut self, value: bool) {
621        self.secure = Some(value);
622    }
623
624    /// Sets the value of `same_site` in `self` to `value`.
625    ///
626    /// # Example
627    ///
628    /// ```rust
629    /// use actori_http::cookie::{Cookie, SameSite};
630    ///
631    /// let mut c = Cookie::new("name", "value");
632    /// assert!(c.same_site().is_none());
633    ///
634    /// c.set_same_site(SameSite::Strict);
635    /// assert_eq!(c.same_site(), Some(SameSite::Strict));
636    /// ```
637    #[inline]
638    pub fn set_same_site(&mut self, value: SameSite) {
639        self.same_site = Some(value);
640    }
641
642    /// Sets the value of `max_age` in `self` to `value`.
643    ///
644    /// # Example
645    ///
646    /// ```rust
647    /// use actori_http::cookie::Cookie;
648    /// use chrono::Duration;
649    ///
650    /// let mut c = Cookie::new("name", "value");
651    /// assert_eq!(c.max_age(), None);
652    ///
653    /// c.set_max_age(Duration::hours(10));
654    /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
655    /// ```
656    #[inline]
657    pub fn set_max_age(&mut self, value: Duration) {
658        self.max_age = Some(value);
659    }
660
661    /// Sets the `path` of `self` to `path`.
662    ///
663    /// # Example
664    ///
665    /// ```rust
666    /// use actori_http::cookie::Cookie;
667    ///
668    /// let mut c = Cookie::new("name", "value");
669    /// assert_eq!(c.path(), None);
670    ///
671    /// c.set_path("/");
672    /// assert_eq!(c.path(), Some("/"));
673    /// ```
674    pub fn set_path<P: Into<Cow<'static, str>>>(&mut self, path: P) {
675        self.path = Some(CookieStr::Concrete(path.into()));
676    }
677
678    /// Sets the `domain` of `self` to `domain`.
679    ///
680    /// # Example
681    ///
682    /// ```rust
683    /// use actori_http::cookie::Cookie;
684    ///
685    /// let mut c = Cookie::new("name", "value");
686    /// assert_eq!(c.domain(), None);
687    ///
688    /// c.set_domain("rust-lang.org");
689    /// assert_eq!(c.domain(), Some("rust-lang.org"));
690    /// ```
691    pub fn set_domain<D: Into<Cow<'static, str>>>(&mut self, domain: D) {
692        self.domain = Some(CookieStr::Concrete(domain.into()));
693    }
694
695    /// Sets the expires field of `self` to `time`.
696    ///
697    /// # Example
698    ///
699    /// ```rust
700    /// use actori_http::cookie::Cookie;
701    ///
702    /// let mut c = Cookie::new("name", "value");
703    /// assert_eq!(c.expires(), None);
704    ///
705    /// let mut now = time::now();
706    /// now.tm_year += 1;
707    ///
708    /// c.set_expires(now);
709    /// assert!(c.expires().is_some())
710    /// ```
711    #[inline]
712    pub fn set_expires(&mut self, time: Tm) {
713        self.expires = Some(time);
714    }
715
716    /// Makes `self` a "permanent" cookie by extending its expiration and max
717    /// age 20 years into the future.
718    ///
719    /// # Example
720    ///
721    /// ```rust
722    /// use actori_http::cookie::Cookie;
723    /// use chrono::Duration;
724    ///
725    /// let mut c = Cookie::new("foo", "bar");
726    /// assert!(c.expires().is_none());
727    /// assert!(c.max_age().is_none());
728    ///
729    /// c.make_permanent();
730    /// assert!(c.expires().is_some());
731    /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
732    /// ```
733    pub fn make_permanent(&mut self) {
734        let twenty_years = Duration::days(365 * 20);
735        self.set_max_age(twenty_years);
736        self.set_expires(time::now() + twenty_years);
737    }
738
739    fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740        if let Some(true) = self.http_only() {
741            write!(f, "; HttpOnly")?;
742        }
743
744        if let Some(true) = self.secure() {
745            write!(f, "; Secure")?;
746        }
747
748        if let Some(same_site) = self.same_site() {
749            if !same_site.is_none() {
750                write!(f, "; SameSite={}", same_site)?;
751            }
752        }
753
754        if let Some(path) = self.path() {
755            write!(f, "; Path={}", path)?;
756        }
757
758        if let Some(domain) = self.domain() {
759            write!(f, "; Domain={}", domain)?;
760        }
761
762        if let Some(max_age) = self.max_age() {
763            write!(f, "; Max-Age={}", max_age.num_seconds())?;
764        }
765
766        if let Some(time) = self.expires() {
767            write!(f, "; Expires={}", time.rfc822())?;
768        }
769
770        Ok(())
771    }
772
773    /// Returns the name of `self` as a string slice of the raw string `self`
774    /// was originally parsed from. If `self` was not originally parsed from a
775    /// raw string, returns `None`.
776    ///
777    /// This method differs from [name](#method.name) in that it returns a
778    /// string with the same lifetime as the originally parsed string. This
779    /// lifetime may outlive `self`. If a longer lifetime is not required, or
780    /// you're unsure if you need a longer lifetime, use [name](#method.name).
781    ///
782    /// # Example
783    ///
784    /// ```rust
785    /// use actori_http::cookie::Cookie;
786    ///
787    /// let cookie_string = format!("{}={}", "foo", "bar");
788    ///
789    /// // `c` will be dropped at the end of the scope, but `name` will live on
790    /// let name = {
791    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
792    ///     c.name_raw()
793    /// };
794    ///
795    /// assert_eq!(name, Some("foo"));
796    /// ```
797    #[inline]
798    pub fn name_raw(&self) -> Option<&'c str> {
799        self.cookie_string
800            .as_ref()
801            .and_then(|s| self.name.to_raw_str(s))
802    }
803
804    /// Returns the value of `self` as a string slice of the raw string `self`
805    /// was originally parsed from. If `self` was not originally parsed from a
806    /// raw string, returns `None`.
807    ///
808    /// This method differs from [value](#method.value) in that it returns a
809    /// string with the same lifetime as the originally parsed string. This
810    /// lifetime may outlive `self`. If a longer lifetime is not required, or
811    /// you're unsure if you need a longer lifetime, use [value](#method.value).
812    ///
813    /// # Example
814    ///
815    /// ```rust
816    /// use actori_http::cookie::Cookie;
817    ///
818    /// let cookie_string = format!("{}={}", "foo", "bar");
819    ///
820    /// // `c` will be dropped at the end of the scope, but `value` will live on
821    /// let value = {
822    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
823    ///     c.value_raw()
824    /// };
825    ///
826    /// assert_eq!(value, Some("bar"));
827    /// ```
828    #[inline]
829    pub fn value_raw(&self) -> Option<&'c str> {
830        self.cookie_string
831            .as_ref()
832            .and_then(|s| self.value.to_raw_str(s))
833    }
834
835    /// Returns the `Path` of `self` as a string slice of the raw string `self`
836    /// was originally parsed from. If `self` was not originally parsed from a
837    /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has
838    /// changed since parsing, returns `None`.
839    ///
840    /// This method differs from [path](#method.path) in that it returns a
841    /// string with the same lifetime as the originally parsed string. This
842    /// lifetime may outlive `self`. If a longer lifetime is not required, or
843    /// you're unsure if you need a longer lifetime, use [path](#method.path).
844    ///
845    /// # Example
846    ///
847    /// ```rust
848    /// use actori_http::cookie::Cookie;
849    ///
850    /// let cookie_string = format!("{}={}; Path=/", "foo", "bar");
851    ///
852    /// // `c` will be dropped at the end of the scope, but `path` will live on
853    /// let path = {
854    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
855    ///     c.path_raw()
856    /// };
857    ///
858    /// assert_eq!(path, Some("/"));
859    /// ```
860    #[inline]
861    pub fn path_raw(&self) -> Option<&'c str> {
862        match (self.path.as_ref(), self.cookie_string.as_ref()) {
863            (Some(path), Some(string)) => path.to_raw_str(string),
864            _ => None,
865        }
866    }
867
868    /// Returns the `Domain` of `self` as a string slice of the raw string
869    /// `self` was originally parsed from. If `self` was not originally parsed
870    /// from a raw string, or if `self` doesn't contain a `Domain`, or if the
871    /// `Domain` has changed since parsing, returns `None`.
872    ///
873    /// This method differs from [domain](#method.domain) in that it returns a
874    /// string with the same lifetime as the originally parsed string. This
875    /// lifetime may outlive `self` struct. If a longer lifetime is not
876    /// required, or you're unsure if you need a longer lifetime, use
877    /// [domain](#method.domain).
878    ///
879    /// # Example
880    ///
881    /// ```rust
882    /// use actori_http::cookie::Cookie;
883    ///
884    /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar");
885    ///
886    /// //`c` will be dropped at the end of the scope, but `domain` will live on
887    /// let domain = {
888    ///     let c = Cookie::parse(cookie_string.as_str()).unwrap();
889    ///     c.domain_raw()
890    /// };
891    ///
892    /// assert_eq!(domain, Some("crates.io"));
893    /// ```
894    #[inline]
895    pub fn domain_raw(&self) -> Option<&'c str> {
896        match (self.domain.as_ref(), self.cookie_string.as_ref()) {
897            (Some(domain), Some(string)) => domain.to_raw_str(string),
898            _ => None,
899        }
900    }
901}
902
903/// Wrapper around `Cookie` whose `Display` implementation percent-encodes the
904/// cookie's name and value.
905///
906/// A value of this type can be obtained via the
907/// [encoded](struct.Cookie.html#method.encoded) method on
908/// [Cookie](struct.Cookie.html). This type should only be used for its
909/// `Display` implementation.
910///
911/// This type is only available when the `percent-encode` feature is enabled.
912///
913/// # Example
914///
915/// ```rust
916/// use actori_http::cookie::Cookie;
917///
918/// let mut c = Cookie::new("my name", "this; value?");
919/// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F");
920/// ```
921pub struct EncodedCookie<'a, 'c>(&'a Cookie<'c>);
922
923impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> {
924    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
925        // Percent-encode the name and value.
926        let name = percent_encode(self.0.name().as_bytes(), USERINFO);
927        let value = percent_encode(self.0.value().as_bytes(), USERINFO);
928
929        // Write out the name/value pair and the cookie's parameters.
930        write!(f, "{}={}", name, value)?;
931        self.0.fmt_parameters(f)
932    }
933}
934
935impl<'c> fmt::Display for Cookie<'c> {
936    /// Formats the cookie `self` as a `Set-Cookie` header value.
937    ///
938    /// # Example
939    ///
940    /// ```rust
941    /// use actori_http::cookie::Cookie;
942    ///
943    /// let mut cookie = Cookie::build("foo", "bar")
944    ///     .path("/")
945    ///     .finish();
946    ///
947    /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
948    /// ```
949    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
950        write!(f, "{}={}", self.name(), self.value())?;
951        self.fmt_parameters(f)
952    }
953}
954
955impl FromStr for Cookie<'static> {
956    type Err = ParseError;
957
958    fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
959        Cookie::parse(s).map(|c| c.into_owned())
960    }
961}
962
963impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
964    fn eq(&self, other: &Cookie<'b>) -> bool {
965        let so_far_so_good = self.name() == other.name()
966            && self.value() == other.value()
967            && self.http_only() == other.http_only()
968            && self.secure() == other.secure()
969            && self.max_age() == other.max_age()
970            && self.expires() == other.expires();
971
972        if !so_far_so_good {
973            return false;
974        }
975
976        match (self.path(), other.path()) {
977            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
978            (None, None) => {}
979            _ => return false,
980        };
981
982        match (self.domain(), other.domain()) {
983            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
984            (None, None) => {}
985            _ => return false,
986        };
987
988        true
989    }
990}
991
992#[cfg(test)]
993mod tests {
994    use super::{Cookie, SameSite};
995    use time::strptime;
996
997    #[test]
998    fn format() {
999        let cookie = Cookie::new("foo", "bar");
1000        assert_eq!(&cookie.to_string(), "foo=bar");
1001
1002        let cookie = Cookie::build("foo", "bar").http_only(true).finish();
1003        assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
1004
1005        let cookie = Cookie::build("foo", "bar").max_age(10).finish();
1006        assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
1007
1008        let cookie = Cookie::build("foo", "bar").secure(true).finish();
1009        assert_eq!(&cookie.to_string(), "foo=bar; Secure");
1010
1011        let cookie = Cookie::build("foo", "bar").path("/").finish();
1012        assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1013
1014        let cookie = Cookie::build("foo", "bar")
1015            .domain("www.rust-lang.org")
1016            .finish();
1017        assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
1018
1019        let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
1020        let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap();
1021        let cookie = Cookie::build("foo", "bar").expires(expires).finish();
1022        assert_eq!(
1023            &cookie.to_string(),
1024            "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT"
1025        );
1026
1027        let cookie = Cookie::build("foo", "bar")
1028            .same_site(SameSite::Strict)
1029            .finish();
1030        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
1031
1032        let cookie = Cookie::build("foo", "bar")
1033            .same_site(SameSite::Lax)
1034            .finish();
1035        assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
1036
1037        let cookie = Cookie::build("foo", "bar")
1038            .same_site(SameSite::None)
1039            .finish();
1040        assert_eq!(&cookie.to_string(), "foo=bar");
1041    }
1042
1043    #[test]
1044    fn cookie_string_long_lifetimes() {
1045        let cookie_string =
1046            "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1047        let (name, value, path, domain) = {
1048            // Create a cookie passing a slice
1049            let c = Cookie::parse(cookie_string.as_str()).unwrap();
1050            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1051        };
1052
1053        assert_eq!(name, Some("bar"));
1054        assert_eq!(value, Some("baz"));
1055        assert_eq!(path, Some("/subdir"));
1056        assert_eq!(domain, Some("crates.io"));
1057    }
1058
1059    #[test]
1060    fn owned_cookie_string() {
1061        let cookie_string =
1062            "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1063        let (name, value, path, domain) = {
1064            // Create a cookie passing an owned string
1065            let c = Cookie::parse(cookie_string).unwrap();
1066            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1067        };
1068
1069        assert_eq!(name, None);
1070        assert_eq!(value, None);
1071        assert_eq!(path, None);
1072        assert_eq!(domain, None);
1073    }
1074
1075    #[test]
1076    fn owned_cookie_struct() {
1077        let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
1078        let (name, value, path, domain) = {
1079            // Create an owned cookie
1080            let c = Cookie::parse(cookie_string).unwrap().into_owned();
1081
1082            (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1083        };
1084
1085        assert_eq!(name, None);
1086        assert_eq!(value, None);
1087        assert_eq!(path, None);
1088        assert_eq!(domain, None);
1089    }
1090
1091    #[test]
1092    fn format_encoded() {
1093        let cookie = Cookie::build("foo !?=", "bar;; a").finish();
1094        let cookie_str = cookie.encoded().to_string();
1095        assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a");
1096
1097        let cookie = Cookie::parse_encoded(cookie_str).unwrap();
1098        assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a"));
1099    }
1100}