1use chrono::Utc;
16use chrono::NaiveDate;
17use std::time::UNIX_EPOCH;
18use chrono::DateTime;
19use std::str::FromStr;
20use std::io::{Error, ErrorKind};
21use std::time::{Duration, SystemTime};
22use std::ops::Add;
23use std::collections::{HashMap, HashSet};
24use std::cmp::{PartialEq, Eq};
25use std::hash::{Hash, Hasher};
26use lazy_static::lazy_static;
27use regex::Regex;
28
29pub(crate) const COOKIE: &str = "cookie";
30pub(crate) const COOKIE_EXPIRES: &str = "expires";
31pub(crate) const COOKIE_MAX_AGE: &str = "max-age";
32pub(crate) const COOKIE_DOMAIN: &str = "domain";
33pub(crate) const COOKIE_PATH: &str = "path";
34pub(crate) const COOKIE_SAME_SITE: &str = "samesite";
35pub(crate) const COOKIE_SAME_SITE_STRICT: &str = "strict";
36pub(crate) const COOKIE_SAME_SITE_LAX: &str = "lax";
37pub(crate) const COOKIE_SAME_SITE_NONE: &str = "none";
38pub(crate) const COOKIE_SECURE: &str = "secure";
39pub(crate) const COOKIE_HTTP_ONLY: &str = "httponly";
40
41#[derive(Debug,Copy,Clone,PartialEq)]
43pub enum SameSiteValue {Strict, Lax, None}
44
45impl FromStr for SameSiteValue {
46 type Err = Error;
47
48 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 return match s {
50 COOKIE_SAME_SITE_STRICT => Ok(SameSiteValue::Strict),
51 COOKIE_SAME_SITE_LAX => Ok(SameSiteValue::Lax),
52 COOKIE_SAME_SITE_NONE => Ok(SameSiteValue::None),
53 _ => Err(
54 Error::new(ErrorKind::InvalidData,
55 format!("Invalid SameSite cookie directive value: {}", s)))
56 }
57
58 }
59}
60
61#[derive(Debug,Clone)]
70pub struct Cookie {
71 pub(crate) name: String,
73 pub (crate) value: String,
75 pub(crate) domain: String,
77 pub(crate) path: String,
79 pub(crate) expires: Option<SystemTime>,
82 pub(crate) same_site: SameSiteValue,
84 pub(crate) secure: bool,
86 pub(crate) http_only: bool,
88 pub(crate) extensions: HashMap<String, String>
90}
91
92
93impl Cookie {
94 pub fn new (name: String, value: String, domain: String, path: String) -> Cookie {
104 Cookie {
105 name,
106 value,
107 domain,
108 path,
109 expires: None,
110 same_site: SameSiteValue::Lax,
111 secure: false,
112 http_only: false,
113 extensions: HashMap::new()
114 }
115 }
116
117 pub fn name(& self) -> &str {
119 self.name.as_str()
120 }
121
122 pub fn value(& self) -> &str {
124 self.value.as_str()
125 }
126
127 pub fn domain(& self) -> &str {
129 self.domain.as_str()
130 }
131
132 pub fn path(& self) -> &str {
134 self.path.as_str()
135 }
136 pub fn expires(& self) -> Option<SystemTime> {
139 self.expires.clone()
140 }
141
142 pub fn same_site(& self) -> SameSiteValue {
144 self.same_site
145 }
146 pub fn secure(& self) -> bool {
148 self.secure
149 }
150 pub fn http_only(& self) -> bool {
152 self.http_only
153 }
154
155 pub fn extensions(&self) -> &HashMap<String, String> {
157 &self.extensions
158 }
159
160 pub fn path_match(&self, request_path: &str) -> bool {
164
165 let cookie_path = self.path();
166
167 let cookie_path_len = cookie_path.len();
168 let request_path_len = request_path.len();
169
170
171 if !request_path.starts_with(cookie_path) { return false;
173 }
174
175 return request_path_len == cookie_path_len || cookie_path.chars().nth(cookie_path_len - 1).unwrap() == '/'
178 || request_path.chars().nth(cookie_path_len).unwrap() == '/';
180 }
181
182 pub fn domain_match(&self, request_domain: &str) -> bool {
186 let cookie_domain = self.domain();
187
188 if let Some(index) = request_domain.rfind(cookie_domain) {
189 if index == 0 { return true;
191 }
192 return request_domain.chars().nth(index-1).unwrap() == '.';
194 }
195
196 return false;
197 }
198
199 pub fn request_match(&self, request_domain: &str, request_path: &str, secure: bool) -> bool {
201
202 if self.secure && !secure {
205 return false;
206 }
207
208 if self.same_site == SameSiteValue::Strict && self.domain != request_domain {
211 return false;
212 }
213
214 if self.same_site() == SameSiteValue::Lax && !self.domain_match(request_domain) {
216 return false;
217 }
218
219 if self.same_site == SameSiteValue::None && ! self.secure {
221 return false;
222 }
223
224 return self.path_match(request_path);
227 }
228
229 pub fn parse(s: &str, domain: &str, path: &str) -> Result<Cookie, Error> {
231 let mut components = s.split(';');
232
233 return if let Some(slice) = components.next() {
234 let (key, value) = parse_cookie_value(slice)?;
235 let mut cookie = Cookie::new(key, value, String::from(domain), String::from(path));
236 while let Some(param) = components.next() {
237 let directive = CookieDirective::from_str(param)?;
238 match directive {
239 CookieDirective::Expires(date) => {
240 if cookie.expires().is_none() { cookie.expires = Some(date);
242 }
243 },
244 CookieDirective::MaxAge(seconds) => {
245 cookie.expires = Some(SystemTime::now().add(seconds));
246 },
247 CookieDirective::Domain(url) => { cookie.domain = if let Some(stripped) = url.as_str().strip_prefix(".") {
249 String::from(stripped)
250 } else {
251 url
252 }
253 },
254 CookieDirective::Path(path) => cookie.path = path,
255 CookieDirective::SameSite(val) => cookie.same_site = val,
256 CookieDirective::Secure => cookie.secure = true,
257 CookieDirective::HttpOnly => cookie.http_only = true,
258 CookieDirective::Extension(name, value) => {
259 let _res = cookie.extensions.insert(name, value);
260 }
261 }
262 }
263 Ok(cookie)
264 } else {
265 if CookieDirective::from_str(s).is_ok() {
266 return Err(Error::new(ErrorKind::InvalidData, "Cookie has not got name/value"));
267 };
268
269 let (key, value) = parse_cookie_value(s)?;
270 Ok(Cookie::new(key, value, String::from(domain), String::from(path)))
271 }
272 }
273}
274
275impl PartialEq for Cookie {
276 fn eq(&self, other: &Self) -> bool {
277 self.name == other.name
278 }
279}
280
281impl Eq for Cookie{}
282
283impl Hash for Cookie {
284 fn hash<H: Hasher>(&self, state: &mut H) {
285 self.name.hash(state);
286 self.domain.hash(state);
287 }
288}
289
290
291pub(crate) fn parse_cookie_value(cookie: &str) -> Result<(String, String), Error>{
293 if let Some(index) = cookie.find('=') {
294 let key = String::from(cookie[0..index].trim());
295 let value = String::from(cookie[index + 1..].trim());
296 return Ok((key, value))
297 } else {
298 Err(Error::new(ErrorKind::InvalidData,
299 format!("Malformed HTTP cookie: {}", cookie)))
300 }
301}
302
303enum CookieDirective {
305 Expires(SystemTime),
306 MaxAge(Duration),
307 Domain(String),
308 Path(String),
309 SameSite(SameSiteValue),
310 Secure,
311 HttpOnly,
312 Extension(String, String)
313}
314
315const DATE_FORMAT_850: &str= "(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|Mon|Tue|Wed|Thu|Fri|Sat|Sun), \
317(0[1-9]|[123][0-9])-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-([0-9]{4}|[0-9]{2}) \
318([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]) GMT";
319
320const DATE_FORMAT_1123: &str= "(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \
322(0[1-9]|[123][0-9]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) \
323([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]) GMT";
324
325
326const DATE_FORMAT_ASCT: &str= "(Mon|Tue|Wed|Thu|Fri|Sat|Sun) \
328(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ ]{1,2}([1-9]|0[1-9]|[123][0-9]) \
329([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]) ([0-9]{4})";
330
331fn parse_rfc_850_date(date: &str) -> Result<SystemTime, Error> {
335 lazy_static! {
336 static ref RE: Regex = Regex::new(DATE_FORMAT_850).unwrap();
337 }
338
339
340 if let Some(captures) = RE.captures(date) {
341 let day : u32 = captures.get(2).unwrap().as_str().parse().unwrap();
343 let month = match captures.get(3).unwrap().as_str() {
344 "Jan" => 1,
345 "Feb" => 2,
346 "Mar" => 3,
347 "Apr" => 4,
348 "May" => 5,
349 "Jun" => 6,
350 "Jul" => 7,
351 "Aug" => 8,
352 "Sep" => 9,
353 "Oct" => 10,
354 "Nov" => 11,
355 "Dec" => 12,
356 _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid date"))
357 };
358
359 let mut year: i32 = captures.get(4).unwrap().as_str().parse().unwrap();
360 year+= if year < 70 {2000} else if year < 100 {1900} else {0};
362
363 let hour : u32 = captures.get(5).unwrap().as_str().parse().unwrap();
364 let min : u32 = captures.get(6).unwrap().as_str().parse().unwrap();
365 let secs : u32 = captures.get(7).unwrap().as_str().parse().unwrap();
366
367 let naive =
368 NaiveDate::from_ymd(year, month, day)
369 .and_hms(hour,min,secs);
370 let time = DateTime::<Utc>::from_utc(naive, Utc);
371 let millis = Duration::from_millis(time.timestamp_millis() as u64);
372 let time = UNIX_EPOCH.clone().add(millis);
373
374 return Ok(time);
375
376
377 } else {
378 return Err(Error::new(ErrorKind::InvalidData, "Invalid date"));
379 }
380}
381
382fn parse_rfc_1123_date(date: &str) -> Result<SystemTime, Error> {
385 lazy_static! {
386 static ref RE: Regex = Regex::new(DATE_FORMAT_1123).unwrap();
387 }
388
389
390 if let Some(captures) = RE.captures(date) {
391 let day : u32 = captures.get(2).unwrap().as_str().parse().unwrap();
393 let month = match captures.get(3).unwrap().as_str() {
394 "Jan" => 1,
395 "Feb" => 2,
396 "Mar" => 3,
397 "Apr" => 4,
398 "May" => 5,
399 "Jun" => 6,
400 "Jul" => 7,
401 "Aug" => 8,
402 "Sep" => 9,
403 "Oct" => 10,
404 "Nov" => 11,
405 "Dec" => 12,
406 _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid date"))
407 };
408
409 let year: i32 = captures.get(4).unwrap().as_str().parse().unwrap();
410
411 let hour : u32 = captures.get(5).unwrap().as_str().parse().unwrap();
412 let min : u32 = captures.get(6).unwrap().as_str().parse().unwrap();
413 let secs : u32 = captures.get(7).unwrap().as_str().parse().unwrap();
414
415 let naive =
416 NaiveDate::from_ymd(year, month, day)
417 .and_hms(hour,min,secs);
418 let time = DateTime::<Utc>::from_utc(naive, Utc);
419 let millis = Duration::from_millis(time.timestamp_millis() as u64);
420 let time = UNIX_EPOCH.clone().add(millis);
421
422 return Ok(time);
423
424
425 } else {
426 return Err(Error::new(ErrorKind::InvalidData, "Invalid date"));
427 }
428}
429
430fn parse_asct_date(date: &str) -> Result<SystemTime, Error> {
433 lazy_static! {
434 static ref RE: Regex = Regex::new(DATE_FORMAT_ASCT).unwrap();
435 }
436
437
438 if let Some(captures) = RE.captures(date) {
439 let month = match captures.get(2).unwrap().as_str() {
441 "Jan" => 1,
442 "Feb" => 2,
443 "Mar" => 3,
444 "Apr" => 4,
445 "May" => 5,
446 "Jun" => 6,
447 "Jul" => 7,
448 "Aug" => 8,
449 "Sep" => 9,
450 "Oct" => 10,
451 "Nov" => 11,
452 "Dec" => 12,
453 _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid date"))
454 };
455
456 let day : u32 = captures.get(3).unwrap().as_str().parse().unwrap();
457
458 let hour : u32 = captures.get(4).unwrap().as_str().parse().unwrap();
459 let min : u32 = captures.get(5).unwrap().as_str().parse().unwrap();
460 let secs : u32 = captures.get(6).unwrap().as_str().parse().unwrap();
461
462 let year: i32 = captures.get(7).unwrap().as_str().parse().unwrap();
463
464 let naive =
465 NaiveDate::from_ymd(year, month, day)
466 .and_hms(hour,min,secs);
467 let time = DateTime::<Utc>::from_utc(naive, Utc);
468 let millis = Duration::from_millis(time.timestamp_millis() as u64);
469 let time = UNIX_EPOCH.clone().add(millis);
470
471 return Ok(time);
472
473
474 } else {
475 return Err(Error::new(ErrorKind::InvalidData, "Invalid date"));
476 }
477}
478
479impl FromStr for CookieDirective {
481
482 type Err = Error;
483
484 fn from_str(s: &str) -> Result<CookieDirective,Error> {
485 if let Some(index) = s.find('=') { let key = s[0..index].trim().to_ascii_lowercase();
487 let value = s[index + 1..].trim();
488 return match key.as_str() {
489 COOKIE_EXPIRES => {
490 let expires = parse_rfc_1123_date(value)
491 .or_else(|_| parse_rfc_850_date(value))
492 .or_else(|_| parse_asct_date(value))?;
493
494 Ok(CookieDirective::Expires(expires))
495 },
496 COOKIE_MAX_AGE => { let digit = u64::from_str(value)
498 .or_else(|e| {
499 Err(Error::new(ErrorKind::InvalidData, e))
500 })?;
501 Ok(CookieDirective::MaxAge(Duration::from_secs(digit)))
502 },
503 COOKIE_DOMAIN => {
504 Ok(CookieDirective::Domain(String::from(value)))
505 },
506 COOKIE_PATH => {
507 Ok(CookieDirective::Path(String::from(value)))
508 }
509 COOKIE_SAME_SITE => {
510 let lower_case = value.to_ascii_lowercase();
511 match SameSiteValue::from_str(lower_case.as_str()) {
512 Ok(site_value) => Ok(CookieDirective::SameSite(site_value)),
513 Err(e) => Err(e)
514 }
515 },
516 _ => Ok(CookieDirective::Extension(key, value.to_string()))
517 }
518 } else {
519 match s.trim().to_ascii_lowercase().as_str() {
520 COOKIE_SECURE => Ok(CookieDirective::Secure),
521 COOKIE_HTTP_ONLY => Ok(CookieDirective::HttpOnly),
522 _ => return Err(
523 Error::new(ErrorKind::InvalidData,
524 format!("Invalid HTTP cookie directive: {}", s)))
525 }
526 }
527 }
528}
529
530pub trait CookieJar {
532 fn cookie(&mut self, value: Cookie, request_domain: &str);
534
535 fn active_cookies(&mut self, request_domain: &str, request_path: &str, secure: bool) -> Vec<(String, String)>;
537 }
538
539
540 pub struct MemCookieJar {
541 cookies: HashSet<Cookie>
543 }
544
545 impl MemCookieJar {
546 pub fn new() -> MemCookieJar{
547 MemCookieJar {
548 cookies: HashSet::new()
549 }
550 }
551 }
552
553
554 impl CookieJar for MemCookieJar {
555 fn cookie(&mut self, value: Cookie, request_domain: &str) {
556
557 if !value.domain_match(request_domain) {
558 return; }
560 let now = SystemTime::now();
561
562 if let Some(expires) = value.expires() {
563 if expires < now {
564 return; }
566 }
567
568 self.cookies.insert(value);
569 }
570
571 fn active_cookies(&mut self, request_domain: &str, request_path: &str, secure: bool) -> Vec<(String, String)> {
572
573 let mut result = Vec::new();
574
575 let now = SystemTime::now();
577
578 self.cookies.retain( |c| {
579 if let Some(time) = c.expires {
580 return time < now;
581 }
582 return true;
583 });
584
585 for cookie in self.cookies.iter() {
586 if cookie.request_match(request_domain, request_path, secure) {
587 result.push((cookie.name.clone(), cookie.value.clone()));
588 }
589 }
590
591 return result;
592 }
593 }
594
595#[cfg(test)]
596mod cookie_test;