immortal_http/
cookie.rs

1
2use std::fmt::Display;
3
4use crate::util::KVParser;
5
6#[allow(dead_code)]
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum SameSite {
9    Undefined,
10    No,
11    Lax,
12    Strict,
13}
14
15/// Cookies are a high-level representation used for serialisation and deserialisation of browser
16/// cookies.
17#[derive(Debug, Clone)]
18pub struct Cookie<'buf> {
19    pub name: &'buf str,
20    pub value: String,
21    pub secure: bool,
22    pub http_only: bool,
23    pub same_site: SameSite,
24    pub domain: &'buf str,
25    pub path: &'buf str,
26    pub max_age: i64,
27}
28
29impl<'buf> Default for Cookie<'buf> {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl<'buf> Display for Cookie<'buf> {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        let mut out = format!("{}={}", self.name, self.value);
38        if self.secure {
39            out += "; Secure";
40        }
41        if self.http_only {
42            out += "; HttpOnly";
43        }
44        out += match self.same_site {
45            SameSite::Undefined => "",
46            SameSite::No => "; SameSite=No",
47            SameSite::Lax => "; SameSite=Lax",
48            SameSite::Strict => "; SameSite=Strict",
49        };
50        if !self.domain.is_empty() {
51            out += &format!("; Domain={}", self.domain);
52        }
53        if !self.path.is_empty() {
54            out += &format!("; Path={}", self.path);
55        }
56        if self.max_age > 0 {
57            out += &format!("; Max-Age={}", self.max_age);
58        }
59        write!(f, "{out}")
60    }
61}
62
63impl<'buf> Cookie<'buf> {
64    pub fn new() -> Self {
65        Self {
66            name: "",
67            value: "".to_string(),
68            secure: false,
69            http_only: false,
70            same_site: SameSite::Undefined,
71            domain: "",
72            path: "",
73            max_age: -1i64,
74        }
75    }
76    
77    /// Exposes the builder pattern
78    pub fn builder() -> CookieBuilder<'buf> {
79        CookieBuilder::new()
80    }
81}
82
83#[derive(Default)]
84pub struct CookieBuilder<'buf> {
85    cookie: Cookie<'buf>,
86}
87
88#[allow(dead_code)]
89impl<'buf> CookieBuilder<'buf> {
90    pub fn new() -> Self {
91        Self {
92            cookie: Cookie::new(),
93        }
94    }
95
96    pub fn name(mut self, name: &'buf str) -> Self {
97        self.cookie.name = name;
98        self
99    }
100
101    pub fn value(mut self, value: &str) -> Self {
102        self.cookie.value = value.to_string();
103        self
104    }
105
106    pub fn secure(mut self, secure: bool) -> Self {
107        self.cookie.secure = secure;
108        self
109    }
110
111    pub fn http_only(mut self, http_only: bool) -> Self {
112        self.cookie.http_only = http_only;
113        self
114    }
115
116    pub fn same_site(mut self, same_site: SameSite) -> Self {
117        self.cookie.same_site = same_site;
118        self
119    }
120
121    pub fn domain(mut self, domain: &'buf str) -> Self {
122        self.cookie.domain = domain;
123        self
124    }
125
126    pub fn path(mut self, path: &'buf str) -> Self {
127        self.cookie.path = path;
128        self
129    }
130
131    pub fn max_age(mut self, max_age: i64) -> Self {
132        self.cookie.max_age = max_age;
133        self
134    }
135
136    pub fn build(self) -> Cookie<'buf> {
137        self.cookie
138    }
139}
140
141/// Take a string containing arbitrary HTTP cookies and parse them into Cookie structs
142///
143/// note: cookies parsed this way will only have their name and value members filled out, as 
144/// browsers do not echo the other components of the cookie in requests.
145pub fn parse_cookies(raw_cookies: &str) -> Vec<Cookie> {
146    if raw_cookies.is_empty() { return Vec::new() }
147
148    let mut cookies: Vec<Cookie> = Vec::new();
149    let mut cookie = Cookie::new();
150
151    for component in raw_cookies.split(';') {
152        let mut pp = KVParser::new(component.trim());
153        if let Some((key, value)) = pp.cookie_kv_pair() {
154            cookie.name = key;
155            cookie.value = value.to_string();
156            cookies.push(cookie);
157            cookie = Cookie::new();
158        }
159    }
160
161    cookies
162}
163