Skip to main content

rama_http_headers/common/
set_cookie.rs

1use rama_http_types::{HeaderName, HeaderValue};
2
3use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
4
5/// `Set-Cookie` header, defined [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1)
6///
7/// The Set-Cookie HTTP response header is used to send cookies from the
8/// server to the user agent.
9///
10/// Informally, the Set-Cookie response header contains the header name
11/// "Set-Cookie" followed by a ":" and a cookie.  Each cookie begins with
12/// a name-value-pair, followed by zero or more attribute-value pairs.
13///
14/// # ABNF
15///
16/// ```text
17/// set-cookie-header = "Set-Cookie:" SP set-cookie-string
18/// set-cookie-string = cookie-pair *( ";" SP cookie-av )
19/// cookie-pair       = cookie-name "=" cookie-value
20/// cookie-name       = token
21/// cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
22/// cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
23///                       ; US-ASCII characters excluding CTLs,
24///                       ; whitespace DQUOTE, comma, semicolon,
25///                       ; and backslash
26/// token             = <token, defined in [RFC2616], Section 2.2>
27///
28/// cookie-av         = expires-av / max-age-av / domain-av /
29///                    path-av / secure-av / httponly-av /
30///                     extension-av
31/// expires-av        = "Expires=" sane-cookie-date
32/// sane-cookie-date  = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
33/// max-age-av        = "Max-Age=" non-zero-digit *DIGIT
34///                       ; In practice, both expires-av and max-age-av
35///                       ; are limited to dates representable by the
36///                       ; user agent.
37/// non-zero-digit    = %x31-39
38///                       ; digits 1 through 9
39/// domain-av         = "Domain=" domain-value
40/// domain-value      = <subdomain>
41///                       ; defined in [RFC1034], Section 3.5, as
42///                       ; enhanced by [RFC1123], Section 2.1
43/// path-av           = "Path=" path-value
44/// path-value        = <any CHAR except CTLs or ";">
45/// secure-av         = "Secure"
46/// httponly-av       = "HttpOnly"
47/// extension-av      = <any CHAR except CTLs or ";">
48/// ```
49///
50/// # Example values
51///
52/// * `SID=31d4d96e407aad42`
53/// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT`
54/// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT`
55/// * `lang=en-US; Path=/; Domain=example.com`
56///
57/// # Example
58#[derive(Clone, Debug)]
59pub struct SetCookie(Vec<HeaderValue>);
60
61impl TypedHeader for SetCookie {
62    fn name() -> &'static HeaderName {
63        &::rama_http_types::header::SET_COOKIE
64    }
65}
66
67// TODO: More powerful cookie API
68
69impl SetCookie {
70    #[inline]
71    // TODO: this probbly will be removed soon...
72    pub fn iter_header_values(&self) -> impl Iterator<Item = &HeaderValue> {
73        self.0.iter()
74    }
75}
76
77impl HeaderDecode for SetCookie {
78    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
79        let vec = values.cloned().collect::<Vec<_>>();
80
81        if !vec.is_empty() {
82            Ok(Self(vec))
83        } else {
84            Err(Error::invalid())
85        }
86    }
87}
88
89impl HeaderEncode for SetCookie {
90    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
91        values.extend(self.0.iter().cloned());
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::super::{test_decode, test_encode};
98    use super::*;
99
100    #[test]
101    fn decode() {
102        let set_cookie = test_decode::<SetCookie>(&["foo=bar", "baz=quux"]).unwrap();
103        assert_eq!(set_cookie.0.len(), 2);
104        assert_eq!(set_cookie.0[0], "foo=bar");
105        assert_eq!(set_cookie.0[1], "baz=quux");
106    }
107
108    #[test]
109    fn encode() {
110        let set_cookie = SetCookie(vec![
111            HeaderValue::from_static("foo=bar"),
112            HeaderValue::from_static("baz=quux"),
113        ]);
114
115        let headers = test_encode(set_cookie);
116        let mut vals = headers.get_all("set-cookie").into_iter();
117        assert_eq!(vals.next().unwrap(), "foo=bar");
118        assert_eq!(vals.next().unwrap(), "baz=quux");
119        assert_eq!(vals.next(), None);
120    }
121}