hyperx/header/common/set_cookie.rs
1use header::{Header, RawLike};
2use std::fmt;
3use std::str::from_utf8;
4
5/// `Set-Cookie` header, defined [RFC6265](http://tools.ietf.org/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/// `SetCookie` _must not_ be encoded as a comma-delimited list. For this
15/// reason, it doesn't implement `fmt::Display` (and `std::string::ToString`).
16///
17/// Consider using the _cookie_ crate for parsing/decoding or encoding
18/// cookie values. [Usage example].
19///
20/// [Usage example]: https://github.com/dekellum/hyperx/issues/30#issuecomment-733151305
21///
22/// # ABNF
23///
24/// ```text
25/// set-cookie-header = "Set-Cookie:" SP set-cookie-string
26/// set-cookie-string = cookie-pair *( ";" SP cookie-av )
27/// cookie-pair = cookie-name "=" cookie-value
28/// cookie-name = token
29/// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
30/// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
31/// ; US-ASCII characters excluding CTLs,
32/// ; whitespace DQUOTE, comma, semicolon,
33/// ; and backslash
34/// token = <token, defined in [RFC2616], Section 2.2>
35///
36/// cookie-av = expires-av / max-age-av / domain-av /
37/// path-av / secure-av / httponly-av /
38/// extension-av
39/// expires-av = "Expires=" sane-cookie-date
40/// sane-cookie-date = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
41/// max-age-av = "Max-Age=" non-zero-digit *DIGIT
42/// ; In practice, both expires-av and max-age-av
43/// ; are limited to dates representable by the
44/// ; user agent.
45/// non-zero-digit = %x31-39
46/// ; digits 1 through 9
47/// domain-av = "Domain=" domain-value
48/// domain-value = <subdomain>
49/// ; defined in [RFC1034], Section 3.5, as
50/// ; enhanced by [RFC1123], Section 2.1
51/// path-av = "Path=" path-value
52/// path-value = <any CHAR except CTLs or ";">
53/// secure-av = "Secure"
54/// httponly-av = "HttpOnly"
55/// extension-av = <any CHAR except CTLs or ";">
56/// ```
57///
58/// # Example values
59///
60/// * `SID=31d4d96e407aad42`
61/// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT`
62/// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT`
63/// * `lang=en-US; Path=/; Domain=example.com`
64///
65/// # Example
66///
67/// ```
68/// # extern crate http;
69/// use hyperx::header::{SetCookie, TypedHeaders};
70///
71/// let mut headers = http::HeaderMap::new();
72///
73/// headers.append(
74/// http::header::SET_COOKIE,
75/// "foo=bar; Path=/path; Domain=example.com".parse().unwrap()
76/// );
77/// let cookie: SetCookie = headers.decode().unwrap();
78/// ```
79#[derive(Clone, PartialEq, Debug)]
80pub struct SetCookie(pub Vec<String>);
81
82__hyper__deref!(SetCookie => Vec<String>);
83
84impl Header for SetCookie {
85 fn header_name() -> &'static str {
86 static NAME: &'static str = "Set-Cookie";
87 NAME
88 }
89
90 fn parse_header<'a, T>(raw: &'a T) -> ::Result<SetCookie>
91 where T: RawLike<'a>
92 {
93 let mut set_cookies = Vec::with_capacity(raw.len());
94 for set_cookies_raw in raw.iter() {
95 if let Ok(s) = from_utf8(&set_cookies_raw[..]) {
96 set_cookies.push(s.trim().to_owned());
97 }
98 }
99
100 if !set_cookies.is_empty() {
101 Ok(SetCookie(set_cookies))
102 } else {
103 Err(::Error::Header)
104 }
105 }
106
107 fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
108 for cookie in &self.0 {
109 f.fmt_line(cookie)?;
110 }
111 Ok(())
112 }
113}
114
115#[cfg(feature = "headers")]
116#[test]
117fn test_set_cookie_fmt() {
118 use ::header::Headers;
119 let mut headers = Headers::new();
120 headers.set(SetCookie(vec![
121 "foo=bar".into(),
122 "baz=quux".into(),
123 ]));
124 assert_eq!(headers.to_string(), "Set-Cookie: foo=bar\r\nSet-Cookie: baz=quux\r\n");
125}
126
127standard_header!(SetCookie, SET_COOKIE);