use http::{HeaderMap, HeaderValue, Request, header};
use std::collections::HashMap;
use crate::{BoxBody, BoxFuture, Error, extract::FromRequest};
#[derive(Debug, Clone, Default)]
pub struct Cookies(pub HashMap<String, String>);
impl Cookies {
pub fn get(&self, name: &str) -> Option<&str> {
self.0.get(name).map(|s| s.as_str())
}
}
impl FromRequest for Cookies {
fn extract(
req: Request<BoxBody>,
) -> BoxFuture<'static, Result<(Self, Request<BoxBody>), Error>> {
Box::pin(async move {
let mut map = HashMap::new();
if let Some(cookie_header) = req.headers().get(header::COOKIE)
&& let Ok(raw) = cookie_header.to_str()
{
for pair in raw.split(';') {
if let Some((k, v)) = pair.split_once('=') {
map.insert(k.trim().to_string(), v.trim().to_string());
}
}
}
Ok((Cookies(map), req))
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum SameSite {
Strict,
Lax,
None,
}
#[derive(Debug, Clone)]
pub struct Cookie {
name: String,
value: String,
path: Option<String>,
http_only: bool,
secure: bool,
same_site: Option<SameSite>,
max_age_secs: Option<i64>,
}
impl Cookie {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
path: Some("/".to_string()),
http_only: true,
secure: false,
same_site: Some(SameSite::Lax),
max_age_secs: None,
}
}
pub fn removal(name: impl Into<String>) -> Self {
Self::new(name, "").max_age(0)
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub fn http_only(mut self, enabled: bool) -> Self {
self.http_only = enabled;
self
}
pub fn secure(mut self, enabled: bool) -> Self {
self.secure = enabled;
self
}
pub fn same_site(mut self, policy: SameSite) -> Self {
self.same_site = Some(policy);
self
}
pub fn max_age(mut self, seconds: i64) -> Self {
self.max_age_secs = Some(seconds);
self
}
pub fn to_header_string(&self) -> String {
let mut parts = vec![format!("{}={}", self.name, self.value)];
if let Some(ref p) = self.path {
parts.push(format!("Path={}", p));
}
if let Some(age) = self.max_age_secs {
parts.push(format!("Max-Age={}", age));
}
if self.http_only {
parts.push("HttpOnly".to_string());
}
if self.secure {
parts.push("Secure".to_string());
}
if let Some(site) = self.same_site {
let s = match site {
SameSite::Strict => "Strict",
SameSite::Lax => "Lax",
SameSite::None => "None",
};
parts.push(format!("SameSite={}", s));
}
parts.join("; ")
}
pub fn append_to_headers(&self, headers: &mut HeaderMap) {
if let Ok(val) = HeaderValue::from_str(&self.to_header_string()) {
headers.append(header::SET_COOKIE, val);
}
}
}