thruster_middleware/
cookies.rs

1use std::collections::HashMap;
2
3use thruster_core::context::Context;
4use thruster_core::middleware::{MiddlewareReturnValue};
5
6#[derive(Debug)]
7pub struct Cookie {
8  pub key: String,
9  pub value: String,
10  pub options: CookieOptions
11}
12
13#[derive(Debug, PartialEq)]
14pub enum SameSite {
15  Strict,
16  Lax
17}
18
19#[derive(Debug)]
20pub struct CookieOptions {
21  pub domain: String,
22  pub path: String,
23  pub expires: u64,
24  pub http_only: bool,
25  pub max_age: u64,
26  pub secure: bool,
27  pub signed: bool,
28  pub same_site: Option<SameSite>
29}
30
31impl CookieOptions {
32  pub fn default() -> CookieOptions {
33    CookieOptions {
34      domain: "".to_owned(),
35      path: "/".to_owned(),
36      expires: 0,
37      http_only: false,
38      max_age: 0,
39      secure: false,
40      signed: false,
41      same_site: None
42    }
43  }
44}
45
46pub trait HasCookies {
47  fn set_cookies(&mut self, cookies: Vec<Cookie>);
48  fn headers(&self) -> HashMap<String, String>;
49}
50
51pub fn cookies<T: 'static + Context + HasCookies + Send>(mut context: T, next: impl Fn(T) -> MiddlewareReturnValue<T>  + Send) -> MiddlewareReturnValue<T> {
52  let mut cookies = Vec::new();
53
54  {
55    let headers: HashMap<String, String> = context.headers();
56
57    if let Some(val) = headers.get("set-cookie") {
58      for cookie_string in val.split(',') {
59        cookies.push(parse_string(cookie_string));
60      }
61    }
62  }
63
64  context.set_cookies(cookies);
65
66  next(context)
67}
68
69fn parse_string(string: &str) -> Cookie {
70  let mut options = CookieOptions::default();
71
72  let mut pieces = string.split(';');
73
74  let mut key_pair = pieces.next().unwrap().split('=');
75
76  let key = key_pair.next().unwrap_or("").to_owned();
77  let value = key_pair.next().unwrap_or("").to_owned();
78
79  for option in pieces {
80    let mut option_key_pair = option.split('=');
81
82    if let Some(option_key) = option_key_pair.next() {
83      match option_key.to_lowercase().trim() {
84        "expires" => options.expires = option_key_pair.next().unwrap_or("0").parse::<u64>().unwrap_or(0),
85        "max-age" => options.max_age = option_key_pair.next().unwrap_or("0").parse::<u64>().unwrap_or(0),
86        "domain" => options.domain = option_key_pair.next().unwrap_or("").to_owned(),
87        "path" => options.path = option_key_pair.next().unwrap_or("").to_owned(),
88        "secure" => options.secure = true,
89        "httponly" => options.http_only = true,
90        "samesite" => {
91          if let Some(same_site_value) = option_key_pair.next() {
92            match same_site_value.to_lowercase().as_ref() {
93              "strict" => options.same_site = Some(SameSite::Strict),
94              "lax" => options.same_site = Some(SameSite::Lax),
95              _ => ()
96            };
97          }
98        }
99        _ => ()
100      };
101    }
102  }
103
104  Cookie {
105    key,
106    value,
107    options
108  }
109}