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