Skip to main content

rws_server/cookie/
mod.rs

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