rusty_express/core/
cookie.rs1use chrono::prelude::*;
2use std::cmp::Ordering;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5#[derive(PartialEq, Eq, Hash, Clone)]
6pub enum KeyPrefix {
7 Secure,
8 Host,
9}
10
11pub struct Cookie {
12 key: String,
13 value: String,
14 key_prefix: Option<KeyPrefix>,
15 expires: Option<SystemTime>,
16 max_age: Option<u32>,
17 domain: String,
18 path: String,
19 secure: bool,
20 http_only: bool,
21}
22
23impl Cookie {
24 pub fn new(key: &str, value: &str) -> Self {
25 Cookie {
26 key: key.to_owned(),
27 value: value.to_owned(),
28 key_prefix: None,
29 expires: None,
30 max_age: None,
31 domain: String::new(),
32 path: String::new(),
33 secure: false,
34 http_only: false,
35 }
36 }
37
38 pub fn set_key_prefix(&mut self, prefix: Option<KeyPrefix>) {
39 self.key_prefix = match prefix {
40 Some(KeyPrefix::Secure) => {
41 self.secure = true;
42 prefix
43 }
44 Some(KeyPrefix::Host) => {
45 if !self.domain.is_empty() {
46 self.domain.clear();
47 }
48 self.path = String::from("/");
49 self.secure = true;
50 prefix
51 }
52 _ => prefix,
53 };
54 }
55
56 pub fn set_expires(&mut self, expires_at: Option<SystemTime>) {
57 self.expires = match expires_at {
58 Some(time) if time.cmp(&SystemTime::now()) != Ordering::Greater => None,
59 _ => expires_at,
60 };
61 }
62
63 pub fn set_max_age(&mut self, max_age: Option<u32>) {
64 self.max_age = max_age;
65 }
66
67 pub fn set_path(&mut self, path: &str) {
68 self.path = match self.key_prefix {
69 Some(KeyPrefix::Host) => String::new(),
70 _ if path.is_empty() => String::new(),
71 _ => {
72 if path.starts_with('/') {
73 path.to_owned()
74 } else {
75 panic!("Cookie path must start with '/'");
76 }
77 }
78 };
79 }
80
81 pub fn set_domain(&mut self, domain: &str) {
82 self.domain = match self.key_prefix {
83 Some(KeyPrefix::Host) => String::new(),
84 _ => domain.to_owned(),
85 }
86 }
87
88 pub fn set_secure_attr(&mut self, is_secure: bool) {
89 self.secure = match self.key_prefix {
90 Some(KeyPrefix::Host) | Some(KeyPrefix::Secure) => true,
91 _ => is_secure,
92 };
93 }
94
95 pub fn set_http_only_attr(&mut self, http_only: bool) {
96 self.http_only = http_only;
97 }
98
99 pub fn update_session_key(&mut self, key: &str) {
100 if key.is_empty() {
101 panic!("Session key must have a value!");
102 }
103 self.key = key.to_owned();
104 }
105
106 pub fn update_session_value(&mut self, value: &str) {
107 if value.is_empty() {
108 panic!("Session key must have a value!");
109 }
110 self.value = value.to_owned();
111 }
112
113 pub fn is_valid(&self) -> bool {
114 (!self.key.is_empty()) && (!self.value.is_empty())
115 }
116
117 pub fn get_cookie_key(&self) -> String {
118 self.key.to_owned()
119 }
120
121 pub fn get_cookie_value(&self) -> String {
122 self.value.to_owned()
123 }
124}
125
126impl ToString for Cookie {
127 fn to_string(&self) -> String {
128 if self.key.is_empty() || self.value.is_empty() {
129 return String::new();
130 }
131
132 let mut cookie = match self.key_prefix {
133 Some(KeyPrefix::Secure) => {
134 ["__Secure-", &self.key[..], "=", &self.value[..], ";"].join("")
135 }
136 Some(KeyPrefix::Host) => ["__Host-", &self.key[..], "=", &self.value[..], ";"].join(""),
137 _ => [&self.key[..], "=", &self.value[..], ";"].join(""),
138 };
139
140 if let Some(time) = self.expires {
141 let dt = system_to_utc(time)
142 .format("%a, %e %b %Y %T GMT")
143 .to_string();
144
145 cookie.reserve_exact(10 + dt.len());
146 cookie.push_str(" Expires=");
147 cookie.push_str(&dt);
148 cookie.push(';');
149 }
150
151 match self.max_age {
152 Some(age) if age > 0 => {
153 let a = age.to_string();
154
155 cookie.reserve_exact(10 + a.len());
156 cookie.push_str(" Max-Age=");
157 cookie.push_str(&a);
158 cookie.push(';');
159 }
160 _ => { }
161 }
162
163 if !self.domain.is_empty() {
164 cookie.reserve_exact(9 + self.domain.len());
165
166 cookie.push_str(" Domain=");
167 cookie.push_str(&self.domain);
168 cookie.push(';');
169 }
170
171 if !self.path.is_empty() {
172 cookie.reserve_exact(7 + self.path.len());
173
174 cookie.push_str(" Path=");
175 cookie.push_str(&self.path);
176 cookie.push(';');
177 }
178
179 if self.secure {
180 cookie.push_str(" Secure;");
181 }
182
183 if self.http_only {
184 cookie.push_str(" HttpOnly;");
185 }
186
187 cookie
188 }
189}
190
191impl Clone for Cookie {
192 fn clone(&self) -> Self {
193 Cookie {
194 key: self.key.clone(),
195 value: self.value.clone(),
196 key_prefix: self.key_prefix.clone(),
197 expires: self.expires,
198 max_age: self.max_age,
199 domain: self.domain.clone(),
200 path: self.path.clone(),
201 secure: self.secure,
202 http_only: self.http_only,
203 }
204 }
205}
206
207fn system_to_utc(t: SystemTime) -> DateTime<Utc> {
208 let (sec, n_sec) = match t.duration_since(UNIX_EPOCH) {
209 Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()),
210 Err(e) => {
211 let dur = e.duration();
212 let (sec, n_sec) = (dur.as_secs() as i64, dur.subsec_nanos());
213 if n_sec == 0 {
214 (-sec, 0)
215 } else {
216 (-sec - 1, 1_000_000_000 - n_sec)
217 }
218 }
219 };
220
221 Utc.timestamp(sec, n_sec)
222}