cookie_monster/cookie/
serialize.rs

1use crate::{
2    Error, SameSite,
3    cookie::parse::{find_invalid_cookie_value, is_token, trim_quotes},
4};
5
6use super::Cookie;
7use std::fmt::Write;
8
9impl Cookie {
10    pub fn serialize(&self) -> crate::Result<String> {
11        self.serialize_inner(|name, value, buf| {
12            // Unencdoded values need manual validation of the characters.
13
14            let trimmed_value = trim_quotes(value);
15
16            if let Some(invalid_char) = find_invalid_cookie_value(trimmed_value) {
17                return Err(Error::InvalidValue(invalid_char));
18            } else if !is_token(name) {
19                return Err(Error::InvalidName);
20            }
21
22            let _ = write!(buf, "{name}={value}");
23            Ok(())
24        })
25    }
26
27    #[cfg(feature = "percent-encode")]
28    pub fn serialize_encoded(&self) -> crate::Result<String> {
29        use crate::cookie::encoding::{encode_name, encode_value};
30
31        self.serialize_inner(|name, value, buf| {
32            // Encoded values don't require validation since the invalid characters are encoded.
33            let _ = write!(buf, "{}={}", encode_name(name), encode_value(value));
34            Ok(())
35        })
36    }
37
38    fn serialize_inner(
39        &self,
40        callback: impl Fn(&str, &str, &mut String) -> crate::Result<()>,
41    ) -> crate::Result<String> {
42        let value = self.value();
43        let name = self.name();
44        let domain = self.domain();
45        let path = self.path();
46
47        if name.is_empty() {
48            return Err(Error::NameEmpty);
49        }
50
51        let buf_len = name.len()
52            + value.len()
53            + domain.map(str::len).unwrap_or_default()
54            + path.map(str::len).unwrap_or_default();
55
56        // 110 is derived from typical length of cookie attributes
57        // see RFC 6265 Sec 4.1.
58        let mut buf = String::with_capacity(buf_len + 110);
59
60        // Write name and value
61        // Validation happens in the callback.
62        callback(name, value, &mut buf)?;
63
64        // Expires
65        if let Some(max_age) = self.max_age_secs() {
66            buf.push_str("; Max-Age=");
67            write!(&mut buf, "{max_age}").expect("Failed to write Max-Age seconds");
68        }
69
70        self.serialize_domain(&mut buf);
71
72        self.serialize_path(&mut buf)?;
73
74        // SameSite=None and Partitioned cookies need the Secure attribute
75        if self.secure() || self.partitioned() || self.same_site() == Some(SameSite::None) {
76            buf.push_str("; Secure");
77        }
78
79        if self.http_only() {
80            buf.push_str("; HttpOnly");
81        }
82
83        if self.partitioned() {
84            buf.push_str("; Partitioned");
85        }
86
87        self.serialize_same_site(&mut buf);
88
89        self.serialize_expire(&mut buf)?;
90        Ok(buf)
91    }
92}