1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
use std::collections::HashMap;

use thruster_core::context::Context;
#[cfg(not(feature = "thruster_async_await"))]
use thruster_core::middleware::{MiddlewareReturnValue};
#[cfg(feature = "thruster_async_await")]
use thruster_core::middleware::{MiddlewareReturnValue};

#[derive(Debug)]
pub struct Cookie {
  pub key: String,
  pub value: String,
  pub options: CookieOptions
}

#[derive(Debug, PartialEq)]
pub enum SameSite {
  Strict,
  Lax
}

#[derive(Debug)]
pub struct CookieOptions {
  pub domain: String,
  pub path: String,
  pub expires: u64,
  pub http_only: bool,
  pub max_age: u64,
  pub secure: bool,
  pub signed: bool,
  pub same_site: Option<SameSite>
}

impl CookieOptions {
  pub fn default() -> CookieOptions {
    CookieOptions {
      domain: "".to_owned(),
      path: "/".to_owned(),
      expires: 0,
      http_only: false,
      max_age: 0,
      secure: false,
      signed: false,
      same_site: None
    }
  }
}

pub trait HasCookies {
  fn set_cookies(&mut self, cookies: Vec<Cookie>);
  fn headers(&self) -> HashMap<String, String>;
}

pub fn cookies<T: 'static + Context + HasCookies + Send>(mut context: T, next: impl Fn(T) -> MiddlewareReturnValue<T>  + Send) -> MiddlewareReturnValue<T> {
  let mut cookies = Vec::new();

  {
    let headers: HashMap<String, String> = context.headers();

    if let Some(val) = headers.get("set-cookie") {
      for cookie_string in val.split(',') {
        cookies.push(parse_string(cookie_string));
      }
    }
  }

  context.set_cookies(cookies);

  next(context)
}

fn parse_string(string: &str) -> Cookie {
  let mut options = CookieOptions::default();

  let mut pieces = string.split(';');

  let mut key_pair = pieces.next().unwrap().split('=');

  let key = key_pair.next().unwrap_or("").to_owned();
  let value = key_pair.next().unwrap_or("").to_owned();

  for option in pieces {
    let mut option_key_pair = option.split('=');

    if let Some(option_key) = option_key_pair.next() {
      match option_key.to_lowercase().trim() {
        "expires" => options.expires = option_key_pair.next().unwrap_or("0").parse::<u64>().unwrap_or(0),
        "max-age" => options.max_age = option_key_pair.next().unwrap_or("0").parse::<u64>().unwrap_or(0),
        "domain" => options.domain = option_key_pair.next().unwrap_or("").to_owned(),
        "path" => options.path = option_key_pair.next().unwrap_or("").to_owned(),
        "secure" => options.secure = true,
        "httponly" => options.http_only = true,
        "samesite" => {
          if let Some(same_site_value) = option_key_pair.next() {
            match same_site_value.to_lowercase().as_ref() {
              "strict" => options.same_site = Some(SameSite::Strict),
              "lax" => options.same_site = Some(SameSite::Lax),
              _ => ()
            };
          }
        }
        _ => ()
      };
    }
  }

  Cookie {
    key,
    value,
    options
  }
}