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
113
114
115
116
117
118
119
120
121
122
123
124
use std::collections::HashMap;

use crate::core::context::Context;
use crate::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,
    }
}