Skip to main content

rust_web_server/cookie/
mod.rs

1#[cfg(test)]
2mod tests;
3
4/// A single HTTP cookie name/value pair.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Cookie {
7    pub name: String,
8    pub value: String,
9}
10
11/// Parses the `Cookie` request header into a collection of [`Cookie`] values.
12///
13/// # Example
14/// ```
15/// use rust_web_server::cookie::CookieJar;
16///
17/// let jar = CookieJar::parse("session=abc123; theme=dark");
18/// assert_eq!(jar.get("session").unwrap().value, "abc123");
19/// ```
20pub struct CookieJar {
21    pub cookies: Vec<Cookie>,
22}
23
24impl CookieJar {
25    /// Parses the raw value of the `Cookie` header (e.g. `"a=1; b=2"`).
26    pub fn parse(header_value: &str) -> CookieJar {
27        let cookies = header_value
28            .split(';')
29            .filter_map(|pair| {
30                let pair = pair.trim();
31                let mut parts = pair.splitn(2, '=');
32                let name = parts.next()?.trim().to_string();
33                let value = parts.next().unwrap_or("").trim().to_string();
34                if name.is_empty() { None } else { Some(Cookie { name, value }) }
35            })
36            .collect();
37        CookieJar { cookies }
38    }
39
40    /// Returns the first cookie with the given name, or `None`.
41    pub fn get(&self, name: &str) -> Option<&Cookie> {
42        self.cookies.iter().find(|c| c.name == name)
43    }
44}
45
46/// Builder for the `Set-Cookie` response header value.
47///
48/// # Example
49/// ```
50/// use rust_web_server::cookie::SetCookie;
51///
52/// let header_value = SetCookie::new("session", "abc123")
53///     .path("/")
54///     .http_only()
55///     .secure()
56///     .same_site("Lax")
57///     .build();
58///
59/// assert!(header_value.starts_with("session=abc123"));
60/// assert!(header_value.contains("HttpOnly"));
61/// ```
62pub struct SetCookie {
63    pub name: String,
64    pub value: String,
65    pub path: Option<String>,
66    pub domain: Option<String>,
67    pub max_age: Option<i64>,
68    pub secure: bool,
69    pub http_only: bool,
70    pub same_site: Option<String>,
71}
72
73impl SetCookie {
74    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
75        SetCookie {
76            name: name.into(),
77            value: value.into(),
78            path: None,
79            domain: None,
80            max_age: None,
81            secure: false,
82            http_only: false,
83            same_site: None,
84        }
85    }
86
87    pub fn path(mut self, path: impl Into<String>) -> Self {
88        self.path = Some(path.into());
89        self
90    }
91
92    pub fn domain(mut self, domain: impl Into<String>) -> Self {
93        self.domain = Some(domain.into());
94        self
95    }
96
97    pub fn max_age(mut self, seconds: i64) -> Self {
98        self.max_age = Some(seconds);
99        self
100    }
101
102    pub fn secure(mut self) -> Self {
103        self.secure = true;
104        self
105    }
106
107    pub fn http_only(mut self) -> Self {
108        self.http_only = true;
109        self
110    }
111
112    pub fn same_site(mut self, policy: impl Into<String>) -> Self {
113        self.same_site = Some(policy.into());
114        self
115    }
116
117    /// Builds the `Set-Cookie` header value string.
118    pub fn build(&self) -> String {
119        let mut s = format!("{}={}", self.name, self.value);
120        if let Some(ref p) = self.path { s.push_str(&format!("; Path={}", p)); }
121        if let Some(ref d) = self.domain { s.push_str(&format!("; Domain={}", d)); }
122        if let Some(age) = self.max_age { s.push_str(&format!("; Max-Age={}", age)); }
123        if self.secure { s.push_str("; Secure"); }
124        if self.http_only { s.push_str("; HttpOnly"); }
125        if let Some(ref ss) = self.same_site { s.push_str(&format!("; SameSite={}", ss)); }
126        s
127    }
128}