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 buf_len = name.len()
67            + value.len()
68            + domain.map(str::len).unwrap_or_default()
69            + path.map(str::len).unwrap_or_default();
70
71        // 110 is derived from typical length of cookie attributes
72        // see RFC 6265 Sec 4.1.
73        let mut buf = String::with_capacity(buf_len + 110);
74
75        // Write name and value
76        // Validation happens in the callback.
77        callback(name, value, &mut buf)?;
78
79        // Expires
80        if let Some(max_age) = self.max_age_secs() {
81            buf.push_str("; Max-Age=");
82            write!(&mut buf, "{max_age}").expect("Failed to write Max-Age seconds");
83        }
84
85        self.serialize_domain(&mut buf);
86
87        self.serialize_path(&mut buf)?;
88
89        // SameSite=None and Partitioned cookies need the Secure attribute
90        if self.secure() || self.partitioned() || self.same_site() == Some(SameSite::None) {
91            buf.push_str("; Secure");
92        }
93
94        if self.http_only() {
95            buf.push_str("; HttpOnly");
96        }
97
98        if self.partitioned() {
99            buf.push_str("; Partitioned");
100        }
101
102        self.serialize_same_site(&mut buf);
103
104        self.serialize_expire(&mut buf)?;
105        Ok(buf)
106    }
107}