Skip to main content

cookie_monster/cookie/
serialize.rs

1use crate::{
2    Error, SameSite,
3    cookie::parse::{find_invalid_cookie_value, invalid_token, trim_quotes},
4};
5
6use super::Cookie;
7use std::fmt::Write;
8
9impl Cookie {
10    /// Serializes the cookie. Errors when:
11    /// * The name is empty.
12    /// * Name or value contain invalid cookie character.
13    /// * Path attribute is empty.
14    /// * Path attribute does not start with a leading '/'.
15    /// * Path attribute contains an invalid cookie character.
16    ///
17    /// Ignores domains with invalid cookie characters.
18    pub fn serialize(&self) -> crate::Result<String> {
19        self.serialize_inner(|name, value, buf| {
20            // Unencdoded values need manual validation of the characters.
21
22            let trimmed_value = trim_quotes(value);
23
24            if let Some(invalid_char) = find_invalid_cookie_value(trimmed_value) {
25                return Err(Error::InvalidValue(invalid_char));
26            } else if let Some(token) = invalid_token(name) {
27                return Err(Error::InvalidName(token));
28            }
29
30            let _ = write!(buf, "{name}={value}");
31            Ok(())
32        })
33    }
34
35    /// Serializes and percent encodes the cookie. Errors when:
36    /// * The name is empty.
37    /// * Path attribute is empty.
38    /// * Path attribute does not start with a leading '/'.
39    /// * Path attribute contains an invalid cookie character.
40    ///
41    /// Ignores domains with invalid cookie characters.
42    #[cfg(feature = "percent-encode")]
43    pub fn serialize_encoded(&self) -> crate::Result<String> {
44        use crate::cookie::encoding::{encode_name, encode_value};
45
46        self.serialize_inner(|name, value, buf| {
47            // Encoded values don't require validation since the invalid characters are encoded.
48            let _ = write!(buf, "{}={}", encode_name(name), encode_value(value));
49            Ok(())
50        })
51    }
52
53    fn serialize_inner(
54        &self,
55        callback: impl Fn(&str, &str, &mut String) -> crate::Result<()>,
56    ) -> crate::Result<String> {
57        let value = self.value();
58        let name = self.name();
59        let domain = self.domain();
60        let path = self.path();
61
62        if name.is_empty() {
63            return Err(Error::NameEmpty);
64        }
65
66        let prefix = self.prefix.map(|p| p.as_str()).unwrap_or_default();
67
68        let buf_len = prefix.len()
69            + name.len()
70            + value.len()
71            + domain.map(str::len).unwrap_or_default()
72            + path.map(str::len).unwrap_or_default();
73
74        // 110 is derived from typical length of cookie attributes
75        // see RFC 6265 Sec 4.1.
76        let mut buf = String::with_capacity(buf_len + 110);
77
78        // Re-apply the recognized name prefix (`__Host-` / `__Secure-`), if any.
79        buf.push_str(prefix);
80
81        // Write name and value
82        // Validation happens in the callback.
83        callback(name, value, &mut buf)?;
84
85        // Expires
86        if let Some(max_age) = self.max_age_secs() {
87            buf.push_str("; Max-Age=");
88            write!(&mut buf, "{max_age}").expect("Failed to write Max-Age seconds");
89        }
90
91        self.serialize_domain(&mut buf);
92
93        self.serialize_path(&mut buf)?;
94
95        // SameSite=None and Partitioned cookies need the Secure attribute
96        if self.is_secure() || self.is_partitioned() || self.same_site() == Some(SameSite::None) {
97            buf.push_str("; Secure");
98        }
99
100        if self.is_http_only() {
101            buf.push_str("; HttpOnly");
102        }
103
104        if self.is_partitioned() {
105            buf.push_str("; Partitioned");
106        }
107
108        self.serialize_same_site(&mut buf);
109
110        self.serialize_expire(&mut buf)?;
111        Ok(buf)
112    }
113}